VSoft Technologies BlogsVSoft Technologies Blogs - posts about our products and software development.https://www.finalbuilder.com/resources/blogsIntroducing VSoft.CommandLineParser for Delphihttps://www.finalbuilder.com/resources/blogs/postid/719/introducing-vsoftcommandline-for-delphiDelphi,Open SourceSat, 26 Jul 2014 14:20:12 GMT<h2>Command line parsing</h2> <p>Pretty much every delphi console application I have ever written or worked on had command line options, and every one of the projects tried different ways for defining and parsing the supplied options. Whilst working on DUnitX recently, I needed to add some command line options, and wanted to find a nice way to add them and make it easy to add more in the future. The result is <a href="https://github.com/VSoftTechnologies/VSoft.CommandLineParser" target="_blank">VSoft.CommandLineParser</a> (copies of which are included with the latest DUnitX).</p> <h3>Defining Options</h3> <p>One of the things I really wanted, was to have the parsing totally decoupled from definition and the storage of the options values. Options are defined by registering them with the TOptionsRegistry, via TOptionsRegistry.RegisterOption&lt;T&gt; - whilst it makes use of generics, only certain types can be used, the types are checked at runtime, as generic constraints are not flexible enough to specify which types we allow at compile time. Valid types are string, integer, boolean, enums &amp; sets and floating point numbers. </p> <p>Calling RegisterOption will return a definition object which implements IOptionDefinition. This definition object allows you to set various settings (such as Required). When registering the option, you specify the long option name, the short option name, help text (will be used when showing the usage) and a TProc&lt;T&gt; anonymous method that will take the parsed value as a parameter.</p> <pre class="brush:delphi; toolbar:true;">procedure ConfigureOptions; var option : IOptionDefintion; begin option := TOptionsRegistry.RegisterOption&lt;string&gt;('inputfile','i','The file to be processed', procedure(value : string) begin TSampleOptions.InputFile := value; end); option.Required := true; option := TOptionsRegistry.RegisterOption&lt;string&gt;('outputfile','o','The processed output file', procedure(value : string) begin TSampleOptions.OutputFile := value; end); option.Required := true; option := TOptionsRegistry.RegisterOption&lt;boolean&gt;('mangle','m','Mangle the file!', procedure(value : boolean) begin TSampleOptions.MangleFile := value; end); option.HasValue := False; option := TOptionsRegistry.RegisterOption&lt;boolean&gt;('options','','Options file',nil); option.IsOptionFile := true; end; </pre> <p>For options that are boolean in nature, ie they have do not value part, the value passed to the anonymous method will be true if the option was specified, otherwise the anonymous method will not be called. The 'mangle' option in the above example shows this scenario. </p> <p>You can also specify that an option is a File, by setting the IsOptionFile property on the option definition. This tells the parser the value will be a file, which contains other options to be parsed (in the same format as the command line). This is useful for working around windows command line length limitations.</p> <p>Currently the parser will accept<br /> -option:value<br /> --option:value<br /> /option:value </p> <p>Note the : delimiter between the option and the value.</p> <p>Unnamed parameters are registered via the TOptionsRegistry.RegisterUnNamedOption&lt;T&gt; method. Unlike named options, unnamed options are positional, but only when more than one is registered, as they will be passed to the anonymous methods in the order they are registered.</p> <h3>Parsing the options.</h3> <p>Parsing the options is as simple as calling TOptionsRegistry.Parse, which returns a ICommandLineParseResult object. Check the HasErrors property to see if the options were valid, the ErrorText property has the parser error messages.</p> <h3>Printing Usage</h3> <p>If the parser reports errors, then typically you would show the user what the valid options are and exit the application, e.g:</p> <pre class="brush:delphi; toolbar:true;"> parseresult := TOptionsRegistry.Parse; if parseresult.HasErrors then begin Writeln(parseresult.ErrorText); Writeln('Usage :'); TOptionsRegistry.PrintUsage( procedure(value : string) begin Writeln(value); end); end else ..start normal execution here </pre> <p>The TOptionsRegistry.PrintUsage makes it easy to print the usage to the command line.</p> <p>When I started working on this library, I found some really complex libraries (mostly .net) out there with a lot of options, but I decided to keep mine as simple as possible and only cover off the scenarios I need right now. So it's entirely possible this doesn't do everything people might need, but it's pretty easy to extend. The <a href="https://github.com/VSoftTechnologies/VSoft.CommandLineParser" target="_blank">VSoft.CommandLineParser</a> library (just three units) is open source and available on Github, with a sample application and unit tests (DUnitX) included.</p>719DUnitX Updated : Filtering Testshttps://www.finalbuilder.com/resources/blogs/postid/717/dunitx-updated-filtering-testsDelphi,Open Source,Unit TestingThu, 24 Jul 2014 16:39:00 GMT<style type="text/css"> table.categories { margin: 1em 5%; padding: 0; width: auto; border-collapse: collapse; } table.categories td, table.categories th { border: 1px solid black; padding: 6px; text-align: left } table.categories th { background: #117e42; color:white } ol.operators {margin-left: 18px;} </style> <h2>Still evolving</h2> <p>DUnitX is still quite young, and still evolving. One of the features most often requested is the ability to select which tests to run. I found myself wishing for that feature recently. I never missed it while the number of my tests were relatively small and fast, but as time went by, it was taking longer and longer to debug tests. So, time to add filtering of fixtures and tests.</p> <p>The command options support in DUnitX was to be honest, quite useless and poorly though out. So my first task was to tackle how options were set/used in DUnitX, and find an extensible way of handling command line options. The result turned out better than I exepected, so I have published a separate project for that. <a href="https://github.com/VSoftTechnologies/VSoft.CommandLineParser" alt="VSoft.CommandLine project on github" rel="nofollow" target="_blank">VSoft.CommandLine</a> is a very simple library for defining and parsing command line options, which decouples the definition and parsing from where the parsed values are stored. I'll blog about this library separately.</p> <p>I did try to avoid breaking any existing test projects out there. To invoke the command line option parsing, you will need to add a call to TDUnitX.CheckCommandLine; at the start of you project code, eg:</p> <pre class="brush:delphi; toolbar:true;">begin try TDUnitX.CheckCommandLine; //Create the runner runner := TDUnitX.CreateRunner; </pre> <p>The call should be inside the try/except because it will throw exceptions if any errors are found with the command line options. I modified the IDE Expert to include the needed changes in any new projects it creates, I recommend running the expert to generate a project and then compare it to your existing dpr.</p> <h2>Filtering</h2> <p>The next thing to look at was how to apply filtering. After much experimentation, I eventually settled on pretty much copying how NUnit does it. I ported the filter and CategoryExpression classes from NUnit, with a few minor mods needed to adapt them to our needs. The cool thing here is I was able to port the associated unit tests over with ease!</p> <p>There are two types of filters, namespace/fixture/test filters, and category filters.</p> <h3>Namespace/Fixture/Test filtering</h3> <p>The new command line options are :</p> <pre>--run - specify which Fixtures or Tests to run, separate values with a comma, or specify the option multiple times</pre> eg: <pre>--run:DUnitX.Tests.TestFixture,DUnitX.Tests.DUnitCompatibility.TMyDUnitTest</pre> <p>If you specify a namespace (ie unit name or part of a unit name) then all fixtures and tests matching the namespace will run.</p> <h3>Category Filters</h3> <p>A new CategoryAttribute allows you to a apply categories to fixtures and/or tests. Tests inherit their fixture's categories, except when they have their own CategoryAttribute. You can specify multiple categories, separated by commas, eg:</p> <pre class="brush:delphi; toolbar:true;">[TestFixture] [Category('longrunning,suspect')] TMyFixture = class public [Test] procedure Test1; [Test] [Category('fast')] procedure Test2; </pre> <p>In the above example, Test1 would have "longrunning" and "suspect" categories, whilst Test2 would have just "fast".</p> <p>You can filter tests using these categories, using the --include and/or --exclude command line options. When both options are specifies, all the tests with the included categories are run, except for those with the excluded categories. The following info is copied from the NUnit doco (on which these options are based) :</p> <table class="categories"> <thead> <tr> <th>Expression</th> <th>Action</th> </tr> </thead> <tbody> <tr> <td>A|B|C</td> <td>Selects tests having any of the categories A, B or C.</td> </tr> <tr> <td>A,B,C</td> <td>Selects tests having any of the categories A, B or C.</td> </tr> <tr> <td>A+B+C</td> <td>Selects only tests having all three of the categories assigned</td> </tr> <tr> <td>A+B|C</td> <td>Selects tests with both A and B OR with category C.</td> </tr> <tr> <td>A+B-C</td> <td>Selects tests with both A and B but not C.</td> </tr> <tr> <td>-A</td> <td>Selects tests not having category A assigned</td> </tr> <tr> <td>A+(B|C)</td> <td>Selects tests having both category A and either of B or C</td> </tr> <tr> <td>A+B,C</td> <td>Selects tests having both category A and either of B or C</td> </tr> </tbody> </table> <p>As shown by the last two examples, the comma operator is equivalent to | but has a higher precendence. Order of evaluation is as follows:</p> <p> </p> <ol class="operators"> <li>Unary exclusion operator (-)</li> <li>High-precendence union operator (,)</li> <li>Intersection and set subtraction operators (+ and binary -)</li> <li>Low-precedence union operator (|)</li> </ol> <p> </p> <p> <string>Note :</string> Because the operator characters have special meaning, you should avoid creating a category that uses any of them in it's name. For example, the category "db-tests" could not be used on the command line, since it appears to means "run category db, except for category tests." The same limitation applies to characters that have special meaning for the shell you are using. I have also fixed some other minor issues with the naming of repeated tests and test cases to allow them to work with the filter. </p> <h3>Other options</h3> <p>Once you have added the command line check, run yourexe /? to see the other command line options available. None of the options are required so running the exe without any options will behave as it did before.</p> <h3>Delphi 2010</h3> <p><strong><span style="color: #ff0000;">Resolved</span></strong> - Thanks to Stefan Glienke for figuring this out - D2010 now support again . This fix was to remove any use of of STRONGLINKTYPES. </p> <p>One thing of note: at the moment these changes break our D2010 support. I get a linker error when I build :</p> <pre>[DCC Fatal Error] F2084 Internal Error: L1737</pre> <p>Interestingly, the resulting executable is produced and does seem to run ok, however it makes debugging tests impossible, and of course it would fail in automated build. I did spend several hours trying to resolve this error but got nowhere. Since my usage of DUnitX is currently focused on XE2, I'm willing to live with this and just use an older version of DUnitX for D2010. I have tested with XE2, XE5 and XE6.</p>717DUnitX has a Wizard!https://www.finalbuilder.com/resources/blogs/postid/702/dunitx-has-a-wizardDelphi,Unit TestingSun, 05 Jan 2014 08:32:25 GMT<p>Thanks to a contribution from Robert Love, DUnitX now sports a shiny new IDE Wizard for creating Test projects and Test Units. </p> <p>Before you install and use the wizard, there is one thing I recommend you do. In your Delphi IDE, add and Environment variable DUNITX and point it at your copy of the DUnitX source. The reason for doing this, is that when the wizard creates a project, it adds $(DUNITX) to the project search path. This avoids hard coding the DUnitX folder in the project search path, and also avoids installing it in your global library path (I have nothing other than the defaults installed there, I always use the project search path, makes it easier to share projects with other devs). </p> <p style="text-align: center;"><img alt="" src="/blogimages/vincent/DUnitX-Wizard/dunitx-environ.png" /></p> <p> </p> <p>Once you have that done (I'm assuming you have pulled down the latest source from GitHub), open the project group (.grouproj) for your IDE version and build the project group. Then right click on the wizard project and click on install :</p> <p style="text-align: center;"><img alt="" src="/blogimages/vincent/DUnitX-Wizard/dunitx-install-wizard.png" /></p> <p>If the package installs successfully then we are ready to use the wizard. Close the project group, and invoke the File\New\Other dialog, you will see the DUnitX Project listed </p> <p> </p> <p style="text-align: center;"><img alt="" src="/blogimages/vincent/DUnitX-Wizard/dunitx-project.png" /><img alt="" src="/blogimages/vincent/DUnitX-Wizard/dunitx-unit.png" /></p> <p> </p> <p>You might like to Customize your File\New menu, I made DUnitX prominent on mine (in part to remind myself to create unit tests first!) :</p> <p style="text-align: center;"><img alt="" src="/blogimages/vincent/DUnitX-Wizard/dunitx-filenew.png" /></p> <p> </p> <p>Invoking the wizard will show a simple dialog :</p> <p> </p> <p style="text-align: center;"><img alt="" src="/blogimages/vincent/DUnitX-Wizard/dunitx-project-wizard.png" /></p> <p>The options are pretty self explainatory, so I won't go into them here. The wizard generates a console application, once we have a gui runner (being worked on) we'll update the wizard to add options for that.</p> <p>DUnitX is open source, get it from <a href="https://github.com/VSoftTechnologies/DUnitX" target="_blank">GitHub</a> - contributions are welcome. We also have a <a href="https://plus.google.com/communities/110602661860791972403" target="_blank">Google Plus Community</a> for DUnitX.</p> <p> </p> 702Integrating DUnitX Unit Testing with Continua CIhttps://www.finalbuilder.com/resources/blogs/postid/699/integrating-dunitx-unit-testing-with-continua-ciContinua CI,Delphi,FinalBuilderMon, 25 Nov 2013 13:32:00 GMT<p>Continua CI includes support for running and reporting on Unit Tests, in this post we will take a look at running <a href="https://github.com/VSoftTechnologies/DUnitX" title="DUnitX is open source, on GitHub">DUnitX</a> Unit Tests. If you not familiar with <a href="https://github.com/VSoftTechnologies/DUnitX" title="DUnitX is open source, on GitHub">DUnitX</a>, it's a newish Delphi Unit Test framework, I <a href="/resources/blogs/introducing-dunitx" title="Introducing DUnitX - a Unit Test framework for Delphi">blogged about it recently</a>. </p> <p>I'm not going to cover how to get up and running in Continua CI, but rather I'll focus on the Unit Testing support. If you are not familiar with Continua CI, take a look at <a href="/resources/blogs/building-delphi-projects-with-continua-ci" title="Building Delphi projects with Continua CI">this recent post</a> which describes how to build Delphi projects with Continua CI.</p> <p>Assuming you have you Continua CI Configuration all set up, lets take a quick look at how to integrate our unit tests into the build process. You need to build a console application, and make use of the xml logger. This is how the dpr of our typical DUnitX console test application might look  :</p> <pre class="brush:delphi; toolbar:true;"> var runner : ITestRunner; results : IRunResults; logger : ITestLogger; xmlLogger : ITestLogger; begin try //Create the runner runner := TDUnitX.CreateRunner; //add a console logger, pass in true to specify quiet mode //as we don't need detailed console output. logger := TDUnitXConsoleLogger.Create(true); runner.AddLogger(logger); //add an nunit xml loggeer xmlLogger := TDUnitXXMLNUnitFileLogger.Create; runner.AddLogger(nunitLogger); //Run tests results := runner.Execute; {$IFDEF CI} //Let the CI Server know that something failed. if not results.AllPassed then System.ExitCode := 1; {$ELSE} //We don;t want this happening when running under CI. System.Write('Done.. press <enter> key to quit.'); System.Readln; {$ENDIF} except on E: Exception do begin System.Writeln(E.ClassName, ': ', E.Message); System.ExitCode := 2; end; end; end. </enter></pre> <p>The key thing to remember, is that this application is going to be running unattended, so never use ReadLn or any sort of interaction/prompting for input etc. If I had a dollar for every "Finalbuilder/Continua CI hangs during my build" bug report in the last 13 years, I'd be a... well not rich, but a few hundred dollars better off! Notice I used $IFDEF CI above to set the exit code to 1 if not all tests pass. </p> <p>So the next thing we need to is actually get the console application building in Continua CI. I covered building delphi applications with Continua CI in earlier post, so I'll just highlight a few things specific items that we need. Firsly, if you don't have the DUnitX source code in your repository, and have configured a repositoroy for it in Continua CI, then you need to update the Search Path for your console application. If you are using MSBuild to build the console app, then it's done on the Properties tab of the MSBuild Action :</p> <p style="text-align: center;"><img alt="MSBuild Properties" src="/blogimages/vincent/DUnitX-CI/DUnitX-MSBuild.png" /></p> <p>I have DUnitX in a Continau CI repository named DUnitX, and I've used the default path to the repository in the workspace ( $Source.DUnitX$ translates to "/Source/DUnitX" in the build workspace). If you are using FinalBuilder, you need to pass that to a FinalBuilder variable in the FinalBuilder Action, I'll cover that in more detail in a future post. Notice I also set the CI define I used in my code. The other important setting, is the ExeOutput path, which much be somewhere inside the build's workspace, so I set it to $Workspace$\Output - Continua CI will translate $Workspace$ at build time to be the workspace folder for the build (each Continua CI build gets a clean unique workspace folder). </p> <p>Now it's time to add our DUnitX action, somewhere in the stage workflow after we have built the test application. </p> <p> </p> <p style="text-align: center;"><img alt="Stage Workflow" src="/blogimages/vincent/DUnitX-CI/DUnitX-Workflow.png" /></p> <p>Setting up the DUnitX action is quite simple :</p> <p style="text-align: center;"><img alt="DUnitX Action" src="/blogimages/vincent/DUnitX-CI/DUnitX-Action.png" /></p> <p> </p> <p>We set the Test Executable to our test console app, which in the MSBuild action we configured to be output to $Workspace$\Output - and we specify where to put the xml file that DUnitX will generate (because we added the NUnit logger).  The other two options control whether the to fail the Action/Build if any tests fail or error. If you have more than one Unit Test action to run in your build process, then it's bests to leave these unchecked and use the Stage Gate feature (on the Stage Options dialog) to fail the build (more on this later). </p> <p>After running the build, the Unit Test results appear in a two places, firstly the Build Details page, which shows the totals for the build (for all unit tests run) :</p> <p> </p> <p style="text-align: center;"><img alt="Unit Test Totals" src="/blogimages/vincent/DUnitX-CI/DUnitX-BuildResult.png" /></p> <p>You can drill into the tests by clickong on the numbers, or by clicking on the Unit Tests tab : </p> <p style="text-align: center;"><img alt="Unit Test Details" src="/blogimages/vincent/DUnitX-CI/DUnitX-Results.png" /></p> <p>This page allows you to filter by status (click on the numbers), and filter by Fixture, Namespacve etc. The first time a test fails, it will show up inder the New Failures category and under Failures. Clicking on a Failed or Errored test expands the row to show the error message logged by the test framework :</p> <p style="text-align: center;"><img alt="Failing Test Details" src="/blogimages/vincent/DUnitX-CI/DUnitX-FailingDetail.png" /></p> <p>Notice the Shelve button next to each test. If you have a test that always fails, and you don't want it to cause the build to fail, <a href="http://wiki.finalbuilder.com/display/continua/Unit+Tests#UnitTests-ShelvingTests" title="Shelving/Unshelving Tests">shelving</a> the tests tells Continua CI to ignore those failures in the future, until you unshelve them.  </p> <p>One last feature which I touched on, is the use of Stage Gates to fails the build. Continua CI collects a bunch of metrics during the build, and we can use those metrics in Stage Gate Conditions to fail the build if for example, there are any failing tests. Each Stage has it's own Gate Conditions, which are evaluated once the stage completes.. When the build fails at the Stage Gate, you will see the output of the condition expressions (note, the expressions below are the defaults on all stages) :</p> <p style="text-align: center;"><img alt="Failed at Stage Gate" src="/blogimages/vincent/DUnitX-CI/DUnitX-FailingSummary.png" /></p> <p> </p> 699A Simple IoC Container for Delphihttps://www.finalbuilder.com/resources/blogs/postid/698/a-simple-ioc-container-for-delphiDelphiMon, 28 Oct 2013 11:27:00 GMT<p>When working on <a href="/resources/blogs/introducing-dunitx">DUnitX</a> recently, I wanted a simple IoC container for internal use, and didn't want to add any external dependencies to the project. So I wrote my own. My container implementation <strong>only</strong> does IoC, it does not (and nor will it ever) do dependency injection. If you need Dependency Injection, the <a href="http://www.spring4d.org/">Delphi Spring Framework</a> includes is a comprehensive IoC/DI container (along with some other useful stuff). </p> <p><a href="https://github.com/VSoftTechnologies/Simple-IoC">Simple IoC</a> is a copy of the IoC container in DUnitX, extracted into it's own <a href="https://github.com/VSoftTechnologies/Simple-IoC">GitHub</a> project. It's basically a single pas file, with a class TSimpleIoC. Lets take a look at TSimpleIoC :</p> <pre class="brush:delphi; toolbar:true;"> TSimpleIoC = class public constructor Create; destructor Destroy;override; //Default Container class function DefaultContainer : TSimpleIoC; //Registration methods. Register the interface type, and an implementation. {$IFDEF DELPHI_XE_UP} //Exe's compiled with D2010 will crash when these are used. //NOTES: The issue is due to the two generics included in the functions. The constaints also seem to be an issue. procedure RegisterType<TInterface: IInterface; TImplementation: class>(const name : string = '');overload; procedure RegisterType<TInterface: IInterface; TImplementation: class>(const singleton : boolean;const name : string = '');overload; {$ENDIF} procedure RegisterType<TInterface: IInterface>(const delegate : TActivatorDelegate<TInterface>; const name : string = '' );overload; procedure RegisterType<TInterface: IInterface>(const singleton : boolean;const delegate : TActivatorDelegate<TInterface>; const name : string = '');overload; //Register an instance as a signleton. If there is more than one instance that implements the interface //then use the name parameter procedure RegisterSingleton<TInterface :IInterface>(const instance : TInterface; const name : string = ''); //Resolution function Resolve<TInterface: IInterface>(const name: string = ''): TInterface; //Returns true if we have such a service. function HasService<T: IInterface> : boolean; //Empty the Container.. usefull for testing only! procedure Clear; //Raise an exception if Resolve would return nil. property RaiseIfNotFound : boolean read FRaiseIfNotFound write FRaiseIfNotFound; end;</pre> <p> </p> <p>There are two ways to use TSimpleIoC, either by just using the DefaultContainer class function (I wanted to call it Default, but that's a reserved word in Delphi!), or by creating an instance of TSimpleIoC and using the instance. </p> <p> </p> <h3>A quick refresher on IoC</h3> <p>The main purpose of IoC is to decouple the interface from the implementation. Without IoC, I need to know (or rather the compiler does) that TMySmartService implements IMyService, and I need to instanciate TMySmartService to get an instance that implements IMyService. But what if I want to use multiple implementations?  I guess I could do this :</p> <pre class="brush:delphi; toolbar:true;"> function GetMyService(const implname : string) : IMyService; begin if implename = 'dumb' then result := TMyDumbService.Create else result := TMySmartService.Create; end;</pre> <p> </p> <p>But that's not very nice. What If I need to provide another implementation? This is where the use of an IoC Container simplify's things. The above code becomes this :</p> <p> </p> <pre class="brush:delphi; toolbar:true;"> var mySvc : IMyService; begin mySvc := TSimpleIoC.DefaultContainer.Resolve<IMyService>; //or mySvc := TSimpleIoC.DefaultContainer.Resolve<IMyService>('dumb'); ... end;</pre> <p>Of course we still need to tell the IoC container how to instanticiate something that implements IMyService.  </p> <h3>Registering Implementations</h3> <p>TSimpleIoC has a bunch of RegisterType overloads, the best one's of which only work on Delphi XE and up, the Delphi 2010 compiler falls over quite badly. I could have removed the D2010 support altogether, but we're using it here with DUnitX and FinalBuilder 7 (which is written in D2010). So, lets register our IMyService implementation with the container :</p> <pre class="brush:delphi; toolbar:true;"> type TMyServiceTest = class [SetupFixture] procedure FixtureSetup; end; implementation uses MyServiceIntf, MyServiceImpl; ... procedure TMyServiceTest.FixtureSetup; begin TSimpleIoC.DefaultContainer.RegisterType<IMyService,TMySmartService>(); TSimpleIoC.DefaultContainer.RegisterType<IMyService,TMyDumbService>('dumb'); end; </pre> <p> </p> <p>In the above example, if we called Resolve<IMyService>, a new instance of TMySmartService would be created each time. Registering multiple implementations is as simple as giving the implementations names. You can of course also register Singletons, even already instantiated implemetations as a singleton. </p> <p> </p> <h3>Poor Man's DI</h3> <p>Ok, so I said Simple IoC would never do DI, but you can fudge it using an activator delegate. An activator delegate is an anonymous function that will create an instance of your implementation. You can use this as a means to pass in constructor dependencies. </p> <pre class="brush:delphi; toolbar:true;"> TSimpleIoC.DefaultContainer.RegisterType<IMyService>( function : IMyService begin result := TMyService.Create(TDependency.Create); end); </pre> <p>I did say fudge!</p> <h3>Notes for DUnitX Users</h3> <p>DUnitX has this IoC container, but it's called TDUnitXIoC. Note that DUnitX makes use of the DefaultContainer internally, so you should not call the Clear method on it. You would be better off creating your own container instance. </p> <p>Simple IoC is open source, get it from <a href="https://github.com/VSoftTechnologies/Simple-IoC">GitHub</a></p> 698Introducing DUnitX - A Unit Test Framework for Delphihttps://www.finalbuilder.com/resources/blogs/postid/697/introducing-dunitxDelphi,Unit TestingThu, 19 Sep 2013 16:09:00 GMT<h4>But, we have DUnit....</h4> <p>Unit Testing in Delphi is not new, the DUnit framework has been around for many years. So why create a new unit test framework? My motivation for creating a new framework was partially frustration with the confusion over DUnit, DUnit2 and who was maintaining DUnit? Sourceforge and subversion makes it very difficult to make contributions to projects. I thought about forking DUnit and putting it up on GitHub or Bitbucket,  but my feeling was that would make it even more confusing. Add to that, DUnit was created a long time ago, it's full of IfDefs for CLR, LINUX etc, so it's probably best left alone (I don't have Kylix or Delphi.NET installed). </p> <p>DUnitX supports Delphi 2010 or later. If you need support for older versions of Delphi, I'm afraid you will have to stick with DUnit, DUnitX makes use of Generics, Attributes and Anonymous methods. D2010 support has some limitations due to compiler bugs. </p> <h4>DUnitX - How does it differ from DUnit?</h4> <p>DUnitX is modeled after NUnit, with some ideas borrowed from xUnit as well. The terminology is different to DUnit, so it's worth looking at the differences below :</p> <table class="telerik-reTable-4" style="width: 100%;"> <tbody> <tr class="telerik-reTableHeaderRow-4"> <td class="telerik-reTableHeaderEvenCol-4" style="width: 33%;">Feature</td> <td class="telerik-reTableHeaderOddCol-4" style="width: 33%;"> DUnit</td> <td class="telerik-reTableHeaderEvenCol-4" style="width: 34%;">DUnitX</td> </tr> <tr class="telerik-reTableOddRow-4"> <td class="telerik-reTableEvenCol-4">Base Test Class</td> <td class="telerik-reTableOddCol-4">TTestCase</td> <td class="telerik-reTableEvenCol-4">None</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Test Method</td> <td class="telerik-reTableOddCol-4">Published</td> <td class="telerik-reTableEvenCol-4">Published or decorated with [Test]</td> </tr> <tr class="telerik-reTableOddRow-4"> <td class="telerik-reTableEvenCol-4">Fixture Setup Method</td> <td class="telerik-reTableOddCol-4">NA</td> <td class="telerik-reTableEvenCol-4">Decorated with [SetupFixture] or constructor*</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Test Setup Method</td> <td class="telerik-reTableOddCol-4">Override Setup from base class</td> <td class="telerik-reTableEvenCol-4">Decorated with [Setup]</td> </tr> <tr class="telerik-reTableOddRow-4"> <td class="telerik-reTableEvenCol-4">Test TearDown Method</td> <td class="telerik-reTableOddCol-4">Override Teardown from base class</td> <td class="telerik-reTableEvenCol-4">Decorated with [TearDown]</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Namespaces</td> <td class="telerik-reTableOddCol-4">Through registration parameter (string)</td> <td class="telerik-reTableEvenCol-4">Unit Names (periods delimit namespaces).</td> </tr> <tr class="telerik-reTableOddRow-4"> <td class="telerik-reTableEvenCol-4">Data driven Tests</td> <td class="telerik-reTableOddCol-4">NA</td> <td class="telerik-reTableEvenCol-4">Decorated with [TestCase(parameters)]</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Asserts</td> <td class="telerik-reTableOddCol-4">Check(X)</td> <td class="telerik-reTableEvenCol-4">Assert class</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Asserts on Containers(IEnumerable<t>)</t></td> <td class="telerik-reTableOddCol-4">Manual</td> <td class="telerik-reTableEvenCol-4">Assert.Contains*, Assert.DoesNotContain*, Assert.IsEmpty*</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Asserts using Regular Expressions</td> <td class="telerik-reTableOddCol-4">NA</td> <td class="telerik-reTableEvenCol-4">Assert.IsMatch (XE2 or later).</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Stack Trace support</td> <td class="telerik-reTableOddCol-4">Jcl</td> <td class="telerik-reTableEvenCol-4">Jcl, madExcept 3, madExcept 4, Eurekalog 7 **</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Memory Leak Checking</td> <td class="telerik-reTableOddCol-4">FastMM4</td> <td class="telerik-reTableEvenCol-4">FastMM4 (under construction) ** </td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">IoC Container</td> <td class="telerik-reTableOddCol-4">Use Spring or other</td> <td class="telerik-reTableEvenCol-4">Simple IoC container Built in.</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">Console Logging</td> <td class="telerik-reTableOddCol-4">Built in</td> <td class="telerik-reTableEvenCol-4">Built in (quiet or verbose modes).</td> </tr> <tr class="telerik-reTableEvenRow-4"> <td class="telerik-reTableEvenCol-4">XML Logging</td> <td class="telerik-reTableOddCol-4">Built in (own format)</td> <td class="telerik-reTableEvenCol-4">Built in - Outputs NUnit compatible xml.</td> </tr> </tbody> </table> <style id="telerik-reTable-4" type="text/css">.telerik-reTable-4 { border-collapse: collapse; border: solid 0px; font-family: Tahoma; } .telerik-reTable-4 tr.telerik-reTableHeaderRow-4 { border-width: 1.0pt 1.0pt 3.0pt 1.0pt; margin-top: 0in; margin-right: 0in; margin-bottom: 10.0pt; margin-left: 0in; line-height: 115%; font-size: 11.0pt; font-family: "Calibri" , "sans-serif"; width: 119.7pt; background: #4F81BD; padding: 0in 5.4pt 0in 5.4pt; color: #FFFFFF; } .telerik-reTable-4 td.telerik-reTableHeaderFirstCol-4 { padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableHeaderLastCol-4 { padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableHeaderOddCol-4 { padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableHeaderEvenCol-4 { padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 tr.telerik-reTableOddRow-4 { border-width: 1pt; color: #666666; font-size: 10pt; vertical-align: top; border-bottom-style: solid; border-bottom-color: #4F81BD; } .telerik-reTable-4 tr.telerik-reTableEvenRow-4 { color: #666666; font-size: 10pt; vertical-align: top; } .telerik-reTable-4 td.telerik-reTableFirstCol-4 { border-width: 1pt; border-color: #4F81BD; padding: 0in 5.4pt 0in 5.4pt; border-bottom-style: solid; border-left-style: solid; } .telerik-reTable-4 td.telerik-reTableLastCol-4 { border-width: 1pt; border-color: #4F81BD; border-bottom-style: solid; border-right-style: solid; padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableOddCol-4 { border-width: 1pt; border-color: #4F81BD; padding: 0in 5.4pt 0in 5.4pt; border-bottom-style: solid; } .telerik-reTable-4 td.telerik-reTableEvenCol-4 { border-width: 1pt; border-color: #4F81BD; padding: 0in 5.4pt 0in 5.4pt; border-bottom-style: solid; } .telerik-reTable-4 tr.telerik-reTableFooterRow-4 { color: #355C8C; background-color: #FFFFFF; font-size: 10pt; vertical-align: top; padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableFooterFirstCol-4 { border-width: 1pt; border-color: #4F81BD; border-bottom-style: solid; border-left-style: solid; padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableFooterLastCol-4 { border-width: 1pt; border-color: #4F81BD; border-bottom-style: solid; border-right-style: solid; padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableFooterOddCol-4 { border-width: 1pt; border-color: #4F81BD; border-bottom-style: solid; padding: 0in 5.4pt 0in 5.4pt; } .telerik-reTable-4 td.telerik-reTableFooterEvenCol-4 { border-width: 1pt; border-color: #4F81BD; border-bottom-style: solid; padding: 0in 5.4pt 0in 5.4pt; } </style> <p>* Not available in D2010 due to compiler bugs.<br /> ** Extensible,  simple api.</p> <p>DUnitX is Attribute driven. Any class can be a Test Fixture. The benefit this brings is to isolate internal architecture changes to the test framework from the tests that use the framework.  Logging, stack trace support and memory leak tracking support are extensible and replacable via the built in IoC container and simple interfaces.  The XML Logger for DUnitX outputs NUnit compatible XML, which means the xml can be imported by <a href="/finalbuilder">FinalBuilder</a> and <a href="/continua-ci">Continua CI</a> (which can display the results nicely).</p> <h4>So what does a DUnitX Test look like?</h4> <pre class="brush:delphi; toolbar:true;"> type uses DUnitX.TestFramework; type [TestFixture('Example','General Example Tests')] TExampleFixture = class public [SetupFixture] procedure IamAboutToRunSomeTests; [TeardownFixture] procedure OKIAmDoneThanks; [Setup] procedure SetupMyTestPlease; [TearDown] procedure TearDownMyTestPlease; [Test] procedure APublicTest; //Ignored Test will show as ignored in the log. [Test] [Ignore('Need to rewrite this test')] procedure IAmNotReadyToRunYet; [Test] [TestCase('Case 1','1,2')] [TestCase('Case 2','3,4')] [TestCase('Case 3','5,6')] procedure RunMeMoreThanOnce(param1 : integer; param2 : integer); [Test(false)] //Disabled Test. Will not show in the log, will not run procedure DontCallMe; published procedure IAmATest; end; </pre> <h4>Can I convert my DUnit Tests to DUnitX?</h4> <p>DUnitX has limited support for DUnit Tests. Conversion to DUnitX is relatively simple, DUnitX.DUnitCompatibility.pas has a TTestCase class with all the Check(X) methods on it (marked as deprecated, which delegate to the new Assert methods. In the uses clause, replace </p> <pre class="brush:delphi; toolbar:false;gutter:false; "> TestFramework</pre> <p>with </p> <pre class="brush:delphi; toolbar:false;gutter:false; "> DUnitX.TestFramework,DUnitX.DUnitCompatibility;</pre> <p>and change the registration to </p> <pre class="brush:delphi; toolbar:false;gutter:false;"> TDUnitX.RegisterTestFixture(TYourTestClass)</pre> <h4>Enabling Stack Trace Support<br />  </h4> <p>DUnitX has a DUnitX.StackTrace.inc file, uncomment the define for the provider you want to use. If you want to provide your own stack trace provider, then implement IStacktraceProvider and register it with </p> <pre class="brush:delphi; toolbar:false;gutter:false;"> TDUnitXIoC.DefaultContainer.RegisterType<istacktraceprovider,tyourprovider>; </istacktraceprovider,tyourprovider></pre> <p>Enabling Memory Leak Tracking</p> <p>This will be similar to the Stack Trace support when fully implemented. </p> <h4>Where do I get it? Can I contribute?</h4> <p><a href="https://github.com/VSoftTechnologies/DUnitX">DUnitX is hosted on GitHub</a>, and we welcome contributions (fork it, make the change and submit a pull request). The project is actively being worked on, with several contributors already. My hope is that someone will help out with MacOS support (we have tried to limit reliance on windows wheree possible).  DUnitX is also the test framework being use for the <a href="https://bitbucket.org/NickHodges/delphi-unit-tests">Delphi Unit Test Project (DUT)</a> started by Nick Hodges. </p> 697