This article is a revised version. It was previously published at refactoring-legacy-code.net published.
This is a series of articles. You can find the other articles here:
- Simple refactorings - Part 1
- Simple refactorings - Part 2
- Simple refactorings - Part 3
- Simple refactorings - Part 4
Improve testability - Internal Test Constructor
To supplement automated tests, it is sometimes useful to use a Internal Test Constructor with a constructor parameter that can be used to pass in the dependency from outside. The following method cannot be tested well automatically, as it has a dependency on the current time, here in the form of the DateTime.Now property, has.
public string Format(string message) { return DateTime.Now + ": " + message; }
The testability can be established by calling the static DateTime.Now Method via a Func can be influenced. To do this, first introduce a field:
private Func now = () => DateTime.Now;
Then replace all DateTime.Now Calls by calls from now():
public string Format(string message) { return now() + ": " + message; }
This refactoring cannot be completely tool-supported, so caution is advised here. In any case, you should before This refactoring saves the status of the source code in version control. You should also check the status to after refactoring so that the difference in the version control corresponds exactly to this change in the source code. This makes it possible to undo this step later if an error has occurred.
To be able to influence the external behavior in the test, make the field now about the Internal Test Constructor accessible. To ensure that the behaviour of the rest of the software system is not changed by the additional constructor, add a parameterless default constructor that transfers the lambda expression for initializing the field to the internal test constructor.
public class FormattingService { private readonly Func now; public FormattingService() : this (() => DateTime.Now) { } internal FormattingService(Func now) { this.now = now; } public string Format(string message) { return now() + ": " + message; } }
Conclusion
Introduce an additional constructor with parameters to define the Testability a class to increase.
Reduce dependencies - Extract Interface
There are often direct dependencies between classes in legacy code projects. This makes automated testing more difficult. By introducing an interface with the Extract Interface Refactoring makes it easier to work with dummies in the test. In the following example, the class CheckoutService a direct dependency on the class PaymentService.
public class CheckoutService { private PaymentService paymentService = new PaymentService(); public void DoCheckout() { // .... paymentService.PayByCreditCard(cardNo, owner, pin, amount, description); } }
When executing the method DoCheckout the PaymentService and a payment is made. This is unsuitable for automated testing because communication with the payment service provider would then take place with every test run. This might still be practicable with special test data, but would have a very negative impact on the runtime and stability of the tests. Therefore, in the first step, I introduce an interface for the class PaymentService in. The result of Extract Interface:
public interface IPaymentService { void PayByCreditCard(string cardNo, string owner, string pin, double amount, string decription); }
Now the class must CheckoutServicethat the PaymentService a way of using a mockup in tests should be created. To do this, I change the usage to the interface instead of the concrete type. I also add two constructors. The public default constructor forwards its initialization to the internal "real" constructor. An instance of the PaymentService is generated, as was previously the case during field initialization:
private IPaymentService paymentService; public CheckoutService() : this(new PaymentService()) { } internal CheckoutService(IPaymentService paymentService) { this.paymentService = paymentService; }
The introduction of the additional constructors corresponds to the procedure in the previous section, in which we introduced a DateTime.Now Call by a Func had replaced. What is new here is the introduction of the interface using Extract Interface.
Conclusion
Use Extract Interfaceto generate an interface for an existing class. By using the interface instead of the concrete type, the Implementation interchangeable be made. This is important for the Automated testing helpful.