views:

284

answers:

8

I am learning about Unit Testing and want to know how to write testable code. But, I'm not sure how to write testable code without making it complex. I'll take famous Car and Engine problem to describe the problem.

class Car
{
private:
   Engine m_engine;

public:
   Car();
   // Rest of the car
}

I came up with following solutions to make the above code testable.

  1. Changing the Car's constructor to take Engine as a parameter. Then mock the Engine and do the testing. But, if I don't have different kinds of Engines, it seems inappropriate to parameterize the constructor just to make it testable.

  2. Using a setter and then pass a mock Engine to the setter. Same flow as the above.

  3. Testing the Engine first and then testing the Car with proven Engine (or using a stub Engine).

What are the alternatives I have to make above code testable? What are the strenghts and weaknesses of each method?

+2  A: 

Unit Testing, as it's name implies, is about testing the Units to confirm that they work as prescribed. This means, you should Test engine separately.

System Testing or Integration Testing is about testing that they all 'glue' together correctly.

Of course, it is more complex than just this, but it should point you in the right direction.

Dan McGrath
The OP is asking how to test Car by itself, using a mock Engine, without changing the Car public interface.
ChrisW
It means it's perfectly ok to use the tested Engine to test the Car. Thanks for the answer. I was bit confused over the concept of using already tested objects. But, it makes things simple and does the work.
Varuna
@Varuna actually its the other way, those type of assumption is what makes things more complex. You miss having a clear guideline to separate the units of your application, and before you realize you already have classes that do too many things and are v. hard to test.
eglasius
@Freddy what do you mean by the other way?
Varuna
eglasius
@Freddy Thanks for the explanation.
Varuna
+2  A: 

The option 1 is generally the right way.

By being able to have full control of the engine you give to the car, you are able to test the car v. well.

You can more easily test how the car behaves with all the different outputs the engine gives to the car. You can also make sure the car its making the appropriate calls to the engine.

Having it in the constructor makes it really clear that the Car depends on an Engine to work. Use it with a dependency injection framework, and the constructor issue isn't really a problem at all.

eglasius
+2  A: 

The real question is, what are the requirements? If the goal is to simply implement a "Car" object then it doesn't even need an engine.

Tests should always be related to requirements. Any object model is going to be some generalization of reality, so the issue is what aspects are required to be represented.

Once you have your requirements down pat, then you should write your tests, at a generic high level. Then, the OO design should be done in such a way that these tests can be implemented.

jessicar
After reading all the answers and comments I decided that this is the best answer. How you make the system testable depends on the requirements and the scope. The popular options is to go for the 1st option, but there are times where 3rd option should be used for the sake of simplicity.
Varuna
I'd argue that although simply testing the car with an "already tested" engine seems simple, its not so simple when someone comes around and re-orders the tests. The order becomes very significant in which I would hope unit-tests wouldn't necessary care about this ordering. They should be separate.That all being said, if its a small project that won't be growing, sure that works. But if its larger, you might consider implementing option 1 as it will hold the test of time.
xyld
@jessicar, unit tests should test all the aspects of your public interface, and not be arbitrarily limited to just the "requirements". Your requirements may not be detailed enough to adequately describe all the behavior. And if you create an object to meet one requirement, someone else will rightly expect all of your functionality to work. If you didn't test it, how do they know?<br>You are talking about integration and system tests, not unit tests.
John Deters
Problem seems to be still open
Varuna
+1  A: 

One possibility in C++ is to use friendship:

class Car
{
private: //for unit testing
    friend class TestCar; //this class is the unit test suite for the Car class
    Car(Engine* mockEngine); //this constructor is only used by the TestCar class
private:
    Engine* m_engine;
public:
    Car();
    // Rest of the car
};

A second possibility is for the constructor's implementation to use a global/static method for example as follows, and you can change the implementation of this method, either via some config file, or by linking (perhaps dynamic-linking) to different versions of this method:

