Picture of Stefan Lieser
Stefan Lieser

Simple refactorings - Part 5

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:

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.

 

Our seminars

course
Clean Code Developer Basics

Principles and tests - The seminar is aimed at software developers who are just starting to deal with the topic of software quality. The most important principles and practices of the Clean Code Developer Initiative are taught.

to the seminar "
course
Clean Code Developer Trainer

Conducting seminars as a trainer - This seminar is aimed at software developers who would like to pass on their knowledge of Clean Code Developer principles and practices or Flow Design to others as a trainer.

to the seminar "
course
Clean Code Developer CoWorking

Online CoWorking incl. coaching -
We are often asked what developers can do to keep up with the topic of clean code development. Our answer: Meet up with other Clean Code Developers online on a regular weekly basis.

to the seminar "

Leave a Comment

Your email address will not be published. Required fields are marked *

en_USEnglish