views:

446

answers:

6

If you write a test class like

class MyTest < Test::Unit::TestCase 
  def setup 
  end

  def test_1 
    flunk
  end

  def test_1 
    assert true
  end
end

the first test_1 is ignored. Although it looks like a stupid mistake, it can happen with copy and paste programming. Apart from running

grep test test_me.rb | wc

and comparing that with how many tests test unit says has run, or using rcov or heckle, or running with -w, how can you detect such issues?

Also, is there any way of specifying that test methods shouldn't be overwritten?

Edit: The method being tested had a parameter with 6 or so possible values, and the tester wanted to test each scenario. This was why copy and paste programming was used. The only alternative I can envisage for such a scenario is a a six-element array of parameters and expected values.

+1  A: 

Is this really a problem if you're giving your tests proper descriptive names? Here's an example of some test method names from my most recent project:

test_should_not_do_html_escaping_for_administrators
test_should_not_be_able_to_create_project_with_company_user_doesnt_own
test_should_be_able_to_edit_own_projects
test_should_not_be_able_to_edit_others_projects

If your test names are short enough that you can easily overwrite or duplicate them, you're probably not being descriptive enough as to what you're actually testing in each one.

Luke
The method being tested had six possibilities (1 to 6) for one of the parameters, and the tests were test_foo_1 to test_foo_6 to test each of those possibilities (except there were two test_foo_5 and no test_foo_6). Would you have used an array of 1 to 6 instead?
Andrew Grimm
Keep your test as simple as possible. The whole idea of testing is to make the design of your code better. The next developer who will come along and maintain the project should be able to look at your test and see what it is doing.
andHapp
A: 

Avoid Copy-Paste programming. Rebind the key shortcuts if you have to.

Morendil
+5  A: 

You can take advantage of Ruby's method_added that gets called anytime a method is added to a class. You should be able to can something into a module that you include, but here is a simple example of doing it inside your test class.

class MyTest < Test::Unit::TestCase

  @@my_tests = []

  def self.method_added(sym)
    raise "#{sym} already defined!" if @@my_tests.include? sym
    @my_tests << sym
  end

  def test_foo_1
  end

  def test_foo_2
  end

  def test_foo_1
  end
end
Aaron Hinni
While this is useful (I did upvote), I just don't think this should be the accepted answer. The right thing to do is not copy and paste, not validate the copy and paste code.
DanSingerman
Anyone can say "Don't copy and paste" - as they say, giving good advice is easy, following it is the hard part. I appreciate answers that tell me something I don't know.
Andrew Grimm
+1  A: 

Edit: The method being tested had a parameter with 6 or so possible values, and the tester wanted to test each scenario. This was why copy and paste programming was used.

In those circumstances I do this:

def test_foo
  test_cases = [
   {:param => 1, :expected => 'whatever is expected'},
   {:param => 2, :expected => 'whatever is expected'},
   {:param => 3, :expected => 'whatever is expected'},
   {:param => 4, :expected => 'whatever is expected'},
   {:param => 5, :expected => 'whatever is expected'},
   {:param => 6, :expected => 'whatever is expected'}
  ]

  for test_case in test_cases
    do_the_test(test_case)
  end
end

def do_the_test(test_case)
  # test code here
end

This completely avoids copy and paste, which as has been said, is bad

The only alternative I can envisage for such a scenario is a a six-element array of parameters and expected values.

Exactly!

DanSingerman
This is the approach I myself would have taken. I guess I didn't want to be too critical of the author of the tests, and introducing looping into a test may be unusual to some.
Andrew Grimm
+2  A: 

Regarding HermanD's answer, since this is Ruby!, you can also do this directly in the class to create unique test methods:

class MyObjectTest < Test::Unit::TestCase
  [
   {:param => 1, :expected => 'whatever is expected'},
   {:param => 2, :expected => 'whatever is expected'},
   {:param => 3, :expected => 'whatever is expected'},
   {:param => 4, :expected => 'whatever is expected'},
   {:param => 5, :expected => 'whatever is expected'},
   {:param => 6, :expected => 'whatever is expected'}
  ].each do |test_case|
    define_method :"test_using_#{test_case[:param]}_should_return_#{params[:expected].underscore}" do
      assert_equal test_case[:expected], MyObject.new.do_something_with(test_case[:param])
    end
  end
end

It feels even more natural using Rspec's (or Shoulda's) sentence like language:

describe MyObject do
   [
   {:param => 1, :expected => 'whatever is expected'},
   {:param => 2, :expected => 'whatever is expected'},
   {:param => 3, :expected => 'whatever is expected'},
   {:param => 4, :expected => 'whatever is expected'},
   {:param => 5, :expected => 'whatever is expected'},
   {:param => 6, :expected => 'whatever is expected'}
  ].each do |test_case|
    it "should return #{test_case[:expected]} when using #{test_case[:param]}" do
      MyObject.new.do_something_with(test_case[:param]).should == test_case[:expected]
    end
  end
end
Ian Terrell
A: 

The gem version of test-unit has the ability to detect test redefining as of 2.0.7.

Andrew Grimm