Currently unittests may lie at top level or inside structs, classes, and templates. One great outcome (not attainable by library-based approaches such as gtest for C++) is that a parameterized class/struct/template will instantiate each unittest for each instantiation, leading to excellent testability of generic code.
Currently parameterized functions cannot benefit of this - there's no way to instantiate and fire a unittest for each instantiation of a function template. We should add this to the core language.
Proposal is to allow a unittest before the body of a function, like this:
void fun(T)(T arg)
{
... stuff ...
}
becomes
void fun(T)(T arg)
unittest
{
... unittest for each T instantiated ...
}
body
{
... stuff ...
}
The unittest has access to T but not arg. In functions with in/out contracts, unittest may appear only before them.
Comment #1 by timon.gehr — 2014-08-14T18:00:47Z
(In reply to Andrei Alexandrescu from comment #0)
> there's no way to instantiate and fire a unittest for each instantiation of a function template.
This is actually not true (also pointed out by Meta on the forum):
template fun(T){
void fun(T arg){
... stuff ...
}
unittest{
... unittest for each T instantiated ...
}
}
I.e. this enhancement suggests a shorthand syntax.
Comment #2 by bugzilla — 2014-08-14T20:14:20Z
(In reply to timon.gehr from comment #1)
> I.e. this enhancement suggests a shorthand syntax.
And it suggests a "lowering" method of implementing it that is just a rewrite.
Comment #3 by andrei — 2014-08-14T20:46:44Z
Honest I did think of the template/eponymous trick but was too lazy to open it for discussion... there would be issues of overloading etc. left to solve with that one.
Comment #4 by timon.gehr — 2014-08-14T20:48:34Z
(In reply to Andrei Alexandrescu from comment #3)
> Honest I did think of the template/eponymous trick but was too lazy to open
> it for discussion... there would be issues of overloading etc. left to solve
> with that one.
What are those issues?
Comment #5 by andrei — 2014-08-14T20:57:06Z
(In reply to timon.gehr from comment #4)
> (In reply to Andrei Alexandrescu from comment #3)
> > Honest I did think of the template/eponymous trick but was too lazy to open
> > it for discussion... there would be issues of overloading etc. left to solve
> > with that one.
>
> What are those issues?
I'm thinking of (a) putting two or more overloads inside the same template - then the unittest can't distinguish which is being instantiated (arguably that could be considered a mistaken us); (b) would overloading a template with a generic function just work?
template fun(T) { void fun(T, int); }
void fun(T)(T, double);
Tried this, does work. I wonder if it works for all overloading cases.
Comment #6 by timon.gehr — 2014-08-14T21:06:58Z
(In reply to Andrei Alexandrescu from comment #5)
> (In reply to timon.gehr from comment #4)
> > (In reply to Andrei Alexandrescu from comment #3)
> > > Honest I did think of the template/eponymous trick but was too lazy to open
> > > it for discussion... there would be issues of overloading etc. left to solve
> > > with that one.
> >
> > What are those issues?
>
> I'm thinking of (a) putting two or more overloads inside the same template -
> then the unittest can't distinguish which is being instantiated (arguably
> that could be considered a mistaken us);
(The new syntax does not allow this.)
> (b) would overloading a template
> with a generic function just work?
>
> template fun(T) { void fun(T, int); }
> void fun(T)(T, double);
>
> Tried this, does work. I wonder if it works for all overloading cases.
There is no case where e.g.
void foo(T)(T arg){ ... }
behaves differently from e.g.
template foo(T){ void foo(T arg){ ... } }
The former is lowered to the latter (DMD does this in the parser AFAIK).
The way to implement the new syntax would be to simply lower it to something of the form of the example in comment #1.
Comment #7 by schveiguy — 2014-09-16T01:20:31Z
Using the template instantiation to cover all unit tests is both a boon and a curse.
I use it in dcollections to great effect, and have found several D/phobos bugs just with that use.
However, often you do NOT want to instantiate unit tests for ALL invocations.
Consider this:
T max(T)(T x, T y)
A test for max might look like:
unittest
{
T t1 = 0, t2 = 1, t3 = 2;
assert(max(t1, t2) == t2);
assert(max(t2, t1) == 1);
assert(max(t1, t3) == t3);
assert(max(t3, t2) == 2);
// etc...
}
But what if T is string?
Now, you have to create a different unit test because the literals are different. Unit tests require both input and results, and the input isn't always easy to determine for any generic type.
Comment #8 by dfj1esp02 — 2014-09-16T09:22:45Z
Related: issue 13454
Comment #9 by andrei — 2014-12-18T00:38:12Z
I just realized this can be realized with relative ease as follows:
void fun(T)(T arg)
{
version(unittest)
{
... testing ...
}
... stuff ...
}
To avoid running unittests every time:
void fun(T)(T arg)
{
version(unittest) for (static bool tested; !tested; tested = true)
{
... testing stuff ...
}
... stuff ...
}
Comment #10 by schveiguy — 2014-12-18T14:17:30Z
(In reply to Andrei Alexandrescu from comment #9)
> I just realized this can be realized with relative ease as follows:
>
> void fun(T)(T arg)
> {
> version(unittest)
> {
> ... testing ...
> }
> ... stuff ...
> }
That won't run during unit tests, though. But I think Timon's point covers the request.