Difference between revisions of "Unittest"

From D Wiki
Jump to: navigation, search
(Created page with "==Usage== D has a simple but very useful construct, the '''unittest''' block. For example: <syntaxhighlight lang=D> real complicatedComputation(real[] args) { real resul...")
 
(Placement)
Line 40: Line 40:
 
==Placement==
 
==Placement==
  
The important question is, ''where'' to put these unittest blocks?
+
There are several considerations of where to place unittest blocks. Some are purely according to taste, some have pragmatic effects.
  
 
===At the end of the file===
 
===At the end of the file===
Line 48: Line 48:
 
===Following the code being tested===
 
===Following the code being tested===
  
Another convention is to place unittests immediately after the function or class that it's testing. This has the advantage of ... TBD
+
Another convention is to place unittests immediately after the function or class that it's testing. This has the advantage of keeping logically-related unittests and code together, so that it's easy to see how much code coverage your unittests have.
 +
 
 +
===Unittests for templated structs and classes===
 +
 
 +
Some special idioms apply when writing unittests for templated structs and classes. For example, if we have a templated struct:
 +
 
 +
<syntaxhighlight lang=D>
 +
struct MyStruct(T)
 +
{
 +
    T data;
 +
    void processData()
 +
    {
 +
        /* do something with .data */
 +
    }
 +
    // A
 +
}
 +
// B
 +
</syntaxhighlight>
 +
 
 +
We can either put the unittest block for processData at the point marked A, or the point marked B.
 +
 
 +
* If we put the unittest block at point A, it will be instantiated along with the rest of the struct, for every type T that is used with MyStruct. In other words, the unittest will be run ''per instantiation'' of MyStruct.
 +
* If instead we put the unittest block at point B, outside the scope of MyStruct, then it will only run once.
 +
 
 +
A good way to decide between the two is to consider what's in the unittest block. If we're testing MyStruct's behaviour for specific values of the template parameter T, then we should put it at B. If we're testing something general that applies to every T, then we should put it at A. For example:
 +
 
 +
<syntaxhighlight lang=D>
 +
struct MyStruct(T)
 +
{
 +
    T data;
 +
    void processData()
 +
    {
 +
        /* do something with .data */
 +
    }
 +
    unittest // A
 +
    {
 +
        auto ms = MyStruct!T;  // N.B.: parametrized on whatever value of T was used
 +
                              // to instantiate MyStruct
 +
        ms.processData();
 +
        assert(isExpectedResult(ms.data));
 +
    }
 +
}
 +
unittest // B
 +
{
 +
    auto msi = MyStruct!int;  // N.B.: parametrized on specific type we want to test
 +
    msi.processData();
 +
    assert(isExpectedIntResult(msi.data));
 +
 
 +
    auto msr = MyStruct!real; // ditto
 +
    msr.processData();
 +
    assert(isExpectedRealResult(msr.data));
 +
}
 +
</syntaxhighlight>
 +
 
 +
The unittest marked A will be run multiple times, once per instantiation of MyStruct, whereas the unittest marked B will only be run once.

Revision as of 22:09, 14 December 2012

Usage

D has a simple but very useful construct, the unittest block. For example:

real complicatedComputation(real[] args)
{
    real result;
    result = /* perform some fancy computation here */;
    return result;
}

unittest
{
    // Test complicatedComputation by calling it with arguments that
    // produce a known result
    auto knownArguments = [
        1.0, 1.61803, 2.71828
    ];
    auto result = complicatedComputation(knownArguments);

    // Check that the result is what you expected
    enum expectedAnswer = 3.14159;
    enum tolerance = 1e-12;
    assert(abs(result - expectedAnswer) < tolerance);
}

Code in this block is not executed at runtime unless you run DMD with the -unittest command-line option. Programs compiled with the -unittest option will run all unittests in the program first, before the main() function is entered. This allows you to write unittests for your code within the source file itself, thus making it more conducive for encouraging programmers to write unittests.

Advantages

  • You don't have to implement your own unittesting framework, as DMD provides it for free.
  • You don't have to switch over to another unittesting system, and possibly write the testing code in a different language.
  • You can write unittests to check for corner cases while your code is still fresh in your mind, rather than later when you may have forgotten about these corner cases.
  • You can even write unittests first, and then write the code to implement the feature being tested until all unittests passed, after which you know that your code is ready to use.
  • If you make a code change that introduces a bug in previously-working code, your program will refuse to start until the failing unittest is addressed. This reduces regressions in your code.
  • It's so easy to write unittests in D that you should be ashamed to write code without any unittests!

Placement

There are several considerations of where to place unittest blocks. Some are purely according to taste, some have pragmatic effects.

At the end of the file

One way to avoid cluttering real code with long unittest blocks is to collect all unittests at the end of the source file. This has the advantage of keeping the actual code in one place.

Following the code being tested

Another convention is to place unittests immediately after the function or class that it's testing. This has the advantage of keeping logically-related unittests and code together, so that it's easy to see how much code coverage your unittests have.

Unittests for templated structs and classes

Some special idioms apply when writing unittests for templated structs and classes. For example, if we have a templated struct:

struct MyStruct(T)
{
    T data;
    void processData()
    {
        /* do something with .data */
    }
    // A
}
// B

We can either put the unittest block for processData at the point marked A, or the point marked B.

  • If we put the unittest block at point A, it will be instantiated along with the rest of the struct, for every type T that is used with MyStruct. In other words, the unittest will be run per instantiation of MyStruct.
  • If instead we put the unittest block at point B, outside the scope of MyStruct, then it will only run once.

A good way to decide between the two is to consider what's in the unittest block. If we're testing MyStruct's behaviour for specific values of the template parameter T, then we should put it at B. If we're testing something general that applies to every T, then we should put it at A. For example:

struct MyStruct(T)
{
    T data;
    void processData()
    {
        /* do something with .data */
    }
    unittest // A
    {
        auto ms = MyStruct!T;  // N.B.: parametrized on whatever value of T was used
                               // to instantiate MyStruct
        ms.processData();
        assert(isExpectedResult(ms.data));
    }
}
unittest // B
{
    auto msi = MyStruct!int;  // N.B.: parametrized on specific type we want to test
    msi.processData();
    assert(isExpectedIntResult(msi.data));

    auto msr = MyStruct!real; // ditto
    msr.processData();
    assert(isExpectedRealResult(msr.data));
}

The unittest marked A will be run multiple times, once per instantiation of MyStruct, whereas the unittest marked B will only be run once.