Unit test -- test different modules with same interface

Started by
6 comments, last by Hodgman 12 years, 4 months ago
In brief, how to unit test an interface, which are implemented by different modules, efficiently?

For example,
I have an interface (an interface or a class, what ever, it doesn't matter), Animal.


class Animal
{
public:
virtual bool canEat();
virtual bool eat();
// blah blah
};


I have several modules implementing that interface, such as Cat, Dog, etc. Assume Cat and Dog only expose the interface Animal, we don't need anything else from them.

OK, now I need to unit test Cat and Dog. Of course I can write tests for them separately, but that's not efficient and will produce almost same and duplicated code. Because they share the same public interface and logic.

So my question is, how can I test on Animal, but for Cat and Dog, separately, with the same code?

I can write


void testAnimal(Animal *);

MYTEST()
{
testAnimal(new Cat);
testAnimal(new Dog);
}


Then I can reuse the test code, but then if any test fails, it will be hard to trace which is wrong, Cat or Dog, because one test is doing two things.

Any suggestions and experience?


Thanks

https://www.kbasm.com -- My personal website

https://github.com/wqking/eventpp  eventpp -- C++ library for event dispatcher and callback list

https://github.com/cpgf/cpgf  cpgf library -- free C++ open source library for reflection, serialization, script binding, callbacks, and meta data for OpenGL Box2D, SFML and Irrlicht.

Advertisement
Here's one suggestion:

void testAnimal(Animal *, const char *name);

#define TEST_ANIMAL(class) testAnimal(new class, #class)

MYTEST()
{
TEST_ANIMAL(Cat);
TEST_ANIMAL(Dog);
}


See http://msdn.microsoft.com/en-US/library/7e3a913x%28v=VS.80%29.aspx if you've not come across the # operator before.
What prevents this?


void testAnimal(Animal *);

TESTCAT()
{
testAnimal(new Cat);
}

TESTDOG()
{
testAnimal(new Dog);
}


You're unit testing the implementations, then do that. They might share a test implementation if they abide by the same contracts.

What prevents this?


void testAnimal(Animal *);

TESTCAT()
{
testAnimal(new Cat);
}

TESTDOG()
{
testAnimal(new Dog);
}


You're unit testing the implementations, then do that. They might share a test implementation if they abide by the same contracts.


Damn, why my mind was so closed and didn't think that? :rolleyes:

You are quite right.
We can extract the common logic to a shared function then call it in different tests, which each test is for one module.

However, I have one more question:
I'm used to, and maybe a lot of people do so, that only write test assertions in the test body (TESTCAT) rather than an external function (testAnimal).
Is there anything bad to forward the test itself (TESTCAT) to another function (testAnimal), like in your code?
I don't think so but want confirmation.

Thanks

https://www.kbasm.com -- My personal website

https://github.com/wqking/eventpp  eventpp -- C++ library for event dispatcher and callback list

https://github.com/cpgf/cpgf  cpgf library -- free C++ open source library for reflection, serialization, script binding, callbacks, and meta data for OpenGL Box2D, SFML and Irrlicht.


I'm used to, and maybe a lot of people do so, that only write test assertions in the test body (TESTCAT) rather than an external function (testAnimal).
Is there anything bad to forward the test itself (TESTCAT) to another function (testAnimal), like in your code?


I don't have a ton of experience with teams that do testing, but from what I understand it's frowned upon. Tests should be isolated, and if two classes have the same implementation contract, why don't you have one class?

That said, I've done it for partially shared functionality and it seems better than copy/paste.

I don't have a ton of experience with teams that do testing, but from what I understand it's frowned upon. Tests should be isolated, and if two classes have the same implementation contract, why don't you have one class?


In my case, the two classes have the same interface, but the implementation is different.
For example, both Cat and Dog supports the same interface canEat and eat, but when, what, and where to eat is the implementation and is different between Cat and Dog.


That said, I've done it for partially shared functionality and it seems better than copy/paste.


Beside copy/paste, I also considered script generated code, include source code, but all of them lack readability and maintenance.
So I may go for the "forward to another function" way.

https://www.kbasm.com -- My personal website

https://github.com/wqking/eventpp  eventpp -- C++ library for event dispatcher and callback list

https://github.com/cpgf/cpgf  cpgf library -- free C++ open source library for reflection, serialization, script binding, callbacks, and meta data for OpenGL Box2D, SFML and Irrlicht.

I think it makes the most sense to have a contract test for the interface provided by the interface module. The Cat test can then supply a Cat to the contract test in its module, and the Dog can supply a Dog to the contract test in its module. When you change the contract test in any way, all derived classes contract tests should automatically have been updated. That is, the contract should come in one piece.

So, the contract for the interface is defined with the interface in the shared module. The check that any derived class conforms to the contract is done in the module of the derived class, as are any tests specific to that derived class.

The specifics of precisely how I'd recommend you do this depends on how your test framework registers tests. If you're using QTest, you could make the contract test abstract and inherit from it in the test for the specific class.

[quote=Telastyn]I don't have a ton of experience with teams that do testing, but from what I understand it's frowned upon. Tests should be isolated, and if two classes have the same implementation contract, why don't you have one class?[/quote]
For example, all classes that inherit from I_Serializer might be required to be able to serialize and deserialize back and forth without changing the object being serialized. You could write this test once and apply it to all things that claim to be a valid implementation of I_Serializer.

They almost certainly have other properties as well. The XmlSerializer would need its own tests that are different from the BinarySerializer. So they could use both a contract test and an implementation test.
However, I have one more question:
I'm used to, and maybe a lot of people do so, that only write test assertions in the test body (TESTCAT) rather than an external function (testAnimal).
Is there anything bad to forward the test itself (TESTCAT) to another function (testAnimal), like in your code?
I don't think so but want confirmation.
In my unit testing framework, I just use the engine's ASSERT macro to implement test conditions. If the test calls into other code, and that code uses ASSERTs, then it can trigger a test failure as well.

This topic is closed to new replies.

Advertisement