views:

51

answers:

3

I'm writing some unit tests for one of my rails models. It's a large model and in some cases I have dozens of assertions for many of the methods. I prefer using plain test/unit with rails' activesupport syntax so I have a file like this:

require 'test_helper'
class ItemTest < ActiveSupport::TestCase
  test "for_sale? should be true if cond 1..."
  test "for_sale? should be true if cond 2..."
  test "for_sale? should be true if cond 3..."
  test "for_sale? should be true if cond 4..."
  test "for_sale? should be false if cond 1..."
  test "for_sale? should be false if cond 2..."
  test "for_sale? should be false if cond 3..."
  test "for_sale? should be false if cond 4..."
end

and so on for each of the methods. The problem is the test are becoming increasingly complicated because I can no longer rely on setup() because the context for each group of tests is so vastly different for each method. Also, it's difficult just to test the method I'm working on without running all the tests.

How do others manage large test cases like this? Is there a best practice?

A: 

I would recommend shoulda if you want to stick with test/unit. You don't need to use the shoulda macros but it will give you one very nifty feature that applies here: context. It allows you to break up your unit tests into separate sections with their own before and these can be nested.

context "as a really cool user" do
  test "test number one..."
end

context "as a lame user" do
  test "test number two..."
end

From there you can continue to use all test/unit syntax or if shoulda wins you over - you can switch or mix-n-match.

Geoff Lanotte
Another useful mental model is to use `context "when some other variable...`. In the previously mentioned example of the credit card is useful to think as: `context "when the credit card is stolen" do ...`
Chubas
A: 

I don't know if it's best practice, but here's what I do.

Rather than have all objects I want to play with created in setup, I have methods for creating a specific object, for example create_credit_card_already_expired, create_credit_card_stolen, create_credit_card_valid. Sometimes I put these methods into a module, so that they can be shared between different test case classes. I also create custom assertion methods, such as assert_credit_card_rejected.

So sample code for me would be

require "test/unit"

module TestCreditCardHelper
  def assert_credit_card_rejected(credit_card, failure_message)
    assert credit_card.rejected?, failure_message #A real method might be more complicated
  end

  def create_credit_card_stolen
    CreditCard.new(:stolen => true)
  end

end

class TestCreditCard < Test::Unit::TestCase
  include TestCreditCardHelper

  def test_stolen_credit_card_rejected
    credit_card = create_credit_card_stolen
    assert_credit_card_rejected(credit_card, "Doesn't reject stolen credit cards")
  end
end
Andrew Grimm
A: 

A unit test shall test one thing only. It sounds too much is tested here. There should be one test per condition.

require 'test_helper'
class ItemTest_Condition1 < ActiveSupport::TestCase
  test "for_sale? should be true if cond 1..."
  test "for_sale? should be false if cond 1..."
end

class ItemTest_Condition2 < ActiveSupport::TestCase
  test "for_sale? should be true if cond 2..."
  test "for_sale? should be false if cond 2..."
end
class ItemTest_Condition3 < ActiveSupport::TestCase
  test "for_sale? should be true if cond 3..."
  test "for_sale? should be false if cond 3..."
end
...
philippe