We had a kind of duplication in our tests that we didn’t know how to deal with. The refactoring “Introduce Polymorphic Creation with Factory Method” explained by Joshua Kerievsky in his brilliant book “Refactoring to Patterns” gave me the solution to avoid duplicated tests.
[TestFixture] public class
ChangingColorWithImplicitExclusionsShould : ConfigurationTests {
[Test] public void
not_allow_change_when_its_compulsory_has_the_same_family_than_a_configured_equipment() {
CatalogBuilder
.AddEquipmentWithFamily("PR1", "Radio")
.AddEquipmentWithFamily("PR2", "Radio")
.AddColor("red", WithEquipmentsCompulsory("PR2"));
var configuration = Agiven.ModelConfiguration()
.With(Agiven.ConfigEquipment("PR1"))
.Build();
Expect.CallTo(() => ExecuteChangeColor("red", configuration))
.ToThrow();
}
}
[TestFixture] public class
ChangingInteriorWithImplicitExclusionsShould : ConfigurationTests {
[Test] public void
not_allow_change_when_its_compulsory_has_the_same_family_than_a_configured_equipment() {
CatalogBuilder
.AddEquipmentWithFamily("PR1", "Radio")
.AddEquipmentWithFamily("PR2", "Radio")
.AddInterior("xyz", WithEquipmentsCompulsory("PR2"));
var configuration = Agiven.ModelConfiguration()
.With(Agiven.ConfigEquipment("PR1"))
.Build();
Expect.CallTo(() => ExecuteChangeInterior("xyz", configuration))
.ToThrow();
}
}
Tests are very similar, the differences are in lines 8 and 25, and also in lines 13 and 30. First tests tries to change the color of a configuration whereas the second one tries the interior. Part of the handling business logic is the same. This is just one scenario but we had many of them, with same expected behavior for color, interior, equipment, and more. Eventually there was a lot of “duplication”.
After refactoring, we have a base abstract class with the tests, exposing template methods that child classes have to implement in order to populate the catalog and also to execute the corresponding action:
[TestFixture] public abstract class
CantChangeConfigurationBecauseThereisImplicitExclusionWhen : ConfigurationTests {
[Test] public void
its_compulsory_has_the_same_family_than_a_configured_equipment() {
CatalogBuilder
.AddEquipmentWithFamily("PR1", "Radio")
.AddEquipmentWithFamily("PR2", "Radio");
AddImplicitExclusionItemWithCompulsories("PR2");
var configuration = Agiven.ModelConfiguration()
.With(Agiven.ConfigEquipment("PR1"))
.Build();
Expect.CallTo(ChooseConflictiveItem(configuration))
.ToThrow();
}
protected abstract void AddImplicitExclusionItem(string code);
protected abstract Func ChooseConflictiveItem(ModelConfiguration configuration);
}
[TestFixture] public class
ChangingColor : CantChangeConfigurationBecauseThereisImplicitExclusionWhen
private const string itemCode = "irrelevant";
protected override void AddImplicitExclusionItem(string code){
CatalogBuilder
.AddColor(itemCode, WithEquipmentsCompulsory(code));
}
protected override Func ChooseConflictiveItem(ModelConfiguration configuration){
return () => ExecuteChangeColor(itemCode, configuration);
}
}
The base class “ConfigurationTests” contains just helper methods such as ExecuteChangeColor, or ExecuteChangeInterior, but no tests at all. Otherwise tests would run twice.