Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should you have duplicate unit tests for seperate methods that share the same private implementation?

I am building a business layer for our large enterprise application, and we're currently sitting at just under 500 unit tests. The scenario is that we have two public methods, public AddT(T) and public UpdateT(T) that both make an internal call to private AddOrUpdateT(T) since a lot of the core logic is the same between both, but not all; they are different.

Because they are separate public API (regardless of private implementation), I wrote unit tests for each API, even though they are the same. This might look like

[TestMethod]
public void AddT_HasBehaviorA() 
{
}

[TestMethod]
public void UpdateT_HasBehaviorA() 
{ 
}

Currently for this particular business object there are around 30 unit tests for adding, and 40 unit tests for updating, where 30 of the update tests are the same as the add tests.

Is this normal, or should I be abstracting the common behavior out into a public helper class that is unit tested in isolation, and the two API's just use that helper class instead of a private method that has the implementation?

What is considered a best practice for these situations?

like image 619
David Anderson Avatar asked Apr 16 '14 04:04

David Anderson


People also ask

Should unit test cover private methods?

Unit Tests Should Only Test Public Methods The short answer is that you shouldn't test private methods directly, but only their effects on the public methods that call them. Unit tests are clients of the object under test, much like the other classes in the code that are dependent on the object.

Should unit tests be in the same package?

Unit tests can be in any package. In essence they are just separate classes used to test the behaviour of the class being tested.

Should unit tests depend on each other?

Tests should never depend on each other. If your tests have to be run in a specific order, then you need to change your tests. Instead, you should make proper use of the Setup and TearDown features of your unit-testing framework to ensure each test is ready to run individually.

When Should unit testing be performed choose two?

Unit testing is the first testing phase and it is practiced before moving to the phase of integration testing. Hence, before moving for the next testing level, make sure to fix all the identified bugs in the unit testing phase.


2 Answers

First of all, it's important to understand why you want to cover those methods by unit tests, because that's going to influence the answer. Only you and your team knows this, but if we assume that at least part of the motivation for unit testing is to get a trustworthy regression test suite, you should test the observable behaviour of the System Under Test (SUT).

In other words, unit tests should be black box tests. The tests shouldn't know about the implementation details of the SUT. Thus, the naive conclusion you could draw from this is that if you have duplicate behaviour, you should also have duplicate test code.

However, the more complex your system becomes, and the more it relies on common behaviour and strategies, the harder it becomes to implement this testing strategy. This is because you'll have a combinatorial explosion of possible ways through the system. J.B. Rainsberger explains it better than I do.

A better alternative is often to listen to your tests (a concept popularized by GOOS). In this case, it sounds like it would be valuable to extract the common behaviour into a public method. This, however, doesn't itself solve the problem of combinatorial explosion. While you can now test the common behaviour in isolation, you also need to prove that the two original methods (Add and Update) use the new public method (instead of, e.g., some copy-and-pasted code).

The best way to do that is to compose the methods with the new Strategy:

public class Host<T>
{
    private readonly IHelper<T> helper;

    public Host(IHelper<T> helper)
    {
        this.helper = helper;
    }

    public void Add(T item)
    {
        // Do something
        this.helper.AddOrUpdate(item);
        // Do something else
    }

    public void Update(T item)
    {
        // Do something
        this.helper.AddOrUpdate(item);
        // Do something else
    }
}

(Obviously, you'll need to give the types and methods better names.)

This enables you to use Behaviour Verification to prove that the Add and Update methods correctly use the AddOrUpdate method:

[TestMethod]
public void AddT_HasBehaviorA() 
{
    var mock = new Mock<IHelper<object>>();
    var sut = new Host<object>(mock.Object);
    var item = new object();

    sut.Add(item);

    mock.Verify(h => h.AddOrUpdate(item));
}

[TestMethod]
public void UpdateT_HasBehaviorA() 
{ 
    var mock = new Mock<IHelper<object>>();
    var sut = new Host<object>(mock.Object);
    var item = new object();

    sut.Update(item);

    mock.Verify(h => h.AddOrUpdate(item));
}
like image 107
Mark Seemann Avatar answered Sep 17 '22 15:09

Mark Seemann


It is a good practice to avoid duplication as much as possible. This aids code readability and maintainability (and probably other *ablilities ;-)). It also makes testing easier as you can start to test the common functionality in one place and not have to have so many duplicate tests. Also, duplication in tests is bad as well.

Another best practice is to only write unit tests for functionality that has unique logic. If in your Add and Update methods, you are only calling another method, there is no need to write unit tests at that level, you should be focusing on the called method.

Which brings up back to the initial point of not duplicating code and if you have private methods that share duplicate code, it may be a good idea to break it out into something else that you can run tests against.

like image 27
jensendp Avatar answered Sep 18 '22 15:09

jensendp