03 December 2009

Better testing, part 2 – Language in testing

Naming is one of the most important parts of programming. The same is true of testing. A badly named test is hard to understand where as a well named test can be read and understood in an instant. Not only that, good test naming can lead to a better understanding of the problem and so a better solution and a more thoroughly tested implementation.

Should

A great technic for better test naming is by adding “should” to your testing vocabulary.

Let’s take for example a function that give you all the distinct items that have been added to the basket.

 1 
 2 class BasketTest < Test::Unit::TestCase
 3 
 4   def setup
 5     @product1 = Product.new(:name => 'Cheese')
 6     @product2 = Product.new(:name => 'Crisps')
 7     @product3 = Product.new(:name => 'Soap')
 8   end
 9 
10   # Bad testing
11   def test_individual_items
12     b = Basket.new
13     b.add(@product1)
14     b.add(@product3)
15     b.add(@product1)
16 
17     assert_equal 2, b.individual_items.size
18   end
19 
20   # Better
21   def test_should_only_return_distinct_products_that_have_been_added
22     b = Basket.new
23     b.add(@product1)
24     b.add(@product3)
25     b.add(@product1)
26 
27     assert_equal 2, b.individual_items.size
28   end
29 
30 end
31 

As I’ve done here, it’s pretty common to name your test after the function you’re testing, so test_individual_items tests that the individual items function is “working”.

But what does “working” mean?

The second test clears that confusion up. By adding the word “should” into the name of the test case, we’re forcing ourselves to think about what we actually want the function to do before coding it.

The second test also has a desirable side effect. By thinking about what the function should do and by writing that down with the word should it highlights that our original function, individual_items, is badly named, and a better name would be distinct_items.

When

It’s likely you’re not going to only want to test the individual_items function when there are items in the basket. We could also want to test a total function that returns how much everything in the basket is going to cost me.

 1 
 2 class BasketTest < Test::Unit::TestCase
 3 
 4   def setup
 5     @product1 = Product.new(:name => 'Cheese', :price => 1.99)
 6     @product2 = Product.new(:name => 'Crisps', :price => 0.49)
 7     @product3 = Product.new(:name => 'Soap', , :price => 1.20)
 8   end
 9 
10   def test_should_only_return_distinct_products_that_have_been_added
11     b = Basket.new
12     b.add(@product1)
13     b.add(@product3)
14     b.add(@product1)
15 
16     assert_equal 2, b.individual_items.size
17   end
18 
19   def test_should_give_running_total_of_all_items_that_have_been_added_to_the_basket
20     b = Basket.new
21     b.add(@product1)
22     b.add(@product3)
23     b.add(@product1)
24 
25     assert_equal 3.19, b.total
26   end
27 
28 end
29 

These test names are getting too long and there’s some pretty obvious duplication going on. Here comes “When” to help out.

 1 
 2 class WhenItemsHaveBeenAddedToTheBasketTest < Test::Unit::TestCase
 3 
 4   def setup
 5     @product1 = Product.new(:name => 'Cheese', :price => 1.99)
 6     @product2 = Product.new(:name => 'Crisps', :price => 0.49)
 7     @product3 = Product.new(:name => 'Soap', , :price => 1.20)
 8 
 9     @b = Basket.new
10     @b.add(@product1)
11     @b.add(@product3)
12     @b.add(@product1)
13   end
14 
15   def test_should_only_return_distinct_products
16     assert_equal 2, @b.individual_items.size
17   end
18 
19   def test_should_give_a_running_total
20     assert_equal 3.19, @b.total
21   end
22 
23 end
24 

So, using the words When and Should can help your tests and implementation become more readable and more maintainable.

A great way of getting started with this kind of testing is by using shoulda thoughtbots excellent extension to TestUnit.