DUnitAssertions: Epsilons
You never want to do exact comparisons on floating-point numbers. They’re finite precision — and 0.1 is a nonrepeating fraction in binary. So 1.1 plus 0.1 does not necessarily equal 1.2.
So float comparisons should always have an epsilon, or fudge factor — something that says “if the difference is only 0.000001, that’s close enough”. That’s why Delphi has the SameValue function (which provides its own reasonable epsilon if you don’t specify your own), and why DUnit has overloads for CheckEquals that take an epsilon.
NUnitLite apparently doesn’t do epsilons, though, so I’m on my own for coming up with a meaningful syntax as I write DUnitAssertions. Here are a few of my brainstorms:
Expect.That(1.1 + 0.1, Tis.EqualTo(1.2, 1E-6)); Expect.That(1.1 + 0.1, Tis.EqualTo(1.2), 1E-6); Expect.That(1.1 + 0.1, Tis.EqualTo(1.2), Tis.Within(1E-6)); Expect.That(1.1 + 0.1, Tis.EqualTo(1.2), To.Within(1E-6)); Expect.That(1.1 + 0.1, Tis.EqualTo(1.2), Plus.Minus(1E-6)); Expect.That(1.1 + 0.1, Tis.EqualTo(1.2).ToWithin(1E-6));
I like the third one (To.Within) the best, except that it won’t compile (to is a reserved word in Delphi). Obviously I could make it &To but that’s kludgy; a Delphi test framework should compile in Delphi without escaping the method names. Jennie suggested “plus or minus”, which is what prompted Plus.Minus. The last one might work too, although I’d have to fiddle with it.
I’m also not sure how discoverable any of these are. Ideally I’d want something where anyone could sit down with nothing more than the test framework, Intellisense, and one example (Expect.That(2 + 2, Is.EqualTo(4), 'Something is wrong');), and be able to figure the rest out. I already know that’s an impossible goal in Delphi/Win32, because of the issues with enums, but it’s still a decent aim.
Any preferences among these examples? Suggestions?
April 17th, 2007 at 1:29 pm
That depends, of course.
What are the other methods of Tis?
What does EqualTo return?
At first glance I would expect that EqualTo should take the epsilon. Your first example.
But that depends on what EqualTo does.
April 17th, 2007 at 7:29 pm
Tis.EqualTo (and its siblings Tis.GreaterThan, Tis.Not.EqualTo, etc.) returns a constraint, which is a hybrid type (essentially an interface reference).
Expect.That then calls methods on the constraint to decide whether the constraint matches the actual value, and if not, to generate the failure message ("Expected… but was…") and throw an exception.
Currently I’m also leaning toward the first example, because it’s easiest from an implementation standpoint right now. But if I can think of something that’s more readable, I’ll work it in — after all, you read test code a lot more often than you write it.
April 18th, 2007 at 11:48 pm
I’d go for the first or the last one.
But that’s just a feeling (and not based on, for example, years of experience:)