Problems when you don’t Refactor Rails: Test Duplication

I’ve already written about the problem of code duplication when you don’t refactor Rails. A similar problem I see all the time is test duplication.

Test Duplication

As Rubyists, we are lucky to have unit testing deeply ingrained in our community. Many developers write tests, some even writing the tests first. But a big problem that comes up is that these tests aren’t maintained as much as the application code.

When I learned the Red-Green-Refactor cycle, it only talked about refactoring the code implementation. Nothing about refactoring the tests. So I would end up with some great refactored code but a dozen test cases thrown together. Just like with code duplication, these unfactored tests make modifications more costly and created bugs from the multiple branches of code.

Costly Modifications

Lets say you wrote a test to submit a request to a Rails application. When it’s given a success parameter that’s greater than 0, the request is successful. When it’s less than or equal to 0, the request is redirected. Writing tests for it, you want to make sure you test the all of the different ways it can be called:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class ExampleControllerTest   '1'
    assert_response :success
  end
 
  should 'be successful with a params success of 2' do
    post :create, :success => '2'
    assert_response :success
  end
 
  should 'be successful with a params success of 3' do
    post :create, :success => '3'
    assert_response :success
  end
 
  should 'be redirected with a params success of -1' do
    post :create, :success => '-1'
    assert_response :redirect
  end
 
  should 'be redirected with a params success of 0' do
    post :create, :success => '0'
    assert_response :redirect
  end
 
end

As you go through Red-Green-Refactor, you’d probably end up with a simple comparison instead of enumerating every integer in your controller action.

1
2
3
4
5
6
7
class ExampleController  0
      render :text => 'Refactored'
    else
      redirect_to('/failed')
    end
  end
end

Now the problem is, what if your requirements change and you need change the parameter name to :content? Or the action should be called “new”? You’ll have very little to change in your controller because it’s well factored. But you have 5 things to change in your tests. I hope you don’t miss one and have one method branching.

Bugs from Branching Code

Just like with code duplication, when there are N copies of test code in an application they all have to be modified at the same time. Say you add a new parameter to the example that forces a redirect. Now you’d need to change each test so you have one version with the parameter and one version without the parameter (10 tests total). Forgetting a test means there is one combination you aren’t testing, which means your user will “get” to test for you. Hopefully it will work exactly like you planned.

Remember, your tests are code too. If you don’t invest time to refactor them, they can cause problems just like unfactored code.


Example: Showing one way to refactor the test case

In case you’re interested, here is how those example tests could be refactored using shoulda:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ExampleControllerTest   param
      assert_response :success
    end
  end
 
  def self.should_be_redirected_with_a_param_of(param)
    should "be redirected with a success param of #{param}" do
      post :create, :success => param
      assert_response :redirect
    end
  end
 
  context "POST to :create" do
    should_be_successful_with_a_param_of('1')
    should_be_successful_with_a_param_of('2')
    should_be_successful_with_a_param_of('3')
 
    should_be_redirected_with_a_param_of('-1')
    should_be_redirected_with_a_param_of('0')
  end
end

Now if your requirements change so you need to use a new parameter or action, there is very little you’d need to change in your tests

Like this post? Read the rest of the Problems When You Don’t Refactor Rails series

  1. Problems when you don’t Refactor Rails: Code Duplication
  2. Problems when you don’t Refactor Rails: Test Duplication
  3. Problems when you don’t Refactor Rails: Wild Estimates
  4. Problems when you don’t Refactor Rails: Lowered morale
  5. Problems when you don’t Refactor Rails: Lower Participation