Sometimes it’s hard to just say what you mean.
When writing automated system and integration tests, I occasionally find it tricky to express the precise expected value of test output. For example, I expect the timestamp on a database record to be within a second or two of “now,” but I can’t predict the exact millisecond it will be created by the web server I’m posting to. And sometimes a floating point result is off by a millionth or trillionth, because the numeric representations used in my tests are not necessarily the binary equivalent of what the code under test is using.
In the cases where I know the differences are circumstantial (and irrelevant to my test), I find myself wanting to write…
…and have it “just work.” I know what I mean, and so (likely) will anyone reading the test after me.
I know, I know… most test tools already provide some sort of tolerance-based assertion helpers or value matchers. (MiniTest has #assert_in_delta and RSpec has #be_close.) But they can damage readability and obscure intent, and sometimes they simply don’t fit the situation.
In the following example, I want to compare a Hash of attribute values for my space ship after ticking the game engine one quantum into the future:
This looks simple enough, but if I happen to be employing a physics simulator, my results may be off by a very minuscule amount, and I won’t be able to predict that difference. Worse, I can’t employ
#be_close because it won’t know what I mean by feeding it a Hash. Better:
Here’s the code that enables
#ish for Numeric values:
Here’s how you can do something similar with Time:
Note that it’s easy to provide for optional tolerance tuning (eg, to be a little looser, you could do something like
I generally call this function
#ish because it’s short, unlikely to collide with any other method names, and it’s fun to say “five-ish” than “assert in delta five comma oh-point-oh-one. It also implies a semantic dependence on the object we’re invoking the
#ish method on. It’s easy to deduce that
9.ish could mean something quite different from
I tend to implement the
#ish helper on a per-project basis, in order to localize my assumptions for reasonable default tolerances. Is plus-or-minus 5 seconds a reasonable tolerance for all people on all projects? No. But for any given project, it’s usually easy to just pick something and move on.
If monkey-patching Ruby’s built-in value types gives you heartburn, you might instead implement a helper function
roughly that accepts any object and determines, internally, how best to inject tolerance into equality tests.
Anyway, I just wanted to share another of our sneaky little testing tricks. Anybody interested in seeing this become a Rubygem? Better yet, have you got any of your own tricks you’d like to share with us?