TDD and inheritance

I am working on my first project using TDD and have applied a bit of brick wall when it comes to inheritance.

For example, if I have something like this

public interface IComponent { void MethodA(); void MethodB(); } public class Component : IComponent { public virtual void MethodA() { // Do something } public virtual void MethodB() { // Do something } } public class ExtendedComponent : Component { public override void MethodA() { base.MethodA(); // Do something else } } 

then I cannot test ExtendedComponent in isolation because it depends on Component.

However, if I use composition to create an ExtendedComponent, like this

 public class ExtendedComponent : IComponent { private readonly IComponent _component; public ComponentB(IComponent component) { _component = component; } public virtual void MethodA() { _component.MethodA(); // Do something else } public virtual void MethodB() { _component.MethodB(); } } 

Now I can test ExtendedComponent in isolation, mocking the wrapped IComponent.

The disadvantage of this approach is that if I want to add new methods to IComponent, then I have to add new methods to Component and ExtendedComponent and any other implementations, which can be many. Using inheritance, I could just add a new method to the base component and it would not break anything.

I really want to be able to test cleanly, so I prefer a compositional route, but I am concerned that the unit test capability is not a good reason to always use composition over inheritance. In addition, adding functionality at a basic level will require the creation of many tedious delegation methods.

I would really like advice on how other people approached this issue.

+4
source share
4 answers

your approach using composition in all practical situations shows how most compilers implement inheritance, so you don’t get anything but pay big expenses (many code templates). So stick to inheritance when there is a relationship and composition, when there is a vessel with relations (this, of course, is not gold and not the only rules).

+2
source

You do not need to worry about testing the extended component “in isolation”, because it does not “depend” on the component, it is a component (at least the way you encoded it).

All the tests that you originally wrote for the component class are still fine and check all the unchanged actions in the extended class. All you have to do is write new tests that test the added functionality of the extended component.

 public class ComponentTests{ [Fact] public void MethodADoesSomething(){ Assert.xxx(new Component().MethodA());//Tests the component class } [Fact] public void MethodBDoesSomething(){ Assert.xxx(new Component().MethodB());//Tests the component class } } public class ExtendedComponentTests{ [Fact] public void MethodADoesSomething(){ Assert.xxx(new ExtendedComponent().MethodA());//Tests the extended component class } } 

You can see from the top that the MethodA function is tested for both the component and the extended component. Although the new functionality is tested only for ExtendedComponent.

+2
source

The main idea here is that you can inherit on the unit test side . In this scenario, I use the following approach. I will have a parallel inheritance hierarchy in unit test cases. eg.

 [TestClass] public abstract class IComponentTest { [TestMethod] public void TestMethodA() { // Interface level expectations. } [TestMethod] public void TestMethodB() { // Interface level expectations. } } [TestClass] public class ComponentTest : IComponentTest { [TestMethod] public void TestMethodACustom() { // Test Component specific test, not general test } [TestMethod] public void TestMethodBCustom() { // Test Component specific test, not general test } } [TestClass] public class ExtendedComponent : ComponentTest { public void TestMethodACustom2() { // Test Component specific test, not general test } } 

Each abstract test class or concrete class considers expectations at its own level. Thus, extensible and maintainable.

+2
source

You are right - using composition over inheritance where it does not fit is not a way. Based on the information you provided here, it is not clear which is better. Ask yourself which one is more appropriate in this situation. Using inheritance, you get polymorphism and virtualization of methods. If you use composition, you effectively separate your front-end logic from the isolated back-end logic - this approach is simpler because changing the base component does not have a ripple effect on the rest of the code, as inheritance often does.

In general, this should not affect the way you test your code. There are many possibilities for testing, but this should not affect the design template you choose.

0
source

Source: https://habr.com/ru/post/1440269/


All Articles