DUnitAssertions: Are compound constraints worth it?
I’ve written enough of DUnitAssertions that you can do Expect.That(Foo, Tis.EqualTo(Bar)); You can also use and and or to combine multiple Tis constraints, so you could write an assertion like:
Expect.That(Foo, Tis.GreaterThan(20) and Tis.LessThan(45));
This kind of and and or stuff seemed like a great idea. You could write your own TisBetween method, implement it in terms of existing constraints, and start using it: Expect.That(Foo, TisBetween(20, 45)); And you would just magically get a meaningful failure message like:
Expected: > 20 and < 45 but was: 99
Well, it did sound cool at one point. But the more I work on it, the more problems I run into.
-
To start with, “between”, my prime example, is a bad example. “Between” is just a bad test. It’s sloppy, kind of like an expected-exception check that passes even if a descendant exception is thrown. You should know what the value is supposed to be, and that’s what your test should check for. Using something like “between” means you could introduce an off-by-one error in a botched refactoring, and your tests wouldn’t tell you about it. (I even hesitated before I added
Tis.GreaterThanand friends, for this very reason. Why was I so gung-ho aboutandandor?) -
For that matter, giving people the ability to put
ands andors andnots into their tests is just begging the less-unit-test-savvy among them to rewrite their program logic in their tests. That kind of duplication is the last thing the programming world needs. This alone isn’t an argument against the feature, but it’s a point to consider. -
What should the failure message look like when you have a complicated expression with
ands andors andnots? How do you visually represent that in the failure message? You’d need to start adding parentheses to show precedence, and that doesn’t fit with the one-condition-per-line layout. Things would get messy in a hurry. -
What happens when you get a tug-of-war over what goes into the “but was:” line?
Tis.EqualTocompares the value, butTis.SameInstanceAscompares the pointer, andTis.InstanceOfcompares the class. If you combine those withandoror, which one shows after “but was:”? Do there need to be multiple paragraphs in the output, each with its own “but was:” line? How does that interact with multipleands andors andnots and parentheses? -
Last but not least,
ands andors will complicate NUnit-style string comparisons. If you haven’t seen this, when you useAssert.AreEqualon two strings, NUnit will find the first index where the strings differ, and will show you something like twenty characters of context on either side, with an arrow pointing to the first character that’s different. It’s really sweet, and I want it for DUnitAssertions… but how does it coexist with, say, the ability to useorto compare against multiple different strings? Which one controls the arrow?
I have support for Tis.Not, so you can do things like Tis.Not.Null, and you could use Tis.Not.GreaterThan in those cases where it would increase readability. But beyond that, I’m debating how valuable operator overloads are for constraints, or whether I should just rip them out.
So here’s my question to y’all: If you could combine constraints with and, or, and not, would you? What kinds of things might you use it for? (In other words, can you convince me this feature is worth keeping?)
April 19th, 2007 at 4:26 pm
Seems like AND and BETWEEN are both mixing two tests. If you split it into two tests, anything that failed the compound test would also fail one of the two tests.
OR seems harder to replace, but
a) might be able to work out something with NOT
b) I can’t yet think of a place i’d *need* it.
Conversely, if you need OR, then you probably need AND also.
" A OR (B AND C)"
If you can’t split the OR, then you can’t split the AND either.
April 19th, 2007 at 6:21 pm
That’s true, anything with "between" or "and" could be two separate tests. At one point it seemed like it would be nicer if you could write a single Expect.That(…) call with a custom-rolled constraint, rather than having to make two Expect.That(…) calls (or making a custom assertion message that calls Expect.That(…) twice). But I’m having trouble nailing down an example of where it would gain you much. "Between" is the best I can come up with, and it’s not that compelling. (Heck, if "between" is that useful, I could just build Tis.Between into the framework.)
I still haven’t come up with an example where "or" would be useful; it just seemed like a good idea at the time. Less so, the more time I spend on the idea.