Car::Car()
{
    m_engine = Engine::create();
}
ChrisW
Thanks for the answer. But, I am bit confused over whether it is a good practise to add things to code just to make it testable.
Varuna
I think you are correct to be wary of this approach. This adds a dependency between Car and TestCar, which shouldn't be necessary for testing and just adds unnecessary code to the Car class.
Mike Ottum
I think this is perfectly valid to have extra methods only for testing. The hardware designers leave test entrypoints in the finished device for the same reason. Having said that if I can design my classes without test only methods I am happier, but this is not always the case. I would prefer this approach if it leads to a simpler client interface.
iain
Of course `Engine` should be a pointer (or better still a `scpoed_ptr`)
iain
"This adds a dependency between Car and TestCar" -- no, Car doesn't 'depend on' TestCar.
ChrisW
"just adds unnecessary code to the Car class" -- you could do something similar with `#ifdef UNIT_TESTING` if you prefer that.
ChrisW
+5  A: 

If you only have one Engine type, why are you trying to make it a new object? If you don't plan on swapping engines, don't create another abstraction layer. Just make the engine part of the car.

You might be decomposing to reduce complexity, rather than to reuse components. Good call. In which case, I'd say that 3 is your best bet - validate your lower level components, then use higher level code that calls the lower level objects.

In reality, Engine is more likely to be something like Database. And you will want to change your constructors to use a different Database (for test reasons, or other reasons), but you can leave that lie for a while.

wisty
Thanks for the answer. I thought 3 to be the right option. But, I was bit confused whether it ok to go for it.
Varuna
Good answer but in an unexpected way.. It demonstrates that a lot of popular techniques are intended for either database-driven development, web-development or both - and advocates often assume that db and web is all the software is about.
ima
Once he's decomposed it into the two components, Car and Engine (as he has), it is his responsibility to test them both. But as he's discovered, testing Car is hard because of the dependency on Engine. Writing Car tests that understand when the Engine is at red-line speeds, or when the Transmission has to shift, are no longer unit tests. They're integration tests. Car is still hard to test, and will only get harder to test as you add Tires and Radios and Windows. By managing the dependencies externally, you avoid the problem in the first place.
John Deters
+5  A: 

Look at it from a different (Test-Driven Development) viewpoint: code that is easy to test is easy to use. Writing the unit tests is actually you testing the "public interface" of your code. If it's hard to test, that's because you've got some dependencies in there that make it hard. Do you really need a containment relationship, or would an associative relationship make more sense?

In your case I personally think it would be more testable to pass the Engine in the constructor, so I'd refactor the constructor as in your suggestion #1. You can test the Engine in one test suite, and provide a mock Engine to test the Car in another test suite. Testing it is now easy, meaning the interface is easy to use. That's a good thing.

Now think about how you would use that implementation in a real project. You'd create a CarFactory class, and the factory would create an Engine and put it in the Car before delivering it to you. (Also notice how this ends up more closely modeling the real world of cars and engines and factories, but I digress.)

Therefore the TDD answer would be to refactor the code to take an Engine pointer on the constructor.

John Deters
+2  A: 

Misko Hevery writes frequently on this topic. Here's a presentation from October 2009. He argues that the dependency graph should be explicit in the constructor.

it seems inappropriate to parameterize the constructor just to make it testable

I think that's a fascinating comment. Under what sorts of conditions would including testability as part of the design be inappropriate? Sacrificing correctness for testability is clearly wrong, though in practice I have never seen that choice forced. Sacrificing performance for testability... perhaps that, in some specific cases. Sacrificing code uniformity? I personally would rather change the coding standard, and gradually bring the legacy code into compliance.

VoiceOfUnreason
In support of your comment, I would be clear that you should never include "test code" in your "production code" (meaning you should avoid bad stuff in your production code like "if (in_test) {return test_data;}". But making it testable is different than adding actual test logic. It's a common mistake to confuse the two.As you point out, making it "testable" is to make it "more usable", which is a benefit of TDD.
John Deters
A: 

I'd argue in favor of allowing (via constructor or property) the ability to add an implementation of an Engine to the Car (which is preferably an IEngine).

The Car tests shouldn't actually care what the Engine does, so long as it responds properly to the results from calls to the Engine. Then, use a fake Engine to test so that you can control the signals sent to the car, and you should be good to go.

kyoryu