{"id":38,"date":"2017-10-28T05:24:11","date_gmt":"2017-10-28T05:24:11","guid":{"rendered":"http:\/\/edspace.com\/blog\/?p=38"},"modified":"2017-10-28T05:34:29","modified_gmt":"2017-10-28T05:34:29","slug":"explain-this-to-me-as-if-i-was-a-small-child-developer-test-driven-development","status":"publish","type":"post","link":"http:\/\/edspace.com\/blog\/2017\/10\/28\/explain-this-to-me-as-if-i-was-a-small-child-developer-test-driven-development\/","title":{"rendered":"Explain this to me as if I was a small child &#8211; Developer Test Driven Development"},"content":{"rendered":"<p>There are so many ways to do testing. It all depends on the technology, the framework, and what automation tools your organization is willing to invest in. Automated testing is a great way to assert sanity in your build, especially when multiple people are touching the same segments of code. Two main approaches to testing here are Behavior Driven Development, which is usually done for acceptance testing, and Test Driven Development, which is used by developers to validate specification. This write-up will cover the latter.<\/p>\n<p>So you&#8217;ve been told to write unit tests with a Test Driven Development (TDD) approach. What does that mean? You&#8217;ve read that you write a little code, and test a little code. You may have read you write all you tests first (daunting!). Sounds foreign to you, so you say, &#8220;screw it, I&#8217;ll use the debugger like I usually do.&#8221; Well, if you write good unit tests, you probably won&#8217;t ever need a debugger, and in the chance you actually need to run the debugger, it&#8217;ll get you to what you need much quicker.<\/p>\n<p>How do you do it? Enter the <em>xUnit<\/em> the framework. <em>xUnit<\/em> is simply a loose collection of testing frameworks modeled off of the original SUnit framework used with SmallTalk. Just about every language has a clone of this framework you can use, e.g. <em>jUnit<\/em> for Java and <em>Test::Class<\/em> for Perl. In this example, we&#8217;ll be using <em>unittest.py<\/em> for Python.<\/p>\n<p>So let&#8217;s get to some code. Let&#8217;s say we&#8217;re trying to write a rudimentary encryption library. We&#8217;ll use something stupid easy, like <a href=\"https:\/\/en.wikipedia.org\/wiki\/ROT13\">ROT13<\/a>. (Note, don&#8217;t <strong><em>ever<\/em><\/strong> use ROT13 as an encryption format). Let&#8217;s start by creating a blank class.<\/p>\n<pre class=\"lang:python decode:true\" title=\"ROT13 - Class stub\">class ROT13(object):\r\n    pass\r\n<\/pre>\n<p>Now at this point, believe it or not, you&#8217;re ready to write your first test. Why would you write a test for something that doesn&#8217;t have much content? There are many reasons. First, it&#8217;s to make sure you spelled things right. Sure, in the age of IDEs, this isn&#8217;t supposed to happen, but it can if you have to make a one-off change in a text editor. Second, it can teach you things about a new language, like, &#8220;Does this language provide a default constructor?&#8221;<\/p>\n<p>And third, which I think is most important, this allows you to define the API, or at least put down some semblance of a specification that you&#8217;re given. The interface should, more often than not, be dictated by how the library is going to be used, not by the data store or back end technology that it wraps.<\/p>\n<p>Let&#8217;s start with the basic test stub and add a basic test. We&#8217;ll say that we want to at least be able to instantiate a <em>ROT13<\/em> object and print out the ciphertext.<\/p>\n<pre class=\"lang:python decode:true\" title=\"ROT13Test - Initial test\">from rot13 import ROT13\r\nimport unittest\r\n\r\nclass ROT13Test(unittest.TestCase):\r\n\r\n    def test_defaultconstructor(self):\r\n        rot13obj = ROT13()\r\n        print(rot13obj)\r\n\r\nif __name__==\"__main__\":\r\n    unittest.main()\r\n<\/pre>\n<p>Our custom test class inherits from <em>unittest.TestCase<\/em>, which gives us access to a bunch of other methods we&#8217;ll come across later. The test method, starting with <em>test<\/em>, will get executed every run. The final line in the main body simply executes all test cases inheriting <em>TestCase<\/em>. Let&#8217;s run it to see what happens.<\/p>\n<pre class=\"lang:sh decode:true \">$ python3 rot13_test.py\r\n&lt;rot13.ROT13 object at 0x7febbdbf3ef0&gt;\r\n.\r\n----------------------------------------------------------------------\r\nRan 1 test in 0.000s\r\n\r\nOK\r\n<\/pre>\n<p>Hmm, okay, it passed. This tells us two things: Python provides us with a default constructor, and it also provides a default string representation. Now, the default string output is not quite what we wanted. Let&#8217;s define our interface such that we have a constructor that takes in a plaintext, and our string representation gives us the ciphertext. We&#8217;ll keep our old test to make sure our old behavior still works, but we&#8217;ll add a new test to confirm our new behavior:<\/p>\n<pre class=\"lang:python decode:true \">    def test_basicencrypt(self):\r\n        rot13obj = ROT13(\"AAAA\")\r\n        self.assertEqual(\r\n            \"NNNN\",\r\n            str(rot13obj),\r\n            \"Should translate AAAA as NNNN\")<\/pre>\n<p>Running our test again, we&#8217;ll see that it fails.<\/p>\n<pre class=\"lang:sh decode:true \">$ python3 rot13_test.py\r\nE&lt;rot13.ROT13 object at 0x7f5e391ecb00&gt;\r\n.\r\n======================================================================\r\nERROR: test_basicencrypt (__main__.ROT13Test)\r\n----------------------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File \"rot13_test.py\", line 11, in test_basicencrypt\r\n    rot13obj = ROT13(\"AAAA\")\r\nTypeError: object() takes no parameters\r\n\r\n----------------------------------------------------------------------\r\nRan 2 tests in 0.000s\r\n\r\nFAILED (errors=1)\r\n<\/pre>\n<p>Now, we can go back to our class and an optional parameter so that we can satisfy both tests.<\/p>\n<pre class=\"lang:python decode:true\">class ROT13:\r\n\r\n    def __init__(self, plaintext=\"\"):\r\n        self.plaintext = plaintext\r\n<\/pre>\n<p>Running our tests again, we&#8217;ll see that we get passed the first error, but are presented with another error:<\/p>\n<pre class=\"lang:sh decode:true \">$ python3 rot13_test.py\r\nF&lt;rot13.ROT13 object at 0x7f585ee52d30&gt;\r\n.\r\n======================================================================\r\nFAIL: test_basicencrypt (__main__.ROT13Test)\r\n----------------------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File \"rot13_test.py\", line 15, in test_basicencrypt\r\n    \"Should translate AAAA as NNNN\")\r\nAssertionError: 'NNNN' != '&lt;rot13.ROT13 object at 0x7f585ee52d30&gt;'\r\n- NNNN\r\n+ &lt;rot13.ROT13 object at 0x7f585ee52d30&gt;\r\n : Should translate AAAA as NNNN\r\n\r\n----------------------------------------------------------------------\r\nRan 2 tests in 0.001s\r\n\r\nFAILED (failures=1)\r\n<\/pre>\n<p>(As a side note, in writing this, since I&#8217;ve been living in Java-land for the past year, I forgot that the constructor in Python is the <em>__init__() <\/em>method, and not <em>ROT13()<\/em>. Having a unit test is a quick way to remind me that I&#8217;m doing it wrong.)<\/p>\n<p>In looking at the output, we still need to correct our <em>str()<\/em> output. Well, let&#8217;s correct that real quick.<\/p>\n<pre class=\"lang:python decode:true \">class ROT13(object):\r\n\r\n    def __init__(self, plaintext=\"\"):\r\n        self.plaintext = plaintext\r\n\r\n    def __str__(self):\r\n        return \"NNNN\"\r\n<\/pre>\n<p>I know&#8230; this is so cheating, but it works! If you run your tests again, all will pass. At some point in your professional life, you will find yourself doing this, whether due to time constraints or to some loose requirement that you don&#8217;t quite understand how to implement. This is why you have these tests and multiple versions of these test to make sure what you&#8217;re testing works as intended.<\/p>\n<p>Let&#8217;s clean it up and make it work with anything, and not just with <em>AAAA<\/em>. What we&#8217;ll do is take each letter in the plaintext, add 13 to the ordinal number of the letter, then convert that ordinal number back into a character using UTC encoding. We can do it in one line.<\/p>\n<pre class=\"lang:python decode:true \">    def __str__(self):\r\n        return \"\".join([chr(ord(c)+13) for c in self.plaintext])\r\n<\/pre>\n<p>Running this will also cause our tests to pass. Great!<\/p>\n<p>However, this will only work with letters up to <em>M<\/em>. What happens when we do something <em>N<\/em> and beyond?<\/p>\n<pre class=\"lang:sh decode:true \" title=\"ROT13 test run - failed after latter half\">$ python3 rot13_test.py\r\n.\r\n.F\r\n======================================================================\r\nFAIL: test_encryptlatterhalfofalphabet (__main__.ROT13Test)\r\n----------------------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File \"rot13_test.py\", line 22, in test_encryptlatterhalfofalphabet\r\n    \"NUNS should translate as AHAF\")\r\nAssertionError: 'AHAF' != '[b[`'\r\n- AHAF\r\n+ [b[`\r\n : NUNS should translate as AHAF\r\n\r\n----------------------------------------------------------------------\r\nRan 3 tests in 0.001s\r\n<\/pre>\n<p>Well, that&#8217;s to be expected. We&#8217;ll have to add some code to account for the wraparound after &#8216;Z&#8217;. Let&#8217;s introduce a helper method that translates one character at a time, and we&#8217;ll have our __<em>str__()<\/em> method still do a list comprehension and call our helper method. The helper method will simply check to see<\/p>\n<pre class=\"lang:python decode:true \" title=\"rot13.py - with N-Z functionality.\">    def __str__(self):\r\n        \r\n        return \"\".join([self._getCipherText(c) for c in self.plaintext])\r\n\r\n    def _getCipherText(self, char):\r\n        ordinal = ord(char)\r\n        if ordinal &gt;= ord('N') and ordinal &lt;= ord('Z'):\r\n            ordinal -= 26\r\n        return chr(ordinal + 13)\r\n<\/pre>\n<p>Running our tests again, we&#8217;ll see that it passes.<\/p>\n<p>Now, let&#8217;s try our hand at lower case letters. Our new test will have mixed case, with a good representation from both before and after the <em>m-n<\/em> midpoint:<\/p>\n<pre class=\"lang:python decode:true \" title=\"ROT13Test.py - mixed case test\">    def test_encryptmixedcase(self):\r\n        rot13obj = ROT13(\"TAcos\")\r\n        self.assertEqual(\r\n            \"GNpbf\",\r\n            str(rot13obj),\r\n            \"Lowercase letters should work, too.\")<\/pre>\n<p>Running this will give us interesting results:<\/p>\n<pre class=\"lang:sh decode:true \" title=\"Executing ROT13Test.py with mixed case test.\">$ python3 rot13_test.py\r\n.\r\n..F\r\n======================================================================\r\nFAIL: test_encryptmixedcase (__main__.ROT13Test)\r\n----------------------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File \"rot13_test.py\", line 29, in test_encryptmixedcase\r\n    \"Lowercase letters should work, too.\")\r\nAssertionError: 'GNpbf' != 'GNp|\\x80'\r\n- GNpbf\r\n+ GNp|\u0080\r\n : Lowercase letters should work, too.\r\n\r\n----------------------------------------------------------------------\r\nRan 4 tests in 0.001s\r\n\r\nFAILED (failures=1)\r\n<\/pre>\n<p>It looks like we just need to add to our helper method to do the same thing with lowercase <em>n<\/em> through <em>z<\/em> characters.<\/p>\n<pre class=\"lang:python decode:true\" title=\"ROT13.py - Add lowercase checks for n and higher\">    def _getCipherText(self, char):\r\n        ordinal = ord(char)\r\n        if (ordinal &gt;= ord('N') and ordinal &lt;= ord('Z')) \\\r\n          or (ordinal &gt;= ord('n') and ordinal &lt;= ord('z')):\r\n            ordinal -= 26\r\n        return chr(ordinal + 13)\r\n<\/pre>\n<p>This will now allow our test to pass.<\/p>\n<p>What about non-alpha characters? We&#8217;ll stipulate that non-alpha characters should just pass through unaltered, so that a space in the plaintext is still a space in the ciphertext.<\/p>\n<pre class=\"lang:python decode:true \" title=\"ROT13Test.py - nonalpha test\">    def test_nonalpha(self):\r\n        rot13obj = ROT13(\"I love tacos 2!\")\r\n        self.assertEqual(\r\n            \"V ybir gnpbf 2!\",\r\n            str(rot13obj),\r\n            \"Non-alpha characters should just pass through.\")<\/pre>\n<p>Before our change, we&#8217;ll see&#8230; not what we wanted.<\/p>\n<pre class=\"lang:sh decode:true \" title=\"ROT13Test run - nonalpha chars\">$ python3 rot13_test.py\r\n.\r\n...F\r\n======================================================================\r\nFAIL: test_nonalpha (__main__.ROT13Test)\r\n----------------------------------------------------------------------\r\nTraceback (most recent call last):\r\n  File \"rot13_test.py\", line 36, in test_nonalpha\r\n    \"Non-alpha characters should just pass through.\")\r\nAssertionError: 'V ybir gnpbf 2!' != 'V-ybir-gnpbf-?.'\r\n- V ybir gnpbf 2!\r\n+ V-ybir-gnpbf-?.\r\n : Non-alpha characters should just pass through.\r\n\r\n----------------------------------------------------------------------\r\nRan 5 tests in 0.001s\r\n\r\nFAILED (failures=1)\r\n<\/pre>\n<p>We&#8217;ll put a guard around the block of code in our helper method to keep out all non-alpha characters from being manipulated.<\/p>\n<pre class=\"lang:python decode:true\" title=\"ROT13.py - With non-alpha\">    def _getCipherText(self, char):\r\n        ordinal = ord(char)\r\n        if (ordinal &gt;= ord('A') and ordinal &lt;= ord('Z')) \\\r\n           or (ordinal &gt;= ord('a') and ordinal &lt;= ord('z')):\r\n            \r\n            if (ordinal &gt;= ord('N') and ordinal &lt;= ord('Z')) \\\r\n               or (ordinal &gt;= ord('n') and ordinal &lt;= ord('z')):\r\n                ordinal -= 26\r\n            return chr(ordinal + 13)\r\n        return char\r\n<\/pre>\n<p>Now, our tests all pass. We have the behavior we want.<\/p>\n<p>This tutorial will end here, but there are many other directions you can go. You can add other methods to continue testing. You can also decide to refactor the helper class to do dictionary lookups instead of integer calculation. Once you change up the helper method, you should be able to run the tests without changing them to confirm that you have the exact same behavior as you did before you do your refactor.<\/p>\n<p>The <em>unittest<\/em> framework also provides other helpful assertions, like null condition checking and truth validation. It also provides helper methods for setting up and tearing down each test case or each class load.<\/p>\n<p>Remember, the idea of this iterative approach is to first establish your core functionality and slowly add in edge cases as you go, so you&#8217;re not overwhelmed with testing every branch of you code all at once. There should be at least one test case per branch.<\/p>\n<p>The complete code for this demo can be found on my <a href=\"https:\/\/github.com\/edjmao\/rot13_tdd_demo\">Github account<\/a>. Hope this helps you better understand how to do TDD!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There are so many ways to do testing. It all depends on the technology, the framework, and what automation tools your organization is willing to invest in. Automated testing is a great way to assert sanity in your build, especially when multiple people are touching the same segments of code. Two main approaches to testing &hellip; <a href=\"http:\/\/edspace.com\/blog\/2017\/10\/28\/explain-this-to-me-as-if-i-was-a-small-child-developer-test-driven-development\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Explain this to me as if I was a small child &#8211; Developer Test Driven Development<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":false,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[3,2],"tags":[],"class_list":["post-38","post","type-post","status-publish","format-standard","hentry","category-explain-this-to-me","category-python"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/paDvvN-C","jetpack-related-posts":[],"_links":{"self":[{"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/posts\/38","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/comments?post=38"}],"version-history":[{"count":5,"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/posts\/38\/revisions"}],"predecessor-version":[{"id":43,"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/posts\/38\/revisions\/43"}],"wp:attachment":[{"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/media?parent=38"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/categories?post=38"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/edspace.com\/blog\/wp-json\/wp\/v2\/tags?post=38"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}