Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test different classes with one interface?

I have some interface ISome. have 4 classes like MyClass1:ISome, MyClass2:ISome etc. How to test some method from this interface with NUnit, but make only 1 unit-test for all classes?

like image 840
Dmitry Dashko Avatar asked Dec 27 '22 11:12

Dmitry Dashko


2 Answers

4 classes means 4 different implementations of the given method. As a consequence you should have 4 unit tests. It would be wrong to try to write a single unit test.

like image 179
Darin Dimitrov Avatar answered Dec 29 '22 23:12

Darin Dimitrov


How to test some method from this interface with NUnit, but make only 1 unit-test for all classes?

The way you wrote this, people can interpret it as "a single unit test, period". This of course is a really bad idea.

Ideally you want to make only one assertion per test case, and you really do have to assert all the different pieces of logic in your code. Each class will have more than one piece of logic, and each implementation of your interface is a distinctly different piece of logic (even if it has the same interface, and even if you copy and pasted code).

So you are faced with one option: You need multiple unit tests.

Code duplication

To avoid duplicating your unit test code, you can try the parameterized unit test features of NUnit.

With it you can multiply the number of unit test methods you actually write by the number of instances you feed into those tests. Those instances could be different implementations.

There are a lot of ways to parameterize your tests in NUnit. See this article:

  • http://www.nunit.org/index.php?p=parameterizedTests&r=2.6

I personally prefer the TestCaseSource attribute:

  • http://www.nunit.org/index.php?p=testCaseSource&r=2.6

The NUnit test runner will still display each unit test method you've written, but will show each item in your data array as a sub-test.

These are tricky to learn how to use because you have to factor your tests into a data-driven model.

How to parameterize on implementation

If your implementations of a given method in your interface simply spit out different data, you might be in luck. E.g. if you're testing a multiply in one implementation and an addition in another method, your matrix could look like this:

Impl      Input1  Input2  Result
Multiply  0       7       0
Multiply  3       6       18
Add       0       0       0
Add       5       6       11

You'd return an array that embodies each row in this matrix, including which implementation of your interface you are running against, feed those to a single unit test method that embodies the general flow of the test, and call it done.

Here's some example code that implements these operations:

// This interface takes two ints, returns one int
public interface IBinaryOperation
{
    int Execute(int x, int y);
    string Name { get; }
}

public class Add : IBinaryOperation
{
    public int Execute(int x, int y) { return x + y; }
    public string Name { get { return "Add"; } }
}

public class Multiply : IBinaryOperation
{
    public int Execute(int x, int y) { return x * y; }
    public string Name { get { return "Multiply"; } }
}

And here is an example test fixture implementation:

[Test, TestCaseSource("OperationTestCases")]
public void ExecuteReturnsCorrectResult(
    IBinaryOperation operation,
    int inputX, int inputY,
    int expectedResult
    )
{
    int actualResult = operation.Execute(inputX, inputY);
    Assert.That(actualResult, Is.EqualTo(expectedResult),
        "Expected operation: '{0}', with '{1}' and '{2}' to return '{3}'"
        , operation.Name
        , inputX
        , inputY
        , expectedResult
        );
}

static object[] OperationTestCases =
{
    new object[] { new Multiply(), 0, 7, 0 },
    new object[] { new Multiply(), 3, 6, 18 },
    new object[] { new Add(), 0, 0, 0 },
    new object[] { new Add(), 5, 6, 11 },
};

Results from running this test suite in NUnit

More complicated examples

If your implementations are more varied from each other than that, or you can't create a simple input/output matrix, you will have a harder time.

One option would be to feed delegates into your test case. But this is as complicated, unclear, and hard to maintain as it sounds. In such a case you'd really be better of writing separate unit test methods.

like image 20
Merlyn Morgan-Graham Avatar answered Dec 30 '22 01:12

Merlyn Morgan-Graham