On Friday, 26 January 2018 at 13:05:26 UTC, Jonathan M Davis
wrote:
On Friday, January 26, 2018 12:30:03 Oleksii Skidan via
Digitalmars-d-learn wrote:
On Friday, 26 January 2018 at 11:32:42 UTC, Mike Parker wrote:
> On Friday, 26 January 2018 at 11:18:21 UTC, Oleksii Skidan
>
> wrote:
>> [...]
>
> Token strings are intended for this and editors *should*
> highlight them (don't know if any currently do):
>
> https://dlang.org/spec/lex.html#token_strings
Seems like I have to add some context into this conversation:
I'm writing a poor man's testing framework, since it's the
best and easiest way to learn D ;-)
I'm trying to achieve something similar to
`Catch2``REQUIRE`macro. To be honest, I did not know about
toking strings until today, and I don't know D much. Here's
what I came up with so far:
```d
string require(string expr)(string file = __FILE__, int line =
__LINE__)
{
import std.array, std.conv;
return q{
if (!($expr)) {
import std.stdio;
writeln("Test failed @", `$file`, ":", $line,
"\n",
" Expected: `", `$expr`, "` to be
`true`.\n");
}
}.replace("$expr", expr)
.replace("$file", file)
.replace("$line", to!string(line));
}
```
That code snippet uses token strings to compose an if
statement that basically checks whether the given condition
holds. That looks okay-ish to me, the usage of that function
is not pretty though:
```d
unittest
{
mixin(require!q{false}); // This test will fail.
}
```
It would be awesome if I could write something like the this
instead:
```d
unittest
{
require!q{false};
}
```
At first glance it seems like I could have moved the `mixin`
statement into the `require` function itself, but that would
not really work. Consider the following snippet:
```d
unittest
{
float value = 3f;
require!q{value == 3f}; // This line won't compile.
}
```
That code won't even compile, since `value` exists in
`unittest` scope, which is not visible to the `require`
function.
Why are you using strings for any of this? Printing out the
expression is kind of pointless. If you have the file and line
number (which an AssertError will give you), then you know
where the failure is, and you can see the expression. All of
this extra machinery is just going to increase your compile
times for no benefit. So, what you're doing here is objectively
worse than just using assertions.
There might be some value if you had something like
assertEqual(lhs, rhs);
and then on failure, you printed the values that were being
compared, since that's not necessarily information that's in
the code, but the expressions themselves _are_ already in the
code, so printing them out doesn't help any.
But even if you have helper functions that take the values
separately so that they can be printed, in my experience, the
extra template instantiations required to use helper functions
like that everywhere in unit tests increases the compilation
times (and memory required) enough that it's not worth it,
especially when you consider that once the tests are passing,
all of that extra machinery does you no good whatsoever.
Ultimately, it just costs less to temporarily make an
adjustment to the test and rerun it if you need more
information.
If you don't think that simply using assertions for unit tests
is good enough, then I'd suggest that you look at
https://code.dlang.org/packages/unit-threaded
- Jonathan M Davis
I've just realized that I can actually make the test code more
pleasant if I use string concatenation. For example, this test:
```
unittest
{
import testing;
{
Particle a = Particle(0.9, 0);
Particle b = Particle(2.1, 0);
Constraint c = Constraint(a, b);
c.distance = 1f;
c.solve();
mixin(requireEq!q{1f, a.x});
mixin(requireEq!q{0f, a.y});
mixin(requireEq!q{2f, b.x});
mixin(requireEq!q{0f, b.y});
}
}
```
could be written as:
```
unittest
{
import testing;
{
Particle a = Particle(0.9, 0);
Particle b = Particle(2.1, 0);
Constraint c = Constraint(a, b);
c.distance = 1f;
c.solve();
mixin(
requireEq!q{1f, a.x} ~
requireEq!q{0f, a.y} ~
requireEq!q{2f, b.x} ~
requireEq!q{0f, b.y}
);
}
}
```
That code looks a little bit unusual, but I guess I can get used
to it. Seems like I can write a test scenario per mixin.