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.
