Avoid Test duplication if a function created via TDD has an implementation detail that is later used in other functions
Lets say I have an API with two functions: CreateNewMachine(CreateMachineCommand createMachine); EditExistingMachine(EditMachineCommand editMachine); The internal logic of both functions should be hidden from the outside, but of course they should be testable: Unit tests should call the functions in an isolated environment and the UI will call them in a production environment. So my command could look like this: class CreateMachineCommand { public PartsCollection Parts {get; set;} public ThrowIfCommandInvalid() { Ensure.IsNotNull(Parts); Parts.ThrowIfNotValid(); } // Other stuff... } and for the Edit-Function something similar: class EditMachineCommand { public PartsCollection Parts {get; set;} public ThrowIfCommandInvalid() { Ensure.IsNotNull(Parts); Parts.ThrowIfNotValid(); } // Other stuff... } In my scenario the "PartsCollection" is a complex collection that contains a lot of validation logic. This validation logic needs to be unit tested. So far, so good. Lets say I start to develop now. And lets also say I use TDD for my approach. Implementing the CreateNewMachine function will take a long time. I need to write 50 unit tests against CreateNewMachine until the TDD approach finally leads to a rocksolid testing suite, that covers all invalid commands. But 40 of those unit tests are only testing one line: But if I continue to create the EditExistingMachine call, I need to duplicate all the 40 unit tests, just because it also calls the ThrowIfNotValid-method of Parts. This cannot be a good solution. So what could I do: I could just leave out the 40 tests and make sure that ThrowIfNotValid is called also by the EditMachine-call via a Spy (or mock). I could create 40 unit tests not against the public API, but against the ThrowIfNotValid function directly. Then I need two spies (one for Create and one for Edit) to make sure that ThrowIfNotValid is called by both functions. I do not like both approaches: The first approach uses a spy, what leads to a high coupling between the test code and the production code. Spies are fine for me, but I use them only if I need to make sure that an external third party (like a database, API etc.) is called. The second approach is even worse. It uses two spies, but it also hurts the TDD approach, because it tests an implementation detail that should hide behind a public interface directly what might lead to fragile tests. What do you think?
Lets say I have an API with two functions:
CreateNewMachine(CreateMachineCommand createMachine);
EditExistingMachine(EditMachineCommand editMachine);
The internal logic of both functions should be hidden from the outside, but of course they should be testable:
Unit tests should call the functions in an isolated environment and the UI will call them in a production environment.
So my command could look like this:
class CreateMachineCommand
{
public PartsCollection Parts {get; set;}
public ThrowIfCommandInvalid()
{
Ensure.IsNotNull(Parts);
Parts.ThrowIfNotValid();
}
// Other stuff...
}
and for the Edit-Function something similar:
class EditMachineCommand
{
public PartsCollection Parts {get; set;}
public ThrowIfCommandInvalid()
{
Ensure.IsNotNull(Parts);
Parts.ThrowIfNotValid();
}
// Other stuff...
}
In my scenario the "PartsCollection" is a complex collection that contains a lot of validation logic.
This validation logic needs to be unit tested.
So far, so good.
Lets say I start to develop now.
And lets also say I use TDD for my approach.
Implementing the CreateNewMachine function will take a long time.
I need to write 50 unit tests against CreateNewMachine until the TDD approach finally leads to a rocksolid testing suite, that covers all invalid commands.
But 40 of those unit tests are only testing one line:
But if I continue to create the EditExistingMachine call, I need to duplicate all the 40 unit tests, just because it also calls the ThrowIfNotValid-method of Parts.
This cannot be a good solution.
So what could I do:
- I could just leave out the 40 tests and make sure that ThrowIfNotValid is called also by the EditMachine-call via a Spy (or mock).
- I could create 40 unit tests not against the public API, but against the ThrowIfNotValid function directly. Then I need two spies (one for Create and one for Edit) to make sure that ThrowIfNotValid is called by both functions.
I do not like both approaches:
- The first approach uses a spy, what leads to a high coupling between the test code and the production code. Spies are fine for me, but I use them only if I need to make sure that an external third party (like a database, API etc.) is called.
- The second approach is even worse. It uses two spies, but it also hurts the TDD approach, because it tests an implementation detail that should hide behind a public interface directly what might lead to fragile tests.
What do you think?