From: Matthew Smillie Date: 2006-07-07T07:47:56+09:00 Subject: Re: Float#==. Legacy? On Jul 6, 2006, at 21:49, Jacob Fugal wrote: > On 7/6/06, Matthew Smillie wrote: >> Another code smell is an overuse of literals and constants, and this >> is equally the case for testing code as for production code. This >> seems to me to be where the error really lies: you're using a >> constant in the testing code where you should be using a computed >> value based on the input. >> >> Knowing (as we do), that floating point calculations are not 100% >> accurate, a more reliable approach would be to make the test results >> using a parallel calculation to the code being tested, not simply a >> constant. > > While I agree (mostly) with the rest of your post, I think this is an > oversimplification of the problem, and produces a "solution" more > painful than the "problem" iteslf. > > You are right, that choosing the wrong delta for assert_in_delta can > be just as problematic as using Float#==. But I don't think that > throwing away assert_in_delta and using assert_equal with a parallel > calculation is the answer. In the trivial example you gave, it's fine, > since you've got one operation. But what if the method under test > performs many operations, due to complicated business rules? > (Hopefully those rules are factored out into their own tested methods, > but that doesn't stop them from being part of the behavior of that > method). Should we duplicate the entire process in the test? That > seems wasteful and error prone. I'll stick with assert_in_delta and an > expected value. I didn't (and wouldn't) suggest abandoning assert_in_delta. What I was discussing was the idea to push that functionality (which is specific to certain situations) into the general Float#== method, where, for a number of reasons, it's inappropriate. As for the example of "what if the method uses other really complicated methods?" though, aren't those other methods unit tested, likely on the same general input domain? If so, and they pass to your satisfaction, then why not simply use them in the calculation of the test value for the encompassing method - errors which arise from the subsidiary methods should be caught in the unit tests for those methods, so there's no loss of generality in the testing. To extend the trivial example from before: def foo(a, b) # arbitrary floating point math a * (some_method(a) + other_method(a, b)) end # test setup - these should all be values for which # you're confident the implementation is adequate. a = 22.0 b = SomeCompany.new("test") c = some_method(a) d = other_method(a, b) # replicate the fundamental logic of the method being tested here. calc_result = a * (c + d) The test, after all, is on the logic of the method being tested, not on the behaviour of all associated methods - those have their own tests to validate them. I don't know if this is any more error prone than using an expected value, since you have to make the complicated calculations *somewhere* in order to come up with the expected value in the first case. Making them in the tests themselves definitely has the advantage of self-documentation, and self-adjustment if the subsidiary methods are changed (such changes, presumably, tested in their own unit tests). As for more wasteful, it is (like so many other things) a trade-off. What if the variation you're seeing isn't a floating point error, but something incorrect in your logic somewhere? Say, a rounding error in a database field, or a problem with serialisation to/from SOAP. What's the risk of that? What's the impact? If you're only concerned about accuracy within some delta, then you only need to test to that delta. It's just that sometimes that delta might be 0. So, there are lots of times where assert_in_delta is the best thing to do (ensuring two different methods or computation agree to some degree, developer time, simplicity), but that doesn't meant that Float#== should take on that functionality. matthew smillie.