{"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=\"span-reading-time 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 class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">Clone the repo somewhere convenient and add the <code class=\"\" data-line=\"\">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 class=\"wp-block-paragraph\">To write a test suite you need only add <code class=\"\" data-line=\"\">Deltics.Smoketest<\/code> to your <code class=\"\" data-line=\"\">program<\/code> <code class=\"\" data-line=\"\">uses<\/code> clause and call the appropriate methods on the <code class=\"\" data-line=\"\">TestRun<\/code> object that this introduces.  At a minimum this means making at least one call to <code class=\"\" data-line=\"\">TestRun.Test<\/code>, passing a test class (or array of test classes) to be executed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To implement a test class that you can pass to the <code class=\"\" data-line=\"\">TestRun<\/code> you derive a class from the <code class=\"\" data-line=\"\">TTest<\/code> class.  Again, this needs only <code class=\"\" data-line=\"\">Deltics.Smoketest<\/code> present in your <code class=\"\" data-line=\"\">uses<\/code> clause.  The tests performed by a test class will be any published, parameter-less methods.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">To write tests in your test methods, use the <code class=\"\" data-line=\"\">Assert<\/code> method inherited from the <code class=\"\" data-line=\"\">TTest<\/code> class.  <code class=\"\" data-line=\"\">AssertException<\/code> and <code class=\"\" data-line=\"\">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 wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">At time of writing only one format is supported: XUnit 2.x.  The format identifier for this is <code class=\"\" data-line=\"\">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 class=\"wp-block-paragraph\"><code class=\"\" data-line=\"\">mytests -xunit2:test-results.xml<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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 class=\"\" data-line=\"\">&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 wp-block-paragraph\">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 class=\"\" data-line=\"\">$DEFINE<\/code> symbols and the only &#8216;code&#8217; under test is the include file.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">These values are fixed once the first test has been performed in a run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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 wp-block-paragraph\">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 class=\"wp-block-paragraph\">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;, &#x5B;expectedVersion, DELPHI_VERSION])) then\n      TestRun.Abort;\n  end;\n\n\nend.\n<\/pre><\/div>\n\n\n<p class=\"wp-block-paragraph\">This is a slightly clunky example since what is being tested here are the settings of compiler <strong><code class=\"\" data-line=\"\">$DEFINE<\/code><\/strong> symbols, but the principle should be clear.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For a test, you write an <strong><code class=\"\" data-line=\"\">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 class=\"wp-block-paragraph\">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 wp-block-paragraph\">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 class=\"wp-block-paragraph\">In the example above, the second test method a demonstrates a couple of additional features of the framework.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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 class=\"wp-block-paragraph\">       <code class=\"\" data-line=\"\">-&lt;&lt;em&gt;optionName&lt;\/em&gt;&gt;[:&lt;&lt;em&gt;value&lt;\/em&gt;&gt;]<\/code> <br> or  <code class=\"\" data-line=\"\">-&lt;&lt;em&gt;optionName&lt;\/em&gt;&gt;[=&lt;&lt;em&gt;value&lt;\/em&gt;&gt;]<\/code> <br> or  <code class=\"\" data-line=\"\">-&lt;&lt;em&gt;optionName&lt;\/em&gt;&gt;<\/code><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The second additional feature is ability to abort a test run.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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 wp-block-paragraph\">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 wp-block-paragraph\">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=\"span-reading-time 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_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":"Introducing: Smoketest 2.0.  A new, ultra-lightweight unit testing framework for Delphi, with xUnit 2 formatted result output.","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":[324,323,4,321,48],"tags":[292,51,295],"class_list":["post-2956","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-automation","category-ci-cd","category-delphi","category-devops","category-testing","tag-delphi","tag-smoketest","tag-testing"],"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":[],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2956","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=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}]}}