Should you duplicate unit tests for individual methods that use the same private implementation?

I am creating a business layer for our large enterprise application, and now we are sitting a little less than 500 unit tests. The scenario is that we have two methods public , public AddT(T) and public UpdateT(T) , which both make an internal call to private AddOrUpdateT(T) , since most of the main logic is the same between both, but not all; they are different.

Since they are a separate public API (regardless of private implementation), I wrote unit tests for each API, although they are the same. It might look like

 [TestMethod] public void AddT_HasBehaviorA() { } [TestMethod] public void UpdateT_HasBehaviorA() { } 

Currently, there are about 30 unit tests for adding and 40 unit tests for updating for this business object, where 30 updating tests coincide with adding tests.

Is this normal, or should I abstract the general behavior into a public helper class that is tested separately, and the two APIs just use this helper class instead of the private method that has the implementation?

What is considered best practice for these situations?

+7
c # unit-testing tdd
source share
3 answers

First of all, it is important to understand why you want to cover these methods with unit tests, because this will affect the answer. Only you and your team know this, but if we assume that at least part of the motivation for unit testing is to get a reliable set of regression tests , you should check the behavior of the observed behavior of the system under test (SUT).

In other words, unit tests should be black boxes . Tests do not need to know about the details of the implementation of SUT. So the naive conclusion you could draw from this is that if you have duplicate behavior, you should also have a repeating test code.

However, the more complex your system, the more it depends on the general behavior and strategies, the more difficult it is to implement this testing strategy. This is because you will have a combinatorial explosion of possible paths through the system. JB Rainsberger explains this better than me .

The best alternative is often to listen to your tests (a concept popularized by GOOS ). In this case, it seems to be useful to extract the general behavior into a public method. This, however, does not in itself solve the problem of the combinatorial explosion. While you can test the general behavior in isolation, you also need to prove that the two original methods ( Add and Update ) use the new public method (and not, for example, some code with copy and paste).

The best way to do this is to compose methods with a 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 need to give type names and methods better names.)

This allows you to use Behavior 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)); } 
+4
source share

It is good practice to avoid duplication as much as possible. This facilitates code readability and maintainability (and possibly other * skills ;-)). It also makes testing easier, as you can start testing general functionality in one place and not have so many repeated tests. In addition, duplication in tests is also bad.

Another best practice is to write only unit tests for features that have unique logic. If in the add and update methods you call only another method, there is no need to write unit tests at this level, you should focus on the called method.

Which goes back to the starting point of non-duplicate code, and if you have private methods that use duplicated code, it might be a good idea to break it down to something else that you can run tests.

+1
source share

"Since a lot of basic logic is the same between both, but not all, they are different."

I think you need separate unit tests for this, because, as you say, they are not quite the same. Also, what if you later change the implementation to call two different methods? Then your unit tests will check only one of them, because they are related to the implementation details of these two methods. The tests will pass, but you can enter an error.

I think the best approach is to test both methods, but add helper methods / classes to do the general work.

+1
source share

All Articles