By dlras2


2012-02-20 20:04:43 8 Comments

I am writing a series of collection classes in C#, each of which implement similar custom interfaces. Is it possible to write a single collection of unit tests for an interface, and automatically run them all on several different implementations? I would like to avoid any duplicated testing code for each implementation.

I'm willing to look into any framework (NUnit, etc.) or Visual Studio extension to accomplish this.


For those looking to do the same, I posted my concrete solution, based off of avandeursen's accepted solution, as an answer.

4 comments

@dlras2 2012-02-21 18:20:25

This is my concrete implementation based off of avandeursen's answer:

[TestClass]
public abstract class IMyInterfaceTests
{
    protected abstract IMyInterface CreateInstance();

    [TestMethod]
    public void SomeTest()
    {
        IMyInterface instance = CreateInstance();
        // Run the test
    }
}

Each interface implementation then defines the following test class:

[TestClass]
public class MyImplementationTests : IMyInterfaceTests
{
    protected override IMyInterface CreateInstance()
    {
        return new MyImplementation();
    }
}

SomeTest is run once for each concrete TestClass derived from IMyInterfaceTests. By using an abstract base class, I avoid the need for any mock implementations. Be sure to add TestClassAttribute to both classes or this won't work. Lastly, you can add any implementation-specific tests (such as constructors) to the child class if desired.

@drzaus 2013-04-30 17:06:43

are you using a particular test framework? I'm using the included unit tests in VS2012, and inherited tests from another "shared" test project (namespace) don't get picked up.

@drzaus 2013-04-30 19:50:53

weird -- inherited tests within the same project, different namespace show up fine. it's only when they're in a different project that they aren't registering. what am i missing?

@avandeursen 2012-02-20 20:18:46

Yes, that is possible. The trick is to let your unit class test hierarchy follow the class hierarchy of your code.

Let's assume you have an interface Itf with implementing classes C1 and C2.

You first create a test class for Itf (ItfTest). To actually exercise the test, you need to create a mock implementation of your Itf interface.

All tests in this ItfTest should pass on any implementation of Itf (!). If not, your implementation does not conform to the Liskov Substitution Principle (the "L" in Martin's SOLID principles of OO design)

Thus, to create a test case for C1, your C1Test class can extend ItfTest. Your extension should replace the mock object creation with the creation of a C1 object (injecting it in, or using a GoF factory method). In this way, all ItfTest cases are applied to instances of type C1. Furthermore, your C1Test class can contain additional test cases specific to C1.

Likewise for C2. And you can repeat the trick for deeper nested classes and interfaces.

References: Binder's Polymorphic Server Test pattern, and McGregor's PACT -- Parallel Architecture for Component Testing.

@dlras2 2012-02-20 22:37:28

To avoid mock implementations, I simply declared my ItfTest class as abstract and declared a protected abstract Itf CreateInstance(); function stub. (Note that both ItfTest and C1Test need to have the [TestClass] attribute.)

@avandeursen 2012-02-21 06:47:40

Yes, I also used the factory method for that since it is simpler. I used this test approach in JUnit, where there is no [TestClass] attribute, but where @Test annotations in superclasses are inherited causing superclass test cases to be re-run in subclasses. And: thanks for the edit.

@Joe Alfano 2012-02-20 20:14:27

You can use the [RowTest] attributes in MBUnit to do this. The example below shows where you pass the method a string to indicate which interface implementation class you want to instantiate, and then creates this class via reflection:

[RowTest]
[Row("Class1")]
[Row("Class2")]
[Row("Class3")]
public void TestMethod(string type)
{
   IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface;

   //Do tests on foo:
}

In the [Row] attributes, you can pass any arbitrary number of input parameters, such as input values for testing or expected values to be returned by method invocations. You will need to add arguments of the corresponding types as test method input arguments.

@dlras2 2012-02-20 20:21:12

Can you edit your answer to explain exactly how I would test implementations this way? You seem to be saying that each unit test needs a switch statement for all implementations, which is not what I want.

@Joe Alfano 2012-02-20 20:51:06

You don't need to use a switch statement if you don't want to. I mentioned that you can also use reflection. See Anthony's answer for an example of this. I also updated my post to include an example of using reflection to instantiate the various concrete implementations of the interface.

@dlras2 2012-02-20 22:34:49

I would probably write this to take instances of IMyInterface instead of just the name, but still +1.

@Joe Alfano 2012-02-20 22:56:08

Thanks Dan for the idea. I've never been able to get MbUnit row tests attributes to work with anything other than constants. For instance, something like this does not compile: [Row(new Class1())]. I think this is a limitation of .net attribute arguments. I don't know if it is possible to do something like this with other testing frameworks.

@dlras2 2012-02-20 23:01:28

Good point about the constants constraint. The activator is definitely a good alternative.

@k.m 2012-02-20 23:14:31

@JoeAlfano: .NET attributes parameters can indeed pretty much be constants only (more).

@Antony Scott 2012-02-20 20:29:37

Expanding on Joe's answer, you can use the [TestCaseSource] attribute in NUnit in a similar way to MBUnit's RowTest. You could create a test case source with your class names in there. You could then decorate every test which the TestCaseSource attribute. Then using Activator.CreateInstance you could cast to the interface and you'd be set.

Something like this (note - head compiled)

string[] MyClassNameList = { "Class1", "Class2" };

[TestCaseSource(MyClassNameList)]
public void Test1(string className)
{
    var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface;

    ...
}

Related Questions

Sponsored Content

51 Answered Questions

10 Answered Questions

[SOLVED] What's the difference between faking, mocking, and stubbing?

33 Answered Questions

[SOLVED] Unit Tests not discovered in Visual Studio 2017

42 Answered Questions

7 Answered Questions

[SOLVED] NUnit - How to test all classes that implement a particular interface

37 Answered Questions

[SOLVED] Visual Studio 2013 doesn't discover unit tests

2 Answered Questions

[SOLVED] How does one test async code using MSTest

  • 2010-01-13 22:14:44
  • Kevin Moore
  • 16737 View
  • 75 Score
  • 2 Answer
  • Tags:   .net unit-testing

2 Answered Questions

Sponsored Content