{"id":2111,"date":"2013-11-07T16:39:34","date_gmt":"2013-11-07T04:39:34","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=2111"},"modified":"2013-11-07T17:30:21","modified_gmt":"2013-11-07T05:30:21","slug":"extending-smoketest-part-2-greater-expectations","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/2111\/","title":{"rendered":"Extending Smoketest (Part 2) &#8211; Great(er) Expectations"},"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><p>In my previous post on <strong>Smoketest<\/strong> I showed how you can <a href=\"https:\/\/www.deltics.co.nz\/blog\/posts\/2095\">extend the inspections framework to work with complex types in your code<\/a>.  As promised, I shall now show how you can do much the same thing to extend the framework with entirely new tests.<\/p>\n<p><!--more--><\/p>\n<blockquote><p>\u201cI must be taken as I have been made. The success is not mine, the failure is not mine, but the two together make me.\u201d<br \/>\n\u2015 Charles Dickens, Great Expectations\n<\/p><\/blockquote>\n<p>Well, you won&#8217;t have to take <strong>Smoketest<\/strong> entirely as it has been made.  You can extend it.  \ud83d\ude42<\/p>\n<p>For a custom inspector an interface and a class are required.  For a custom test (or tests) we need <strong>two<\/strong> interfaces and a class.<\/p>\n<p>The first interface provides the methods which capture a value for which we will subsequently provide the test(s) in the second interface which must be returned by the capture method.  Following the convention in the <strong>Smoketest<\/strong> framework, capture methods are called <strong>Expect()<\/strong>.<\/p>\n<p>Both interfaces must have an <strong>IID<\/strong>.<\/p>\n<p>By convention the capture interface is named for the type being captured with a &#8220;Test&#8221; suffix.  The interface providing the test(s) is named with an &#8220;Expectation&#8221; suffix:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n    ComplexNumberTest = interface;\r\n    ComplexNumberExpectation = interface;\r\n\r\n    ComplexNumberTest = interface\r\n    ['{A8FFAEFB-6F72-46F4-8E6C-68B770CC524B}']\r\n      function Expect(aValue: TComplexNumber): ComplexNumberExpectation;\r\n    end;\r\n\r\n    ComplexNumberExpectation = interface\r\n    ['{3B45B9FA-8647-4630-88A6-A30A2E0E026A}']\r\n      function Equals(aValue: TComplexNumber): IEvaluation;\r\n    end;\r\n<\/pre>\n<p>For the purposes of this example I shall implement only one test &#8211; for equality.<\/p>\n<p>Test methods are expected (but not required) to yield an <strong>Evaluation<\/strong> interface as their result to allow a test author to append conditions such as <strong>IsRequired<\/strong>, <strong>IsCritical<\/strong> etc (this functionality is gained automatically, as long as you return this interface).<\/p>\n<p>Although we need two interfaces, only one class is necessary to implement both of these.  As with the inspector class, this must extend a specific base class which for tests and expectations is the <strong>TExpectation<\/strong> class.  This class will hold the captured value being tested so we need a member variable for that:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n    TComplexNumberTest = class(TExpectation, ComplexNumberTest,\r\n                                             ComplexNumberExpectation)\r\n    private\r\n      fValue: TComplexNumber;\r\n      property Value: TComplexNumber read fValue;\r\n\r\n    public \/\/ ComplexNumberTest\r\n      function Expect(aValue: TComplexNumber): ComplexNumberExpectation;\r\n\r\n    public \/\/ ComplexNumberExpectation\r\n      function Equals(aExpected: TComplexNumber): Evaluation; reintroduce;\r\n    end;\r\n<\/pre>\n<p><strong>NOTE:<\/strong> <em>The reintroduce directive on the <strong>Equals()<\/strong> method is to silence the warning that results on more recent versions of Delphi where <strong>TObject<\/strong> introduces an <strong>Equals()<\/strong> method of it&#8217;s own.<\/em><\/p>\n<p>The implementation of the <strong>Expect()<\/strong> method is straightforward.  This method needs only to capture the value being tested (passed to the <strong>Expect()<\/strong> method) and return itself, since the tests are also implemented on the same object:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  function TComplexNumberTest.Expect(aValue: TComplexNumber): ComplexNumberExpectation;\r\n  begin\r\n    result := self;\r\n    fValue := aValue;\r\n    Actual := ComplexNumberToString(aValue);\r\n  end;\r\n<\/pre>\n<p>In this instance as well as capturing the value to be subjected to any tests we also set a property of the <strong>TExpectation<\/strong> class called <strong>Actual<\/strong>.  This is a string representation of the captured value which can be substituted in test output later.<\/p>\n<p>The test method &#8211; <strong>Equals()<\/strong> &#8211; is similarly straightforward:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  function TComplexNumberTest.Equals(aExpected: TComplexNumber): IEvaluation;\r\n  begin\r\n    result := self;\r\n\r\n    Description := '= {expected}';\r\n    Expected    := ComplexNumberToString(aExpected);\r\n\r\n    OK := (Value.Real = aExpected.Real)\r\n      and (Value.Imaginary = aExpected.Imaginary);\r\n  end;\r\n<\/pre>\n<p>Again, the <strong>TExpectation<\/strong> class already implements the necessary <strong>Evaluation<\/strong> interface allowing a test author to specify the criticality of any failure of the test if they require, so if we choose to yield the <strong>Evaluation<\/strong> interface all we have to do is return <strong>self<\/strong> again.<\/p>\n<p>Other than that, it is helpful to set some further properties that will be used in the output of our test results.<\/p>\n<p>For most tests it makes sense to set a <strong>Description<\/strong> string which describes the intent of the test.  In this case we use a simple string <code>&#039;= {expected}&#039;<\/code>.<\/p>\n<p>The token <code>{expected}<\/code> will be substituted by <strong>Smoketest<\/strong> (later) with the value of the second property we shall set: <strong>Expected<\/strong>.  Similar to the <strong>Actual<\/strong> property, this is a simple string representation of the expected value, as passed to the <strong>Equals<\/strong> method.<\/p>\n<p>The <strong>Description<\/strong> property is a string that is appended to any label specified by the test expression.  Something to bear in mind is that if the test author has not specified a label then the value of <strong>Actual<\/strong> will be used.<\/p>\n<p>How you format the <strong>Actual<\/strong>, <strong>Expected<\/strong> and <strong>Description<\/strong> strings for any given test is largely up to you.  These are used only for the presentation of the results and are not involved in the test itself (there are some aspects of the way that results are presented which should be borne in mind but that is beyond the scope of this simple example).<\/p>\n<p>That most critical aspect of a custom test is handled by setting the <strong>OK<\/strong> property.<\/p>\n<p>This is a simple boolean that determines whether the test has passed or failed.<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n    OK := (Value.Real = aExpected.Real)\r\n      and (Value.Imaginary = aExpected.Imaginary);\r\n<\/pre>\n<p>For a complex number equality test, the test passes if the real and imaginary parts of the two numbers are both equal.<\/p>\n<p>The final step is to register the extension class and the test interface.  The expectation interface is not involved in the registration process as the Smoketest framework has no need to even be aware of it&#8217;s existence, only the test interface is needed:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  Smoketest.RegisterExtension(ComplexNumberTest, TComplexNumberTest);\r\n<\/pre>\n<p>Just as with the inspector extension, the implementing class can be kept strictly as an implementation detail in an extension unit, if you wish.<\/p>\n<p>With all that in place, test authors can now write tests for the TComplexNumber type almost as if they were part of the testing framework:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  var\r\n    a, b: TComplexNumber;\r\n  begin\r\n    a.Real      := 1;\r\n    a.Imaginary := 4;\r\n\r\n    b := a;\r\n    (Test as ComplexNumberTest).Expect(a).Equals(b);\r\n  end;\r\n<\/pre>\n<p>Which results in the following output:<\/p>\n<figure id=\"attachment_2112\" aria-describedby=\"caption-attachment-2112\" style=\"width: 276px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-07-at-17.28.18.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-07-at-17.28.18.png?resize=276%2C98&#038;ssl=1\" alt=\"Equality!\" width=\"276\" height=\"98\" class=\"size-full wp-image-2112\" data-recalc-dims=\"1\" \/><\/a><figcaption id=\"caption-attachment-2112\" class=\"wp-caption-text\">Equality!<\/figcaption><\/figure>\n<p>For completeness here is an example where the test is rigged to fail:<\/p>\n<pre class=\"brush: delphi; title: ; notranslate\" title=\"\">\r\n  var\r\n    a, b: TComplexNumber;\r\n  begin\r\n    a.Real      := 1;\r\n    a.Imaginary := 4;\r\n\r\n    a.Real      := 1;\r\n    a.Imaginary := 3;\r\n    (Test as ComplexNumberTest).Expect(a).Equals(b);\r\n  end;\r\n<\/pre>\n<figure id=\"attachment_2113\" aria-describedby=\"caption-attachment-2113\" style=\"width: 266px\" class=\"wp-caption aligncenter\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-07-at-17.29.05.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-07-at-17.29.05.png?resize=266%2C111&#038;ssl=1\" alt=\"All complex numbers are not equal\" width=\"266\" height=\"111\" class=\"size-full wp-image-2113\" data-recalc-dims=\"1\" \/><\/a><figcaption id=\"caption-attachment-2113\" class=\"wp-caption-text\">All complex numbers are not equal<\/figcaption><\/figure>\n<p>An extension can implement as many methods on an expectation interface as it sees fit.  In the case of Complex Numbers is might make sense &#8211; if this were a real extension &#8211; to provide tests for <strong>GreaterThan<\/strong>, <strong>LessThan<\/strong>, <strong>NotEqual<\/strong> etc.  <\/p>\n<p>All of these would be implemented by the same extension class and would simply need to set a Description more appropriate to the expectation being tested in each case and set the OK property to reflect the outcome of the particular test.<\/p>\n<p>Pretty soon you will be able to play with the possibilities yourself as I hope to have the <strong>Smoketest<\/strong> framework with enough documentation to get people started ready to publish before the end of next week (though complete documentation may take longer).<\/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> In my previous post on Smoketest I showed how you can extend the inspections framework to work with complex types in your code. As promised, I shall now show how you can do much the same thing to extend the framework with entirely new tests.<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4],"tags":[292,257,51],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-y3","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":2095,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2095\/","url_meta":{"origin":2111,"position":0},"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":2144,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2144\/","url_meta":{"origin":2111,"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":2006,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2006\/","url_meta":{"origin":2111,"position":2},"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":2193,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2193\/","url_meta":{"origin":2111,"position":3},"title":"DUnit Compatibility in Smoketest","date":"25 Nov 2013","format":false,"excerpt":"To address some odd concerns about differences between DUnit and Smoketest, I thought it would be useful to demonstrate how it is entirely within the gift of a Smoketest user to create their own \"comfort\" layer, to make using Smoketest more similar to the DUnit framework if they wish (though\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":961,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/961\/","url_meta":{"origin":2111,"position":4},"title":"Thinking Creatively with QueryInterface()","date":"02 Aug 2012","format":false,"excerpt":"I soon hope to be releasing \"Smoketest\", a testing framework that I have developed over the past few years. It has actually been in production use for most of that time (albeit by my own good self) but also continues to develop and evolve. \u00a0On the occasions when I have\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":2164,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2164\/","url_meta":{"origin":2111,"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\/2111"}],"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=2111"}],"version-history":[{"count":4,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2111\/revisions"}],"predecessor-version":[{"id":2117,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/2111\/revisions\/2117"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=2111"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=2111"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=2111"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}