The Dev Blog

Putting Family Management on Rails!

Testing: DRYing the Asserting of Array Similarity

Posted by Guy Naor Tue, 13 Feb 2007 11:21:00 GMT

After one too many instances of:

ph = contacts(:john).phones.collect{|p| p.number }
assert_equal 3, ph.size
assert_equal []. ['12345', '56789', '67890'] - ph

I decided it's time to DRY up array similarity checking. Using assert_equal on arrays is a so-so solution, given that you don't always know if the order in the array is the same. Returning an array from a has_many collection has a non-deterministic order by default. So asserting that what we get from a collection is a bit painfull, and require the code above to make absolutely sure we got what we wanted.

To make my life easier and DRYer, I added the following function to the test/test_helper.rb file:

def assert_array_similarity(expected, actual, message=nil)
  full_message = build_message(message, "<?> expected but was\n<?>.\n", expected, actual)
  assert_block(full_message) { (expected.size ==  actual.size) && (expected - actual == []) }
end

And now testing for array similarity is simply:

assert_array_similarity ['12345', '56789', '67890'], contacts(:john).phones.collect{|p| p.number }

Not bad, 1 line instead of 3 and better error reporting to boot.

Posted in ,  | 8 comments

del.icio.us:Testing: DRYing the Asserting of Array Similarity digg:Testing: DRYing the Asserting of Array Similarity spurl:Testing: DRYing the Asserting of Array Similarity wists:Testing: DRYing the Asserting of Array Similarity simpy:Testing: DRYing the Asserting of Array Similarity newsvine:Testing: DRYing the Asserting of Array Similarity blinklist:Testing: DRYing the Asserting of Array Similarity furl:Testing: DRYing the Asserting of Array Similarity reddit:Testing: DRYing the Asserting of Array Similarity fark:Testing: DRYing the Asserting of Array Similarity blogmarks:Testing: DRYing the Asserting of Array Similarity Y!:Testing: DRYing the Asserting of Array Similarity smarking:Testing: DRYing the Asserting of Array Similarity magnolia:Testing: DRYing the Asserting of Array Similarity segnalo:Testing: DRYing the Asserting of Array Similarity

Comments

  1. Guillaume Theoret said 1 day later:

    I may be looking at this problem wrong, but if the only problem is the nondeterministic results why don't just just build an array of phone numbers manually, build an array of phone numbers from the db, call sort! on both and compare that?

    Disclaimer: I've never tried to assert contents of an array so the above might be completely wrong.

  2. Guy Naor said 1 day later:

    There's no problem with comparing with sort!, only problem is that once again it's less DRY. More things to write, and a lot less expressive.

  3. Duncan Beevers said 2 days later:

    If the array you're examining is full of ActiveRecord models, (as ActiveRecord find calls often are) then you'd need to define a <=> method for the class in order for sort! to work.

    Not that that's really a problem, but if sorting instances of itself isn't a primary function of your model, supporting sort! introduces unnecessary complexity.

    Handy little assertion for a reasonably common operation.

    I think (expected - actual).empty? looks a little nicer.

    Also, you can clean up the actual assertion using something like:

    assert_array_similarity ['12345', '56789', '67890'], contacts(:john).phones.map(&:number)
  4. Guy Naor said 2 days later:

    Duncan,

    Yeah, I could change it to empty and it would look a bit nicer. And the map is also a good shorthand.

    Thanks!

  5. Joshua E Cook said 2 days later:

    I think you're approaching this problem incorrectly by comparing Arrays. Assuming the uniqueness of elements within a collection, when the order of a collection's elements is not defined, the appropriate data structure is a Set, not an Array.

    I think something like this is more expressive:

    require 'set'
    assert_equal contacts(:john).phones.map(&:number).to_set,
                 ['12345','56789','67890'].to_set
    
  6. Guy Naor said 2 days later:

    Joshua,

    Sets are fine, but it adds more code and more typing. I'm trying to save on those :-), and simplify.

    UPDATE: Actually there's a much bigger problem with the set transformation in testing. One of the things I want to check is that the arrays contain exactly the same items. WOn't work with sets if the array have non-unique items.

    For example:

    assert_array_similarity [1,2,3,4,5], [1,1,2,3,4,5]
    

    Will fail. But this will pass:

    asset_equal  [1,2,3,4,5].to_set, [1,1,2,3,4,5].to_set
    

    Which means that the set test is only good for same size arrays, or if you don't care aboutthe possibility of multiple items in the result.

    For testing I'm always for doing as exact a test as possible. What if because of some bug you get doubled items? The set with to_set won't find this bug.

  7. Joshua E Cook said 2 days later:

    Yeah, I disclaimed my approach by assuming that the elements would be unique. However, if you do intend for the elements to be unique within the collection, and there is a possibility of a bug causing doubled items, then I think it is appropriate to test for uniqueness separately.

    ary = [1,1,2,3,4,5]
    assert_equal ary, ary.uniq
    

    I realize you want to reduce the amount of code you're writing, but you also say you want to write precise tests. I think that if you care about uniqueness, you should test for uniqueness.

    Something about assert_array_similarity just has a bad smell about it to me. Think about it this way: sample a random group of programmers and ask them what "array similarity" means. Then ask them what it means for a collection's items to be unique, and what it means for sets to be equal. I assert you'll get many more consensus answers for the latter questions.

  8. Guy Naor said 3 days later:

    Joshua,

    Maybe I need a better name :-), but the operation is something I do all the time, and I'm sure other do as well, and DRYing it is very helpful.

    Got I deas for a better name?

Comments are disabled

Subscribe to The Dev Blog