{"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=\"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>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>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>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>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>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>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\">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>Two <code>AssertException()<\/code> calls were required:  The first (in the <code>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>The second (in the <code>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>There two immediately obvious problems with this:<\/p>\n\n\n\n<ol><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>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\">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>Without a <code>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>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>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>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>A pattern I use for this is to implement a <code>private<\/code> method in my test class, accepting data as input, and performing the required test.  The <code>published<\/code>, parameterless test method itself then calls this <code>private<\/code> method with the required input data:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-pascal\">\/\/ 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>The problem here is that if the first call to <code>SomeTestMethodWithData(&#039;foo&#039;) <\/code>does indeed raise the expected exception, the second call to <code>SomeTestMethodWithData(&#039;bar&#039;)<\/code> will not be reached!<\/p>\n\n\n\n<p>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>try..except<\/code> blocks.<\/p>\n\n\n\n<p>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>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\">\/\/ 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>The first thing to note is that this test is written in the past tense.<\/p>\n\n\n\n<p>Rather than indicating an expectation of something that will happen in the future (<code>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>Test.&lt;strong&gt;&lt;em&gt;Raised&lt;\/em&gt;&lt;\/strong&gt;Exception<\/code><\/p>\n\n\n\n<p>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\"><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>Since the exception test is entirely contained within a <code>try..except<\/code> that does not &#8220;leak&#8221; any exception, all of the <code>..WithData()<\/code> tests are certain to be performed.<\/p>\n\n\n\n<p>The outcome of the exception test in each call to those <code>..WithData()<\/code> methods is functionally equivalent to the 2.0 approach.<\/p>\n\n\n\n<p>First, consider the &#8220;happy path&#8221; where the expected exception is raised.  An exception is caught by the <code>try..except<\/code> and the <code>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>So far, so straightforward.<\/p>\n\n\n\n<p>If <code>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>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>Internally the <code>Test.FailedToRaiseException<\/code> method doesn&#8217;t actually record anything.  It does raise an exception of its own: an <code>ENoException<\/code> exception, to be precise, defined internally by Smoketest.<\/p>\n\n\n\n<p>This causes control to pass to the exception handler, which in turn results in <code>Test.RaisedException()<\/code> being performed.  When this test finds that the exception raised was an <code>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>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><code>Test.RaisedException<\/code> and <code>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>Where there is no need to perform multiple tests for exceptions, then <code>Test.&lt;strong&gt;Raises&lt;\/strong&gt;Exception<\/code> can be used, avoiding the need for any <code>try..except<\/code> boilerplate.<\/p>\n\n\n\n<p>But where <em>multiple<\/em> exception tests are required\/helpful, <code>Test.FailedToRaiseException<\/code> and <code>Test.&lt;strong&gt;Raised&lt;\/strong&gt;Exception<\/code> can be used with a <code>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>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 decoding=\"async\" loading=\"lazy\" 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=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>Merry Christmas! \ud83d\ude42<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"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_post_was_ever_published":false,"jetpack_publicize_message":"Recent updates to Smoketest 2.x, post 1 of 'n'.  In this episode: Testing for expected exceptions.","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4,321,348,48],"tags":[51],"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":[{"id":2185,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2185\/","url_meta":{"origin":3045,"position":0},"title":"Smoketest &#8211; Performance Case Visualisations","date":"22 Nov 2013","format":false,"excerpt":"This post is a peek behind the curtain of the next major update to Smoketest which I hope to have completed shortly: Performance Case visualisations. Smoketest has always had two types of test case that you could implement by deriving from two distinct base classes: TTestCase is the base class\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-22-at-09.57.52.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2144,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2144\/","url_meta":{"origin":3045,"position":1},"title":"Smoketest 1.0 &#8211; Release and Be Damned!","date":"14 Nov 2013","format":false,"excerpt":"As I have been promising for some time (quite literally 5 years (!), I am ashamed to admit) I am finally unclenching and releasing the Smoketest framework into the wild, ready or not. The code is published and will continue to be updated in a github repository. Documentation is still\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-14-at-19.51.57-.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2095,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2095\/","url_meta":{"origin":3045,"position":2},"title":"Extending Smoketest (Part 1) &#8211; An Inspector Calls","date":"05 Nov 2013","format":false,"excerpt":"In the soon to be released Smoketest framework it is sometimes useful to create new test types to supplement the tests built-in to the framework. In this and the next post I will walk through the process of implementing and registering a custom test with the Smoketest framework. In a\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":284,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/284\/","url_meta":{"origin":3045,"position":3},"title":"This Week&#8217;s Poll, Delphi at Tech Ed and 2009 Ship Date","date":"01 Sep 2008","format":false,"excerpt":"Since I'm preparing a series of posts about (and eventual publication of) Smoketest, my own testing framework, I thought I'd test the water with this weeks poll and see what - if anything - people are already using as far as unit testing goes. Also I thought I'd briefly mention\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2956,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2956\/","url_meta":{"origin":3045,"position":4},"title":"Introducing: Smoketest 2.0","date":"24 Sep 2019","format":false,"excerpt":"Meet the new Smoketest. Nothing like the old Smoketest. A new, ultra-lightweight unit testing framework with xUnit 2.x result output (and the ability to support other output formats as needed).","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/vape_2.jpg?fit=1200%2C307&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2164,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2164\/","url_meta":{"origin":3045,"position":5},"title":"Smoketest &#8211; Some Differences With DUnit","date":"19 Nov 2013","format":false,"excerpt":"Writing tests in Smoketest is intended to enable a test developer to write tests in a way that describe themselves, without requiring the test developer to add this \"narrative\" themselves. To see this in action, I thought I would compare some simple DUnit tests with the equivalent using the Smoketest\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3045"}],"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}]}}