Unit Testing: coding to interfaces?
Currently my project is composed of various concrete classes. Now as I'm getting into unit testing it looks like I'm supposed to create an interface for each and every cla开发者_如何学编程ss (effectively doubling the number of classes in my project)? I happen to be using Google Mock as a mocking framework. See Google Mock CookBook on Interfaces. While before I might have just classes Car
and Engine
, now I would have abstract classes (aka C++ interfaces) Car
and Engine
and then the implementation classes CarImplementation
and EngineImpl
or whatever. This would allow me to stub out Car
's dependency on Engine
.
There are two lines of thought I have come across in researching this:
Only use interfaces when you may have the need for more than one implementation of a given abstraction and/or for use in public APIs, so otherwise don't create interfaces unnecessarily.
Unit tests stubs/mocks often are the "other implementation", and so, yes, you should create intefaces.
When unit testing, should I create an interface for each class in my project? (I'm leaning towards creating interfaces for ease of testing)
Think you've got a number of options. As you say, one option is to create interfaces. Say you have classes
class Engine:
{
public:
void start(){ };
};
class Car
{
public:
void start()
{
// do car specific stuff
e_.start();
private:
Engine e;
};
To introduce interfaces - you would have to change Car to take an Engine
class Car
{
public:
Car(Engine* engine) :
e_(engine)
{}
void start()
{
// do car specific stuff
e_->start();
private:
Engine *e_;
};
If you've only got one type of engine - you've suddenly made your Car objects harder to use (who creates the engines, who owns the engines). Cars have a lot of parts - so this problem will continue to increase.
If you want seperate implementations, another way would be with templates. This removes the need for interfaces.
class Car<type EngineType = Engine>
{
public:
void start()
{
// do car specific stuff
e_.start();
private:
EngineType e;
};
In your mocks, you could then create Cars with specialised engines:
Car<MockEngine> testEngine;
Another, different approach, would be to add methods to Engine to allow it to be tested, something like:
class Engine:
{
public:
void start();
bool hasStarted() const;
};
You could then either add a check method to Car, or inherit from Car to test.
class TestCar : public Car
{
public:
bool hasEngineStarted() { return e_.hasStarted(); }
};
This would require Engine to be changed from private to protected in the Car class.
Depending on the real world situation, will depend on which solution is best. Also, each developer will have their own holy grail of how they believe code should be unit tested. My personal views is to keep the client/customer in mind. Lets assume your clients (perhaps other developers in your team) will be creating Cars and don't care about Engines. I would therefore not want to expose the concepts of Engines (a class internal to my library) just so I can unit test the thing. I would opt for not creating interfaces and testing the two classes together (third option I gave).
there are two categories of testing regarding implementation visibility: black-box testing and white-box testing
black-box testing focuses on testing implementation through their interfaces, and validating the adjust to their spec.
white-box testing tests granular details about the implementation that SHOULD NOT in general be accessible from the outside. This sort of testing will validate that the implementation components work as intended. So their results are mostly of interest to developers trying to figure out what is broken, or needs mantainance
mocks by their definition fit into modular architectures, but it doesn't follow that all classes in a project need to be entirely modular out themselves. Its perfectly fine to draw some line when a group of classes will know about each other. They as a group can present to other modules from the persepective of some facade interface class. However, you'll still want to have white-box test drivers inside this module with knowledge about the implementation details. Hence this sort of testing is not a good fit for mocks.
It follows trivially from this that you don't need to have mocks or interfaces for everything. Just take the high-level design components that implement facade interfaces and create mocks for them. It will give you the sweet spot where mock testing pays off IMHO
having said that, try to use the tool to your needs, rather than letting the tool force you into changes you think will not be beneficial in the long run
Creating interfaces for every class within your project may or may not be necessary. This is entirely a design decision. I've found that it mostly isn't. Often in n-tier design you wish you abstract the layer between data access and logic. I would argue that you should work towards this as it aids in testing the logic without much infrastructure necessary for the tests. Methods of abstraction like dependency injection and IoC would require you to do something like this and would make it easier to test said logic.
I would examine what you are trying to test and focus on the areas that you view as most prone to error. This can help you decide whether interfaces are necessary.
精彩评论