{"id":3045,"date":"2020-12-19T09:06:12","date_gmt":"2020-12-18T21:06:12","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=3045"},"modified":"2020-12-19T09:06:17","modified_gmt":"2020-12-18T21:06:17","slug":"big-updates-to-smoketest-2-x","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/3045\/","title":{"rendered":"Big Updates to Smoketest 2.x"},"content":{"rendered":"<span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span>\n<p class=\"wp-block-paragraph\">A little over a year ago, I released <a href=\"https:\/\/github.com\/deltics\/deltics.smoketest\">Smoketest 2.0<\/a>, a complete re-write of the Smoketest unit testing framework first implemented some years ago.  Other things then consumed my time, but in the past couple of months, Smoketest has rapidly progressed through no less than 4 (four!) fairly significant feature updates as I have extended it to support my testing needs in other projects.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Today I released v2.4.0, and though it was about time <em>(read: long overdue!)<\/em>, I shared some of the new features.  As you might have gathered from my recent post on an exception handling bug in the Delphi 10.4.1 x86 compiler, exceptions have been a recent focus, so I&#8217;ll start with the changes in that area.<\/p>\n\n\n\n<!--more-->\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p class=\"wp-block-paragraph\">The 2.1.0 release was a big one, incorporating a major overhaul in the internals that greatly simplified some things; in particular, the support required for self-test extensions (for the framework to test itself) became easier to reason about.  This was not something that affects the framework&#8217;s consumers but made my life easier and any contributor.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For <em>users<\/em> of the framework, there was an even bigger impact due to the introduction of a fluent Api for expressing tests.  I&#8217;ll talk more about that in another post.  For now, I&#8217;ll focus on the support in this fluent Api for an entirely new approach to testing for exceptions.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To appreciate the significance of the test writing experience changes, it may help to recap the mechanism that it replaced.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Previously to test for exceptions (in Smoketest 2.0), you would write something similar to the following:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-pascal\" data-line=\"\">procedure TSomeTestClass.SomeTestMethod;\nbegin\n  try\n    SomeOperationUnderTest;\n\n    AssertException( ESomeExceptionClass, &#039;The message text on the expected exception.&#039;);\n  except\n    AssertException( ESomeExceptionClass, &#039;The message text on the expected exception.&#039;);\n  end;\nend;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Two <code class=\"\" data-line=\"\">AssertException()<\/code> calls were required:  The first (in the <code class=\"\" data-line=\"\">try<\/code> block) would cause the test to fail since, for this statement to have been reached, the expected exception could not have been raised.  The exception specification was used to create the test failure report.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second (in the <code class=\"\" data-line=\"\">except<\/code> block) would test the exception in scope against the specification provided in the test and, if the current exception object matched that specification, the test would pass; otherwise, it would fail.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">There two immediately obvious problems with this:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>The duplication is a clear violation of the <a href=\"https:\/\/en.wikipedia.org\/wiki\/Don%27t_repeat_yourself\">DRY principle<\/a>.<\/li><li>Compounding that problem, the duplicated code <em>behaved differently in the two contexts in which it was used<\/em>.  The first was expressly recording a failure, whereas the second was performing a test that could pass or fail.<\/li><\/ol>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Eliminating <s>Unnecessary<\/s> Duplication <\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">In 2.1.0, the need for the try..except boilerplate is eliminated, and the test is reduced to a single statement expressing the expectation that the test will raise an exception:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-pascal\" data-line=\"\">procedure TSomeTestClass.SomeTestMethod;\nbegin\n  Test.RaisesException( ESomeExceptionClass, &#039;The message text on the expected exception.&#039; );\n\n  SomeOperationUnderTest;\nend;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Without a <code class=\"\" data-line=\"\">try..except<\/code>, any exception raised by the method under test will be caught by the test framework, which then inspects the exception to verify that it matches the expected specification.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is a <em>massive<\/em> improvement in the test writing experience but has one major shortcoming: <em>there can be only one such test in a given test method<\/em>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">In many cases, this is not a significant problem, but it is HUGE in those cases where it is a problem.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Once Is Not Always Enough<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Consider a scenario where some function is being tested to ensure that it raises an expected exception when presented with various invalid inputs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A pattern I use for this is to implement a <code class=\"\" data-line=\"\">private<\/code> method in my test class, accepting data as input, and performing the required test.  The <code class=\"\" data-line=\"\">published<\/code>, parameterless test method itself then calls this <code class=\"\" data-line=\"\">private<\/code> method with the required input data:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-pascal\" data-line=\"\">\/\/ private\nprocedure TSomeTestClass.SomeTestMethodWithData(const aInput: String);\nbegin\n  Test.RaisesException( ESomeExceptionClass, &#039;The message text on the expected exception.&#039; );\n\n  SomeOperationUnderTest(aInput);\nend;\n\n\/\/ published\nprocedure TSomeTestClass.SomeTestMethod;\nbegin\n  SomeTestMethodWithData(&#039;foo&#039;);\n  SomeTestMethodWithData(&#039;bar&#039;);\nend;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The problem here is that if the first call to <code class=\"\" data-line=\"\">SomeTestMethodWithData(&#039;foo&#039;) <\/code>does indeed raise the expected exception, the second call to <code class=\"\" data-line=\"\">SomeTestMethodWithData(&#039;bar&#039;)<\/code> will not be reached!<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Ironically this was not a problem with the old mechanism, which relied on a try..except block to perform the exception tests within the test method&#8217;s scope.  Multiple tests could be performed in a series of <code class=\"\" data-line=\"\">try..except<\/code> blocks.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">What was needed was some similar mechanism but which avoided the code duplication problem.  This is what has been developed for 2.4.0.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Let&#8217;s first look at what this looks like in use, then talk about how it works, using the &#8216;test with data&#8217; scenario above to illustrate:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-pascal\" data-line=\"\">\/\/ private\nprocedure TSomeTestClass.SomeTestMethodWithData(const aInput: String);\nbegin\n  try\n    SomeOperationUnderTest(aInput);\n\n    Test.FailedToRaiseException;\n\n  except\n    Test.RaisedException( ESomeExceptionClass, &#039;The message text on the expected exception.&#039; );\n  end;\nend;\n\n\/\/ published\nprocedure TSomeTestClass.SomeTestMethod;\nbegin\n  SomeTestMethodWithData(&#039;foo&#039;);\n  SomeTestMethodWithData(&#039;bar&#039;);\nend;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The first thing to note is that this test is written in the past tense.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Rather than indicating an expectation of something that will happen in the future (<code class=\"\" data-line=\"\">Test.&lt;strong&gt;&lt;em&gt;Raises&lt;\/em&gt;&lt;\/strong&gt;Exception<\/code>), this test expresses something that is expected to have already happened: <code class=\"\" data-line=\"\">Test.&lt;strong&gt;&lt;em&gt;Raised&lt;\/em&gt;&lt;\/strong&gt;Exception<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is important because it captures the fact that the two tests are quite different in intent.  This allows internal validation that the correct test has been expressed.<\/p>\n\n\n\n<p class=\"has-background has-luminous-vivid-amber-background-color wp-block-paragraph\"><em>NB. No such validation is implemented as yet, but is on the TODO list &#8211; &#8220;perfect&#8221; is the enemy of &#8220;done.&#8221;<\/em> \ud83d\ude42 <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Since the exception test is entirely contained within a <code class=\"\" data-line=\"\">try..except<\/code> that does not &#8220;leak&#8221; any exception, all of the <code class=\"\" data-line=\"\">..WithData()<\/code> tests are certain to be performed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The outcome of the exception test in each call to those <code class=\"\" data-line=\"\">..WithData()<\/code> methods is functionally equivalent to the 2.0 approach.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">First, consider the &#8220;happy path&#8221; where the expected exception is raised.  An exception is caught by the <code class=\"\" data-line=\"\">try..except<\/code> and the <code class=\"\" data-line=\"\">Test.RaisedException()<\/code> call determines whether the raised exception matches the expected specification (the test passes) or not (the test fails).<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So far, so straightforward.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If <code class=\"\" data-line=\"\">Test.FailedToRaiseException<\/code> is called, then this signifies that some expected exception was <strong><em>not<\/em><\/strong> raised, and so a test failure must be recorded.  But the framework does not know the particular exception that was expected to have been raised.  Simply recording &#8220;<em>Some unspecified thing that was expected to have happened didn&#8217;t happen<\/em>&#8221; would not be very helpful!  What to do!?<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Well, the required information <em>is<\/em> available: <em>in the test performed in response to an actual exception<\/em>.  Rather than burden the test author with duplicating the data, the framework goes and gets it for itself.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Internally the <code class=\"\" data-line=\"\">Test.FailedToRaiseException<\/code> method doesn&#8217;t actually record anything.  It does raise an exception of its own: an <code class=\"\" data-line=\"\">ENoException<\/code> exception, to be precise, defined internally by Smoketest.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This causes control to pass to the exception handler, which in turn results in <code class=\"\" data-line=\"\">Test.RaisedException()<\/code> being performed.  When this test finds that the exception raised was an <code class=\"\" data-line=\"\">ENoException<\/code>, it <em>immediately<\/em> knows that the test has failed and records that fact using the exception specification it was provided with.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is functionally identical to the old 2.0 mechanism but with clearer intent and without any need to duplicate details of the exception specification involved in the test!<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">Hammers and Screwdrivers<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\"><code class=\"\" data-line=\"\">Test.RaisedException<\/code> and <code class=\"\" data-line=\"\">Test.RaisesException<\/code> both exist and are supported by Smoketest 2.4.x.  They are not <em>alternatives<\/em> to each other but satisfy different needs.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Where there is no need to perform multiple tests for exceptions, then <code class=\"\" data-line=\"\">Test.&lt;strong&gt;Raises&lt;\/strong&gt;Exception<\/code> can be used, avoiding the need for any <code class=\"\" data-line=\"\">try..except<\/code> boilerplate.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">But where <em>multiple<\/em> exception tests are required\/helpful, <code class=\"\" data-line=\"\">Test.FailedToRaiseException<\/code> and <code class=\"\" data-line=\"\">Test.&lt;strong&gt;Raised&lt;\/strong&gt;Exception<\/code> can be used with a <code class=\"\" data-line=\"\">try..except<\/code>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Footnote<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In case you were wondering, this is the project that has consumed a lot of my time since September last year:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"640\" height=\"427\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Pop-in-Portraits-Albany-Merry-Pop-ins-Family-Portrait-07-Nov-2020-256341-Shareable.jpg?resize=640%2C427&#038;ssl=1\" alt=\"\" class=\"wp-image-3046\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Pop-in-Portraits-Albany-Merry-Pop-ins-Family-Portrait-07-Nov-2020-256341-Shareable.jpg?resize=1024%2C683&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Pop-in-Portraits-Albany-Merry-Pop-ins-Family-Portrait-07-Nov-2020-256341-Shareable.jpg?resize=300%2C200&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Pop-in-Portraits-Albany-Merry-Pop-ins-Family-Portrait-07-Nov-2020-256341-Shareable.jpg?resize=768%2C512&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Pop-in-Portraits-Albany-Merry-Pop-ins-Family-Portrait-07-Nov-2020-256341-Shareable.jpg?resize=380%2C253&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Pop-in-Portraits-Albany-Merry-Pop-ins-Family-Portrait-07-Nov-2020-256341-Shareable.jpg?w=1200&amp;ssl=1 1200w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">Merry Christmas! \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"span-reading-time rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\"> 5<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span>A little over a year ago, I released Smoketest 2.0, a complete re-write of the Smoketest unit testing framework first implemented some years ago. Other things then consumed my time, but in the past couple of months, Smoketest has rapidly progressed through no less than 4 (four!) fairly significant feature updates as I have extended [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":2957,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_feature_clip_id":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"Recent updates to Smoketest 2.x, post 1 of 'n'.  In this episode: Testing for expected exceptions.","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2},"jetpack_post_was_ever_published":false},"categories":[4,321,348,48],"tags":[51],"class_list":["post-3045","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-delphi","category-devops","category-smoketest","category-testing","tag-smoketest"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/vape_2.jpg?fit=1229%2C314&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-N7","jetpack_sharing_enabled":true,"jetpack-related-posts":[],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3045","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=3045"}],"version-history":[{"count":7,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3045\/revisions"}],"predecessor-version":[{"id":3053,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3045\/revisions\/3053"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media\/2957"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=3045"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=3045"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=3045"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}