{"id":501,"date":"2016-02-28T10:28:52","date_gmt":"2016-02-28T09:28:52","guid":{"rendered":"http:\/\/joost.vunderink.net\/blog\/?p=501"},"modified":"2016-03-06T17:41:04","modified_gmt":"2016-03-06T16:41:04","slug":"automated-software-testing-part-4-combining-similar-tests","status":"publish","type":"post","link":"https:\/\/joost.vunderink.net\/blog\/2016\/02\/28\/automated-software-testing-part-4-combining-similar-tests\/","title":{"rendered":"Automated software testing part 4: combining similar tests"},"content":{"rendered":"<p>Previous: <a href=\"http:\/\/joost.vunderink.net\/blog\/2016\/02\/21\/automated-software-testing-part-3-writing-tests-and-refactoring\/\">part 3<\/a>\u00a0&#8211; Next: <a href=\"http:\/\/joost.vunderink.net\/blog\/2016\/03\/06\/automated-software-testing-part-5-spying\/\">part 5<\/a><\/p>\n<p>You can also find the code below in the <span class=\"lang:default decode:true crayon-inline \">src\/unit-test-2<\/span>\u00a0\u00a0 dir of my <a href=\"https:\/\/github.com\/joostvunderink\/jvblog-node-samples\">blog code repository<\/a>.<\/p>\n<p>Today, you have been asked by your uncle to help him set up\u00a0the\u00a0new MegaBanana Slide in his Fun Park. The MegaBanana Slide is meant for children, but not too small children. Certainly not for adults. And you must take off your shoes before using it. Oh, and of course you are not allowed to go together with your friend &#8211; each child has to wait for its turn!<\/p>\n<div id=\"attachment_504\" style=\"width: 214px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/02\/giant-slide-in-maxima-philippines-philippines1152_12985045119-tpfil02aw-8862.jpg\" rel=\"attachment wp-att-504\"><img loading=\"lazy\" decoding=\"async\" aria-describedby=\"caption-attachment-504\" class=\"size-medium wp-image-504\" src=\"http:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/02\/giant-slide-in-maxima-philippines-philippines1152_12985045119-tpfil02aw-8862-204x300.jpg\" alt=\"A child going down a pretty big slide\" width=\"204\" height=\"300\" srcset=\"https:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/02\/giant-slide-in-maxima-philippines-philippines1152_12985045119-tpfil02aw-8862-204x300.jpg 204w, https:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/02\/giant-slide-in-maxima-philippines-philippines1152_12985045119-tpfil02aw-8862-768x1128.jpg 768w, https:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/02\/giant-slide-in-maxima-philippines-philippines1152_12985045119-tpfil02aw-8862-697x1024.jpg 697w, https:\/\/joost.vunderink.net\/blog\/wp-content\/uploads\/2016\/02\/giant-slide-in-maxima-philippines-philippines1152_12985045119-tpfil02aw-8862.jpg 784w\" sizes=\"(max-width: 204px) 100vw, 204px\" \/><\/a><p id=\"caption-attachment-504\" class=\"wp-caption-text\">The MegaBanana Slide!<\/p><\/div>\n<p>To prevent any accidents, the MegaBanana Slide has a sophisticated Child\u00a0Measurement System, where any child\u00a0who stands near the start of the Slide is measured. The results of this measurement are then fed into the Permission Granting System, which then opens the gate &#8211; or not.<\/p>\n<p>Your uncle knows that you are a star programmer, so he has asked you to program the PGS according to his specifications:<\/p>\n<ul>\n<li>only 1 child at a time<\/li>\n<li>no shoes<\/li>\n<li>no children under 90 cm (that&#8217;s about 3 feet)<\/li>\n<li>absolutely nobody who weighs more than 125 kg (275 lbs)!<\/li>\n<\/ul>\n<p>The PGS wants an object with a <span class=\"lang:default decode:true crayon-inline \">canUse<\/span>\u00a0 field and a <span class=\"lang:default decode:true crayon-inline \">reason<\/span>\u00a0 field. If <span class=\"lang:default decode:true crayon-inline \">canUse<\/span>\u00a0 is <span class=\"lang:default decode:true crayon-inline \">false<\/span>\u00a0, the reason is shown to the poor child who is not allowed to go down the slide.<\/p>\n<p>Writing this code takes you all of 3 minutes and 42 seconds.<\/p>\n<pre class=\"lang:js decode:true \" title=\"slide.js\">function canUseSlide(person) {\r\n  if (Array.isArray(person) &amp;&amp; person.length &gt; 1) {\r\n    \/\/ Sneaky! Trying to go with more than 1 person together!\r\n    return {\r\n      canUse: false,\r\n      reason: 'Only 1 person at a time!',\r\n    };\r\n  }\r\n\r\n  if (person.weight &gt; 125) {\r\n    \/\/ We don't want the slide to break!\r\n    return {\r\n      canUse: false,\r\n      reason: 'This slide is for children only.',\r\n    };\r\n  }\r\n\r\n  if (person.height &lt; 90) {\r\n    \/\/ Small children are not allowed on this dangerous slide.\r\n    return {\r\n      canUse: false,\r\n      reason: 'You are not tall enough yet.',\r\n    };\r\n  }\r\n\r\n  if (person.isWearing('shoes')) {\r\n    \/\/ You're only allowed to go down the slide barefoot.\r\n    return {\r\n      canUse: false,\r\n      reason: 'You must take off your shoes first!',\r\n    };\r\n  }\r\n\r\n  return {\r\n    canUse: true,\r\n    reason: ''\r\n  };\r\n}<\/pre>\n<p>Easy peasy. But you know that your uncle is a nitpick, so you decide to write some unit tests (even though he has no clue what that term even means) to verify that your code does the right thing.<\/p>\n<pre class=\"lang:js decode:true\" title=\"slide.spec.js first version\">require('should');\r\nvar slide = require('.\/slide');\r\n\r\ndescribe('canUseSlide', function() {\r\n  it('should verify if you can use the slide: child the slide is intended for', function() {\r\n    var result = slide.canUseSlide({\r\n      height: 105,\r\n      weight: 15,\r\n      isWearing: function() { return false; }\r\n    });\r\n    result.should.eql({\r\n      canUse: true,\r\n      reason: '',\r\n    });\r\n  });\r\n  \r\n  it('should verify if you can use the slide: large adult instead of child', function() {\r\n    var result = slide.canUseSlide({\r\n      height: 193,\r\n      weight: 151,\r\n      isWearing: function() { return false; }\r\n    });\r\n    result.should.eql({\r\n      canUse: false,\r\n      reason: 'This slide is for children only.',\r\n    });\r\n  });\r\n  \r\n  it('should verify if you can use the slide: too small child', function() {\r\n    var result = slide.canUseSlide({\r\n      height: 83,\r\n      weight: 11,\r\n      isWearing: function() { return false; }\r\n    });\r\n    result.should.eql({\r\n      canUse: false,\r\n      reason: 'You are not tall enough yet.',\r\n    });\r\n  });\r\n  \r\n  it('should verify if you can use the slide: wearing shoes', function() {\r\n    var result = slide.canUseSlide({\r\n      height: 105,\r\n      weight: 15,\r\n      isWearing: function() { return true; }\r\n    });\r\n    result.should.eql({\r\n      canUse: false,\r\n      reason: 'You must take off your shoes first!',\r\n    });\r\n  });\r\n  \r\n  it('should verify if you can use the slide: 2 children at the same time', function() {\r\n    var result = slide.canUseSlide([{\r\n      height: 105,\r\n      weight: 15,\r\n      isWearing: function() { return false; }\r\n    }, {\r\n      height: 115,\r\n      weight: 17,\r\n      isWearing: function() { return false; }\r\n    }]);\r\n    result.should.eql({\r\n      canUse: false,\r\n      reason: 'Only 1 person at a time!',\r\n    });\r\n  });\r\n});<\/pre>\n<p>5 possible situations, so 5 tests. But this test code hurts your eyes. So much copy-pasting going on here, agh. You can do better.<\/p>\n<p>Let&#8217;s make an array of test situations. We&#8217;ll give each one a name (to put in the <span class=\"lang:default decode:true crayon-inline \">it<\/span>\u00a0 description), the input of <span class=\"lang:default decode:true crayon-inline \">canUseSlide<\/span>\u00a0, and the expected output.<\/p>\n<pre class=\"lang:js decode:true \" title=\"slide.spec.js version 2\">require('should');\r\nvar slide = require('.\/slide');\r\n\r\ndescribe('canUseSlide 2', function() {\r\n  var testCases = [\r\n    {\r\n      name: 'child the slide is intended for',\r\n      person: {\r\n        height: 105,\r\n        weight: 15,\r\n        isWearing: function() { return false; }\r\n      },\r\n      expectedResult: {\r\n        canUse: true,\r\n        reason: '',\r\n      }\r\n    },\r\n    {\r\n      name: 'large adult instead of child',\r\n      person: {\r\n        height: 193,\r\n        weight: 151,\r\n        isWearing: function() { return false; }\r\n      },\r\n      expectedResult: {\r\n        canUse: false,\r\n        reason: 'This slide is for children only.',\r\n      }\r\n    },\r\n    {\r\n      name: 'too small child',\r\n      person: {\r\n        height: 83,\r\n        weight: 11,\r\n        isWearing: function() { return false; }\r\n      },\r\n      expectedResult: {\r\n        canUse: false,\r\n        reason: 'You are not tall enough yet.',\r\n      }\r\n    },\r\n    {\r\n      name: 'wearing shoes',\r\n      person: {\r\n        height: 105,\r\n        weight: 15,\r\n        isWearing: function() { return true; }\r\n      },\r\n      expectedResult: {\r\n        canUse: false,\r\n        reason: 'You must take off your shoes first!',\r\n      }\r\n    },\r\n    {\r\n      name: '2 children at the same time',\r\n      person: [{\r\n        height: 105,\r\n        weight: 15,\r\n        isWearing: function() { return false; }\r\n      }, {\r\n        height: 115,\r\n        weight: 17,\r\n        isWearing: function() { return false; }\r\n      }],\r\n      expectedResult: {\r\n        canUse: false,\r\n        reason: 'Only 1 person at a time!',\r\n      }\r\n    },\r\n  ];\r\n\r\n  testCases.forEach(function(tc) {\r\n    it('should verify if you can use the slide: ' + tc.name, function() {\r\n      var result = slide.canUseSlide(tc.person);\r\n      result.should.eql(tc.expectedResult);\r\n    });\r\n  });\r\n});<\/pre>\n<p>Excellent. You have separated the data from the test execution. It&#8217;s now easy and clear how to add another test. It&#8217;s even possible to put the test data in a different file.<\/p>\n<p>However, your test file just went up from 68 lines to 78 lines. And there is still a lot of duplication.<\/p>\n<p>In the test data, the expected result can be reduced to just the reason. During test execution, you\u00a0can then create the expected result object from the reason (after all, <span class=\"lang:default decode:true crayon-inline \">canUse<\/span>\u00a0 is <span class=\"lang:default decode:true crayon-inline \">true<\/span>\u00a0 if the reason is empty, and <span class=\"lang:default decode:true crayon-inline \">false<\/span>\u00a0 otherwise).<\/p>\n<p>Also, you decide to make\u00a0a helper function to create the person objects.<\/p>\n<p>This is the final version of your test file:<\/p>\n<pre class=\"lang:js decode:true \" title=\"slide.spec.js version 3\">require('should');\r\nvar slide = require('.\/slide');\r\n\r\ndescribe('canUseSlide 3', function() {\r\n  function createPerson(height, weight, isWearingShoes) {\r\n    return {\r\n      height: height,\r\n      weight: weight,\r\n      isWearing: function() { return isWearingShoes; }\r\n    }\r\n  };\r\n\r\n  var testCases = [\r\n    {\r\n      name: 'child the slide is intended for',\r\n      person: createPerson(105, 15, false),\r\n      reason: '',\r\n    },\r\n    {\r\n      name: 'large adult instead of child',\r\n      person: createPerson(193, 151, false),\r\n      reason: 'This slide is for children only.',\r\n    },\r\n    {\r\n      name: 'too small child',\r\n      person: createPerson(83, 11, false),\r\n      reason: 'You are not tall enough yet.',\r\n    },\r\n    {\r\n      name: 'wearing shoes',\r\n      person: createPerson(105, 15, true),\r\n      reason: 'You must take off your shoes first!',\r\n    },\r\n    {\r\n      name: '2 children at the same time',\r\n      person: [createPerson(105, 15, false), createPerson(115, 17, false)],\r\n      reason: 'Only 1 person at a time!',\r\n    },\r\n  ];\r\n\r\n  testCases.forEach(function(tc) {\r\n    it('should verify if you can use the slide: ' + tc.name, function() {\r\n      var result = slide.canUseSlide(tc.person);\r\n      var expectedResult = {\r\n        canUse: tc.reason.length === 0,\r\n        reason: tc.reason,\r\n      }\r\n      result.should.eql(expectedResult);\r\n    });\r\n  });\r\n});<\/pre>\n<p>51 lines of lean and mean test code. The test data is very clear and compact. Not bad!<\/p>\n<p>Your uncle installs your code on the PGS, and soon after, the MegaBanana Slide is fully operational. Everyone is happy!<\/p>\n<p>Except that you still have this slight nagging feeling that it should be possible somehow to refactor the <span class=\"lang:default decode:true crayon-inline \">person<\/span>\u00a0 field of the test data so you can move the <span class=\"lang:default decode:true crayon-inline \">createPerson<\/span>\u00a0 calls from the test data to the test execution. But you can&#8217;t think of an elegant way&#8230;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Previous: part 3\u00a0&#8211; Next: part 5 You can also find the code below in the src\/unit-test-2\u00a0\u00a0 dir of my blog code repository. Today, you have been asked by your uncle to help him set up\u00a0the\u00a0new MegaBanana Slide in his Fun &hellip; <a href=\"https:\/\/joost.vunderink.net\/blog\/2016\/02\/28\/automated-software-testing-part-4-combining-similar-tests\/\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[10,272],"tags":[276,279,281,280,278,282,283,235,274],"class_list":["post-501","post","type-post","status-publish","format-standard","hentry","category-software-development","category-testing-software-development","tag-automated-testing","tag-banana","tag-megabanana","tag-slide","tag-software-testing","tag-test-data","tag-test-execution","tag-unit-test","tag-unit-tests"],"_links":{"self":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/501"}],"collection":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/comments?post=501"}],"version-history":[{"count":4,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/501\/revisions"}],"predecessor-version":[{"id":512,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/posts\/501\/revisions\/512"}],"wp:attachment":[{"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/media?parent=501"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/categories?post=501"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/joost.vunderink.net\/blog\/wp-json\/wp\/v2\/tags?post=501"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}