I thought I could do a better effort to describe why DUnit is so extraordinary,
for a native language, especially for those unfamiliar with xUnit frameworks
or TDD. So here it goes:


*What is a unit test*

Unit tests, ideally, test a specific functionality in isolation, so that
if the test fails, you can assume that it's because the functionality
under test, and only that functionality, is broken.

Testing a program or system is complex. This is not the only kind of test
that should be done on a system. But it's one kind of test that lets you
split the testing effort into less complex units, especially if your
system is in fact assembled from smaller units.

Unit testing frameworks provide mechanisms to isolate and setup the
program state after one test, so that when the next test begins,
you get a clean slate.
They also give you mechanisms to make it easier for you to reuse the
effort you put into setting the test environment for a test, because
in fact many tests will have the same setup. Those tests are then
made to be part of the same "TestCase".
A "TestCase" contains tests, and contains a setup method common
to all of them.



*What is an xUnit framework?*

It's a framework that allows you to write unit tests in a fashion that
goes well with TestDrivenDevelopment and TestFirstDesign.

It's also a kind of "meme" that got spread to most programming languages.
We owe the existance of this powerful meme to Kent Beck, and his SUnit 
framework.
Arguably, the most popular incarnation nowadays is JUnit
(https://en.wikipedia.org/wiki/JUnit).

This "meme" consists of at least the following elements:

   * TestCases as classes (with setup() and teardown() methods).

   * Tests as methods of TestCases.

   * Convenience assert functions.

   * A green progress bar that turns to red when one of the Tests fails.

   * Pluggable console or GUI "test runners".

So, the user writes a class... that contains methods... which in turn contain 
asserts.
The user then compiles the program or library and starts the "test runner", 
which
then runs all the tests. Some runners display the list of available TestCases, 
and
allow to pick which ones to run (or re-run):

    NUNIT: http://nunit.org/docs/2.5/img/gui-screenshot.jpg

    NUNIT CONSOLE: http://nunit.org/docs/2.6/img/console-mock.jpg

    JUNIT: http://www.eclipse.org/screenshots/images/JavaPerspective-WinXP.png
    (bottom part of the IDE screenshot)

    CPPUNIT: 
http://sourceforge.net/apps/mediawiki/cppunit/nfs/project/c/cp/cppunit/8/81/Mfctestrunner.png

    CPPUNIT CONSOLE: 
http://sourceforge.net/apps/mediawiki/cppunit/index.php?title=File:Cursetr_RunningTests.png

Note the presence of the tipical green progress bar (which turns red when
one test fails, giving fast feedback to the user, and acting as a reinforcement
when green. It only keeps the green when *all* tests pass (no failing asserts).


*But how does the 'test runner' know which methods are tests to run?*

Each programming language has its own best way of marking a class as a TestCase,
and of marking a method in it as a Test to be run.

   With JUnit3, one had to inherit the class from a specific base class.
A test runner would have to use Reflection to get the list of all
classes that were TestCases to present them to the user. And then use
reflection to search for and list the methods whose names start with "test".

   With JUnit4, the user now has to only mark methods with the @Test attribute.
Through reflection, test runners can find all the classes which contain
methods marked with the @Test attribute, and then call those methods.
This still has some overhead, (hopefully not on the order of methods of the 
program)
and we are talking about late-binding too.

   With C++, since there is absolutely no real reflection capability (like
getting all the names of the methods of a TestCase class). So one has to 
manually
register the test method using macros. So, each time that you add a
new test method, you have to type its name at least three times. The result
is not beautiful: cppunit.sourceforge.net/doc/lastest/money_example.html
(but it's efficient though).


*How does DUnit do it?*

In DUnit classes are marked as a TestCase by declaring a "mixin TestMixin"
once in any part of their body. You don't need to type the name of a method
more than once, or the name of the class more than once. The mixin gets
it all from the context in which it was instantiated (thanks to "typeof(this)").
It creates a static constructor for the class, with:

    . a compile time generated immutable list of the names of the Test methods
      of the class whose names begin with "test" and can be called without
      arguments.
      (thanks to __traits(allMembers,), __traits(compiles,) and recursive
      template declarations).

    . a compile time generated function with a switch-case statement that takes
      the name of a Test and calls it.
      (thanks to __traits(allMembers) and __traits(compiles,) and recursive
      template declarations).

    . and a compile time generated function that can instantiate an object of
      that class. (thanks to string mixins).

When the program starts, the static constructors of each of the TestCases
registers those constants and function pointers with the dunit module,
for the test runners to see.
You can of course enclose your TestCases with something like
    version(DUnit) {
        ...
    },
so that you can exclude them from compilation for a release version.

The end result is that "test runners" are very easy to write, and it is also
extremely fast to get the list of TestCase classes, and in turn the list
of the Tests methods and setUp, tearDown, setUpClass and teardDownClass methods.
Calling a test function is also a switch statement away, and there is a
switch-case function per TestCase, so the list of switch cases is small.
The Test method is called directly from the switch-case,
there is no late binding at that point, because the switch is statically
generated as a string mixin.


This provides test runners with all the flexibility, since the test runner
knows the tests by name, and may call them by name. This means
that a test runner can let the user select which tests to run.

All of this also means that unit test writers only have to type the bare
minimum, unlike in C++, to define a test:

-------------------------
Minimum example:
-------------------------
import dunit;

class ATestCase {
   mixin TestMixin;

   void testtOne() {
       assert(true);
   }
}

-------------------------
Example:
-------------------------
import dunit;

class ATestCase {
   mixin TestMixin;

   void setUpClass() {
   }
   void tearDownClass() {
   }
   void setUp() {
   }
   void tearDown() {
   }

   void testtOne() {
       assert(true);
   }
   void test2() {
       assertEquals("str", "s"~"t"~"r);
   }
}


--jm

Reply via email to