views:

192

answers:

6

Suppose I have class Car with following methods:

  • LoadGasoline(IFuel gas)
  • InsertKey(IKey key)
  • StartEngine()
  • IDrivingSession Go()

the purpose of Car is to configure and return an IDrivingSession which the rest of the application uses to drive the car. How do I unit-test my Car?

It looks like it requires a sequence of operations done before I can call Go() method. But I want to test each method separately as they all have some important logic. I don't want to have bunch of unit-tests like

Test1: LoadGasoline, Assert

Test2: LoadGasoline, InsertKey, Assert

Test3: LoadGasoline, InsertKey, StartEngine, Assert

Test4: LoadGasoline, InsertKey, StartEngine, Go, Assert

Isn't there a better way to unit-test sequential logic or is this a problem with my Car design?

--- EDIT ---- Thanks for all the answers. As many noticed, I should also have tests for invalid scenarios and I have those too, but this question is focused on how to test the valid sequence.

+1  A: 

Why don't you want all those tests?

Go has very different behavior if you call it before or after, say, InsertKey, right? So you ought to be testing both behaviors, in my opinion.

mquander
I think his issue is that Test 4 is testing the same thing as tests 1,2 and 3. Since all actions are doen in the same sequence. He is not calling Go before insert key or after key in two different tests. I agree with the op that Test 4 is repeative of tests 1-3. And I agree with you that you need to have variation in the order. So you try and drive a car that isn't started.
JoshBerke
+1  A: 

Its a fair reluctance but sometimes thats what you need to do. If you can't fake out the system under test so it thinks its in a later state, then you need to go through the same steps to get it into that state. Without knowing more about what your testing its not clear how you could fake out the different states.

One way you can make this tolerable is use an extract method refactoring on tests for one state so that same code can be used to prepare the next test.

Frank Schwieterman
+1  A: 

I would probaly have

Test 1: LoadGasoline, Assert, InsertKey Assert, StartEngine Assert, Go Assert
Test 2: LoadGasoline, Go, Assert
Test 3: Go, Assert
Test 4: StartEngine, Go, Assert

Depending on the actual object, I would probally not try and do all permutations, but I would have a single test that hits the success track, then I would tests that hit my fringe cases.

Edit:

After some thought I might have tests like:

  • Start a car key that has no gas
  • Start a car with gas, and wrong key
  • Start a car with gas and right key (Test 1 above)
  • Push Peddle before starting car.
JoshBerke
+1 this looks a lot more like behavior-driven development which is probably what he wants to focus on.
Jeffrey Cameron
What if LoadGasoline later refactored to have a bug? In your example, both Test1 and Test2 will fail, which is what I think I should avoid.
zvolkov
Yes it will fail the quesation is if you want to test more integration style behavior style or if you want a more pure isolated test. To get an isolated test you will need to ensure you can setup the object to the correct state. Depending on your model this may or may not work
JoshBerke
+2  A: 

Some unit testing frameworks let you specify set-up code which runs before the actual test starts.

This allows you to get the target object into the proper state before running your test. That way your test can pass or fail based on the specific code you're testing rather than on the code needed before you can run a test.

As a result, your test sequence will wind up something like this:

Test1: 
    LoadGasoline, Assert

Test2 Setup:
    LoadGasoline

Test2:
    InsertKey, Assert

Test3 Setup: 
    LoadGasoline, InsertKey

Test3:
    StartEngine, Assert

Test4 Setup: 
    LoadGasoline, InsertKey, StartEngine

Test4:
    Go, Assert

Realistically speaking, since the tests are all run in sequence there's no chance of Test's Setup failing if the previous test passes.

With that said, you should also test failure cases that aren't expected to work but that's a different issue.

17 of 26
This is what I originally did but (at least with MSTest) the unit-test fails if its Setup logic fails so the result is same as if I had all steps in every method.
zvolkov
+2  A: 

I think each method should be tested separately and independently.

IMHO, you should prepare the environment for each case, so only the LoadGasoline test will break if you change the LoadGasoline method, and you won't need to see all the tests break because of a single bug.

I don't know how the state of your Car looks like, but, before the InsertKey, you should prepare with a method like, car.SetTotalGasoline(20); or whatever variable is set in this method, but not depend on a complex logic of the method LoadGasoline.

You will later need a test (in this case, not a unit test) to test all the sequence.

Samuel Carrijo
But if you have to initialize the object to get the car into the right state your going to end up calling LoadGasoline method. Which if you modify the implementation you might not only break it's unit test but the setup code for all the other tests.
JoshBerke
You could extend the car class to do this. Like do some class to help in the test that extends Car class, but with more methods to help in testing
Samuel Carrijo
I like this approach the best, it seems very aligned with my idea of good design and unit-testing... Will flag as The answer later if nothing better comes up.
zvolkov
A: 

Technically you should use the following tests at least:

testLoadGasoline

testInsertKeyGasolineNotLoaded

testStartEngineKeyNotInserted

testGoEngineNotStarted

testGo

If you can directly view intermediate steps you can add

testInsertKeyGasolineLoaded

testStartEngineKeyInserted

Note that if you can directly set the state (which is language and design dependent), then testInsertKeyGasolineLoaded might not actually call LoadGasoline.

Kathy Van Stone