{"id":2956,"date":"2019-09-24T09:18:28","date_gmt":"2019-09-23T21:18:28","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=2956"},"modified":"2019-09-24T09:18:36","modified_gmt":"2019-09-23T21:18:36","slug":"introducing-smoketest-2-0","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/2956\/","title":{"rendered":"Introducing: Smoketest 2.0"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">6<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span>\n<p>Many years ago I created <a href=\"https:\/\/www.deltics.co.nz\/blog\/posts\/tag\/smoketest\">Smoketest<\/a> and had a lot of fun doing so.  But recently I ran into a problem when trying to use it for creating tests for my code that I am currently preparing for packaging.  The problem was that it was just too darned &#8216;heavy&#8217;.<\/p>\n\n\n\n<p>In particular, it made extensive use of the very code I was trying to test with it.  In the past I admit I had just lived with this, developing all sorts of coping strategies.  But in trying to automate my CI\/CD pipelines these headaches grew to migraine levels and enough was enough.<\/p>\n\n\n\n<p class=\"has-drop-cap\">Initially I set about creating a much more basic and crude set of tests for my lowest level libraries, thinking that if I could just get test coverage in place at that level then once I had all the dependencies that <strong>Smoketest<\/strong> needed I could then start using that for those higher-level, more complex testing needs.<\/p>\n\n\n\n<p>Because of the code it was needed to test, this basic testing approach needed to be dependency-free, relying solely on the <strong>Delphi RTL<\/strong> and being portable across <em>all<\/em> <strong>Delphi<\/strong> versions from <strong>7 to 10.3<\/strong>.  These constraints actually made the job easier as it limited options and kept things very basic indeed.<\/p>\n\n\n\n<p>In fact, I had something that did this job in a little over half a day, even with support for outputting results in <strong>xUnit 2<\/strong> format!  I then realised that what I had come up with needed only a little more work to become the basis for what I have decided will be <strong><a href=\"https:\/\/github.com\/deltics\/smoketest\">Smoketest 2.0<\/a><\/strong>,  <a href=\"https:\/\/github.com\/deltics\/smoketest\">available now on GitHub<\/a>.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p class=\"has-drop-cap\">Weighing in at a smidgen over 40KB of code, <strong>Smoketest 2.0<\/strong> is an absolute featherweight compared to the original at <em><strong>450KB+<\/strong><\/em> of code.  In fact, the original is actually embarrassingly bloated, topping the scales at a whopping <strong>2-3 MEGABytes<\/strong> with all the resources for the built-in UI runner taken into account!  I know that this isn&#8217;t necessarily important or even significant, but it&#8217;s an interesting data-point none-the-less.<\/p>\n\n\n\n<p>The first thing to get out of the way is that <strong>Smoketest 2.0<\/strong> has absolutely nothing in common with the previous <strong>Smoketest<\/strong> framework other than the name.  Any existing <strong>Smoketest<\/strong> tests you have will need to be re-purposed.  I have the same problem.<\/p>\n\n\n\n<p class=\"has-background has-very-light-gray-background-color\">Being so completely different, I wrestled with whether to keep the name and initially had a new name in mind.  Eventually however, I concluded that this was the genesis of a total <strong>Smoketest<\/strong> replacement, and hence a completely new version of <strong>Smoketest<\/strong> rather than something actually separate and new.<\/p>\n\n\n\n<p>Everything you really need to know is in the <strong><a href=\"https:\/\/github.com\/deltics\/smoketest\/blob\/master\/README.md\">README.md<\/a><\/strong> for the project on GitHub, though the <strong>Getting Started<\/strong> section is a bit of a tease.  It references <strong>duget<\/strong> which has yet to be revealed to the world (coming soon, I promise).<\/p>\n\n\n\n<p>For now, these instructions don&#8217;t make much sense but fortunately because of the dependency-free nature of <strong>Smoketest 2.0<\/strong>, using it is actually super-easy.  Barely and inconvenience [<em>You really should stop using that or Ryan George will on your back! &#8211; Ed<\/em>]<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The REAL &#8216;Getting Started&#8217; Guide<\/h3>\n\n\n\n<p>Clone the repo somewhere convenient and add the <code>src<\/code> folder to the path of any project (or globally).  I use scope resolving declarations to elevate <strong>types<\/strong> and <strong>consts<\/strong> etc throughout the framework where needed, so:<\/p>\n\n\n\n<p>To write a test suite you need only add <code>Deltics.Smoketest<\/code> to your <code>program<\/code> <code>uses<\/code> clause and call the appropriate methods on the <code>TestRun<\/code> object that this introduces.  At a minimum this means making at least one call to <code>TestRun.Test<\/code>, passing a test class (or array of test classes) to be executed.<\/p>\n\n\n\n<p>To implement a test class that you can pass to the <code>TestRun<\/code> you derive a class from the <code>TTest<\/code> class.  Again, this needs only <code>Deltics.Smoketest<\/code> present in your <code>uses<\/code> clause.  The tests performed by a test class will be any published, parameter-less methods.<\/p>\n\n\n\n<p>To write tests in your test methods, use the <code>Assert<\/code> method inherited from the <code>TTest<\/code> class.  <code>AssertException<\/code> and <code>AssertBaseException<\/code> are also provided for super-advanced exception handling tests.<\/p>\n\n\n\n<p class=\"has-background has-light-green-cyan-background-color\">Examples for all three of these are provided in the <strong><a href=\"https:\/\/github.com\/deltics\/smoketest\/blob\/master\/README.md\">README<\/a><\/strong>.<\/p>\n\n\n\n<p>Output of results to file is handled automatically by <strong>Smoketest<\/strong> upon completion of the test run, if the appropriate format option is provided on the command line.  Different output formats are supported by implementations of a <strong>ResultsWriter<\/strong> class.  Each class is registered in the Smoketest framework with a corresponding format identifier.  To output in a particular format you simply specify a command line option to the test run where the option has the name of the format and has a value which identifies a filename to hold the results in that format.<\/p>\n\n\n\n<p>At time of writing only one format is supported: XUnit 2.x.  The format identifier for this is <code>xunit2<\/code> so to emit test results in this format you would run your test suite with a command line similar to:<\/p>\n\n\n\n<p><code>mytests -xunit2:test-results.xml<\/code><\/p>\n\n\n\n<p>The <strong>xUnit2<\/strong> results writer supports appending results.  If the filename specified already exists then the writer will assume that you wish to add the results of this run to those already in the file and will add them as a new <code>&lt;assembly&gt;<\/code>.  Whether this makes sense may vary from test suite to test suite but if you want a separate file for each test run results then you will need to contrive some way of specifying unique filenames to your test runs.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p class=\"has-drop-cap\">Below is an example of a test suite taken from one of the packages I am currently, uh, packaging.  It simply packages up the include file that I use across all my projects for detecting and adapting to various Delphi versions.    This test suite is a little unusual therefore in that the tests are testing <code>$DEFINE<\/code> symbols and the only &#8216;code&#8217; under test is the include file.<\/p>\n\n\n\n<p>First, the test suite:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\n{$apptype CONSOLE}\n\n{$i deltics.inc}\n\nprogram Test;\n\n  uses\n    Deltics.Smoketest,\n    TestConsts in &#039;TestConsts.pas&#039;,\n    TestCore in &#039;TestCore.pas&#039;,\n    TestVersionDefines in &#039;TestVersionDefines.pas&#039;;\n\nbegin\n  TestRun.Environment := &#039;Delphi &#039; + Uppercase(DELPHI_VERSION);\n  try\n    TestRun.Test(TCoreFunctionality);\n    TestRun.Test(TVersionDefines, UpperCase(DELPHI_VERSION));\n\n  except\n    on e: Exception do\n      WriteLn(&#039;ERROR: &#039; + e.Message);\n  end;\nend.\n<\/pre><\/div>\n\n\n<p>The <strong>Environment<\/strong> property which is set is used in the test result output.  A <strong> Name<\/strong> property can also be set.  Both are optional and default values will be used if not provided.  <strong>Name<\/strong> will default to the executable filename and <strong>Environment<\/strong> will default to &#8216;Test&#8217;.<\/p>\n\n\n\n<p>These values are fixed once the first test has been performed in a run.<\/p>\n\n\n\n<p>Then the suite runs two test classes.  The second is run with a <strong>NamePrefix<\/strong> parameter.  This is a prefix that is prefixed to every test name produced by that test.  This is for discriminating between results where the same names are used for different runs of the same tests.  In this case, this test suite will be executed for multiple Delphi versions (separate compilations and executions), so the Delphi version is a useful prefix.<\/p>\n\n\n\n<p class=\"has-background has-very-light-gray-background-color\">As well as making it easier for humans to interpret results, this enables the test tracking performed by Azure DevOps to identify when a particular test that was broken has now become fixed.<\/p>\n\n\n\n<p>For the tests themselves, here&#8217;s an example from the same test suite:<\/p>\n\n\n<div class=\"wp-block-syntaxhighlighter-code \"><pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\n  unit TestCore;\n\n{$i deltics.inc}\n\ninterface\n\n  uses\n    Deltics.Smoketest;\n\n  type\n    TCoreFunctionality = class(TTest)\n      procedure DelphiVersionIsIdentified;\n      procedure DelphiVersionIsTheExpectedVersion;\n    end;\n\n\n\nimplementation\n\n  uses\n    SysUtils,\n    TestConsts;\n\n\n  procedure TCoreFunctionality.DelphiVersionIsIdentified;\n  begin\n    Assert(&#039;UNKNOWN_COMPILER_VERSION is not defined&#039;,\n           {$ifdef UNKNOWN_COMPILER_VERSION} FALSE {$else} TRUE {$endif},\n           &#039;UNKNOWN_COMPILER_VERSION *is* defined!&#039;);\n  end;\n\n\n  procedure TCoreFunctionality.DelphiVersionIsTheExpectedVersion;\n  var\n    expectedVersion: String;\n  begin\n    if NOT TestRun.HasCmdLineOption(&#039;delphiVersion&#039;, expectedVersion) then\n    begin\n      Console.WriteLn(&#039;ERROR: -delphiVersion:&lt;version&gt; command line parameter missing&#039;);\n      TestRun.Abort;\n    end;\n\n    if NOT Assert(&#039;Expected version = Detected version&#039;,\n                  SameText(expectedVersion, DELPHI_VERSION),\n                  Format(&#039;Expected version &#039;&#039;%s&#039;&#039;, Detected version %s&#039;, &#91;expectedVersion, DELPHI_VERSION])) then\n      TestRun.Abort;\n  end;\n\n\nend.\n<\/pre><\/div>\n\n\n<p>This is a slightly clunky example since what is being tested here are the settings of compiler <strong><code>$DEFINE<\/code><\/strong> symbols, but the principle should be clear.<\/p>\n\n\n\n<p>For a test, you write an <strong><code>Assert()<\/code><\/strong> that has at least two parameters.  The first is the name of the test that the assert applies.  This is used in the test output to identify the individual test.<\/p>\n\n\n\n<p>The second parameter is a boolean &#8211; if this is <strong>TRUE<\/strong> then the test is considered to have passed.  If <strong>FALSE<\/strong> then it failed.<\/p>\n\n\n\n<p class=\"has-background has-very-light-gray-background-color\">The third parameter is optional and may be specified to provide a  message that will be attached to the test result.  In most cases results writers will output this message only for tests that fail or end in an error state.<\/p>\n\n\n\n<p>In the example above, the second test method a demonstrates a couple of additional features of the framework.<\/p>\n\n\n\n<p>The first is a Delphi version agnostic mechanism for interrogating command line switches and obtaining values from them.  These are expected to be of the form:<\/p>\n\n\n\n<p>       <code>-&lt;&lt;em&gt;optionName&lt;\/em&gt;&gt;[:&lt;&lt;em&gt;value&lt;\/em&gt;&gt;]<\/code> <br> or  <code>-&lt;&lt;em&gt;optionName&lt;\/em&gt;&gt;[=&lt;&lt;em&gt;value&lt;\/em&gt;&gt;]<\/code> <br> or  <code>-&lt;&lt;em&gt;optionName&lt;\/em&gt;&gt;<\/code><\/p>\n\n\n\n<p>The second additional feature is ability to abort a test run.<\/p>\n\n\n\n<p>A third feature is the fact that an assert also returns its pass\/fail value back to the caller which may be used to direct conditional flow where appropriate.  In this case the result of a test is also used to determine whether or not to abort the remaining test run.<\/p>\n\n\n\n<p class=\"has-background has-pale-pink-background-color\">An aborted test run still actually <em>performs all remaining tests<\/em> but every single test that follows is recorded with an outcome of &#8220;skipped&#8221; rather than pass or fail.  Even raising an exception will not prevent subsequent tests from running, only those in the current test method.<\/p>\n\n\n\n<hr class=\"wp-block-separator\"\/>\n\n\n\n<p class=\"has-drop-cap\">I will be providing more documentation at some point as the project grows.  In the meantime, for a more detailed example you can peruse the <a href=\"https:\/\/github.com\/deltics\/smoketest\/tree\/master\/tests\">self-test project<\/a> in the repo.  Be aware that this also takes advantage of some sneaky-stuff to access methods designed and intended for use specifically (and only) in self-test scenarios.<\/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\">6<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> 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).<\/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":"Introducing: Smoketest 2.0.  A new, ultra-lightweight unit testing framework for Delphi, with xUnit 2 formatted result output.","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[324,323,4,321,48],"tags":[292,51,295],"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-LG","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":2144,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2144\/","url_meta":{"origin":2956,"position":0},"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":2164,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2164\/","url_meta":{"origin":2956,"position":1},"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":[]},{"id":2185,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2185\/","url_meta":{"origin":2956,"position":2},"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":2095,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2095\/","url_meta":{"origin":2956,"position":3},"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":2006,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2006\/","url_meta":{"origin":2956,"position":4},"title":"Blowing Smoke&#8230;","date":"01 Nov 2013","format":false,"excerpt":"To alleviate the grind of polishing and sanitising my code (and, let's be honest, just plain 'fixing' it in some cases) ready for release, I have re-kindled my participation on Stack Overflow. In a happy confluence yesterday a question came up which allowed me to exercise one of the libraries\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"Ready to Run","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-01-at-09.00.33.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2169,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2169\/","url_meta":{"origin":2956,"position":5},"title":"Smoketest &#8211; Set Me Up \/ Tear Me Down","date":"20 Nov 2013","format":false,"excerpt":"In a previous post I demonstrated how the default \"pretty name\" for a Smoketest test case (derived from the test case classname) can be over-ridden by a test developer by implementing a specific interface (INameCase) on the test case class itself. There are some other interfaces that can be implemented\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\/2956"}],"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=2956"}],"version-history":[{"count":10,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2956\/revisions"}],"predecessor-version":[{"id":2975,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2956\/revisions\/2975"}],"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=2956"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=2956"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=2956"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}