Thread overview
unittests and templates
May 12, 2010
bearophile
May 11, 2010
A cool thing I learned on the way to d2.

unittests can be templatized just like the classes/structs they reside in.  When enhancing dcollections with lots of detailed unit tests, I was doing things like this:

class ArrayList(V)
{
   V take() {...}
   unittest
   {
      auto al = new ArrayList!uint;
      al.add([0u, 1, 2, 3, 4]);
      assert(al.take() == 4);
      assert(al.length == 4);
   }
}

But I found that the unittests weren't compiling -- at all!  What I realized is that unittests are part of the instantiation!

At first, I was peeved, I wanted to simply test the "take" function not caring what type I used to instantiate the class with.  In order to do that, I have to move all the unit tests outside the class declaration -- not good for maintainability.

But I came to realize -- there is great power in that feature.  For example, with a little change to my unit test:

unittest
{
   auto al = new ArrayList;
   al.add([cast(V)0, 1, 2, 3, 4]);
   assert(al.take() == 4);
   assert(al.length == 4);
}

I can create a global unit test that looks like this:

unittest
{
  ArrayList!uint al1;
  ArrayList!int al2;
  ArrayList!ulong al3;
  ArrayList!long al4;
}

and now my unit tests cover 4 instantiations.  With a single line, I can extend my unittest coverage to another type!

Of course, the issue is, how do I write a truly generic unit test?  For example, that 'take' unit test fails for ArrayList!string.  If I wanted to compile a project that happened to include the string instantiation with unit tests turned on, it fails to compile.

The ugly solution is to use static if like so:

static if(isIntegral!V)
{
   unittest
   {
   }
}

But that is, well, ugly.  I thought of proposing something like:

unittest if(isIntegral!V)
{
}

which looks better, but still is a bit verbose.  This might be the cleanest solution.  Any suggestions?

I still would like a way to do a unittest inside a template that is technically not part of the instantiation.  This allows you to unit test a function with a specific instantiation when it's not easy to build a generic unit test *and* you want the unit test close to the function declaration.  Although it's bloaty, you could just write your unittests like I did originally, specifying the full type in the unit test, but you then have to remember to create an external unit test to do the instantiation.

I hate to suggest static unittest :P  Any other ideas?

-Steve
May 11, 2010
On Tue, 11 May 2010 16:06:31 -0400, Steven Schveighoffer <schveiguy@yahoo.com> wrote:


> The ugly solution is to use static if like so:
>
> static if(isIntegral!V)
> {
>     unittest
>     {
>     }
> }
>
> But that is, well, ugly.

I found a less ugly solution -- remove the braces:

static if(isIntegral!V) unittest
{
   ...
}

This is probably fine for most purposes.

I'd still like the non-generic unit test near the function declaration.

-Steve
May 12, 2010
Steven Schveighoffer:

> class ArrayList(V)
> {
>     V take() {...}
>     unittest
>     {
>        auto al = new ArrayList!uint;
> ...


That unit test is the test of just take(). To denote it in my code I add a comment:

class ArrayList(V) {
    V take() {...}
    unittest { // Test of take()
...


Having an unit test block focused on just a method is good, because you can move around or remove or add methods with no problems, keeping them close to their unittests.

This is why in my recent unittest enhancement proposals (not in bugzilla yet) I have vaguely asked for a way to link a unittest with something else like a module, function, method, etc. But I have found no good and simple way to do this. Even adding a name to the unit test doesn't solve this problem because the compiler will ignore the unittest names:


class ArrayList(V) {
    V take() {...}
    unittest take_test {
...


>But I found that the unittests weren't compiling -- at all!<

This is a common enough problem with unit tests in D. So at the end of my modules I add something like:

unittest { puts(__FILE__ ~ " unittest performed."); }

But this is not enough to catch your problem with unittests in templated classes.


>I still would like a way to do a unittest inside a template that is technically not part of the instantiation.  This allows you to unit test a function with a specific instantiation when it's not easy to build a generic unit test *and* you want the unit test close to the function declaration.<

This is not easy to solve with the way the whole D template system is designed.


>I hate to suggest static unittest :P<

Even static names get templated:

void foo(T)(T x) {
    static T y;
}
void main() {}


that's why (for a different purpose, that is to define a constant shared by all instances of a template) time ago I have suggested a "static static" for templates, but it was not appreciated:
http://www.digitalmars.com/webnews/newsgroups.php?art_group=digitalmars.D&article_id=100499

I don't have other ideas for you.

Bye,
bearophile
May 13, 2010
On Wed, 12 May 2010 18:54:20 -0400, bearophile <bearophileHUGS@lycos.com> wrote:

> Steven Schveighoffer:
>
>> class ArrayList(V)
>> {
>>     V take() {...}
>>     unittest
>>     {
>>        auto al = new ArrayList!uint;
>> ...
>
>
> That unit test is the test of just take().

[snip]

> I don't have other ideas for you.

Right now, I'm doing fine with the static if for conditionally having unit tests.  I feel less strongly about having unit tests which test a function regardless of implementation than I originally did.  In order to unittest a function, you will inevitably end up instantiating with certain types, you can just use a static if to only build the unit tests when those types are present, or you could do it the same way I originally did, and have the unit tests run more than once.

Of course, it is odd to have to do this at the end of the class declaration:

unittest
{
   ArrayList!int al1;
   ArrayList!uint al2;
   ...
}

But there's no other way to do it.  If D could implicitly do this, I would like it, but it's not that big of a deal.  In fact, you could probably write a CTFE function that takes a list of types and instantiates them all in order to make sure unit tests run.

This post was more of a "hey, look what's cool about unit tests and templates!" than a complaint :)

-Steve