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?
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.
Unit tests can be in any package. In essence they are just separate classes used to test the behaviour of the class being tested.
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.
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.
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));
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With