Make your legacy code testable again

Photograph by Mulyadi on Unsplash

In all probability the vast majority of software program builders know phrases corresponding to Unit Testing and TDD very properly. Builders staffed on tasks work onerous and do their greatest to ship options that fulfill enterprise necessities.

Nonetheless, typically, as a consequence of surprising difficulties (underestimated consumer story/altering necessities/upcoming deadline/too small crew/lack of information — choose one), technical debt can emerge on the horizon. This will likely appear harmless initially, however the greater our debt is, the probability of bugs is getting extra severe.

On this article, I’m going to concentrate on one specific scent of technical debt, which is untestable code.

However first, brief definition: we will say the code is untestable when it’s not possible, or it’s very onerous to cowl that code with unit/integration assessments.

Personally, I prefer to study from examples probably the most, so let’s think about a hypothetical case associated to the insurance coverage business.

To have a greater image of what the declare registration course of appears to be like like, roughly talking, we will divide the entire declare registration course of into a number of smaller components:

  1. discover the coverage — sometimes there’s a separate system holding prospects’ insurance policies
  2. choose a correct protection kind from the coverage — for instance, if we hit another person’s automobile, we will need to have Motor Third Celebration Legal responsibility protection on our coverage; then again, if the hail broken our automobile, there’s a separate protection kind for this circumstance — so, if our coverage doesn’t have a coverage-relevant to the state of affairs that occurred on the street, the insurer received’t pay us any compensation
  3. accumulate information essential to determine the insured, claimant, automobiles concerned, and so forth
  4. Generally, to make the entire declare dealing with course of go extra easily, it’s good to make the most of an exterior service offering extra details about the automobiles concerned

So, understanding this, you at the moment are an skilled, and The Enterprise is eager to present you new challenges — certainly one of them sounds as follows: Implement a brand new declare registration course of.

  • You’ve been growing a Java software for an insurance coverage firm for dealing with motor claims
  • The requirement is to permit claimants to register a declare that’s MTPL (Motor Third Celebration Legal responsibility)
  • Claimants might be offering us car particulars, like model, mannequin, VIN (Car Identification Quantity)
  • To hurry up the general means of declare dealing with, the enterprise desires you to make use of a fancy-third-party-service that decodes VIN, in order that declare handlers will obtain extra details about automobiles concerned

Now suppose you bought the necessities, and also you began the implementation half proper off the bat:

Preliminary implementation

You’ve simply completed the implementation, clicked by the UI, the whole lot appears to be like wonderful, and hooray! You could have time to look at humorous canine 🙂

Photograph by Roberto Nickson on Unsplash

The excellent news is the Enterprise accepted the answer, the unhealthy information is the Enterprise doesn’t know (and maybe you neither) that technical debt obtained elevated by not overlaying the launched performance with unit/integration assessments.

What’s fallacious with the code above?

  1. Code is just not coated by assessments
  2. If you wish to write unit assessments afterward, you’ll see calling RegisterMotorClaimService#registerClaim() inside assessments tries to name the coverage system and VIN decoding service

Level primary is self-explanatory. It’s by no means too late to cowl code with assessments, so let’s concentrate on level quantity two as a substitute.

By way of integration assessments, you possibly can say:

Hey, however new PolicyService() in all probability comprises all required data to name the right service, like URL, username, password and different information. And possibly the constructor will get this data from database or some properties file, so we will distinguish PROD setting from TEST setting.

In the case of integration assessments, in actual fact, this can be the case, so we might write them. Nonetheless, in case of unit assessments, issues get a bit extra sophisticated.

How can we write unit assessments which can be remoted from exterior environments and providers, in order that at any time when we run them, we’re positive they received’t fail as a consequence of coverage system set up or as a result of the VIN decoding service is down?

The reply is: to permit the performance below take a look at to make use of faux dependencies. In different phrases, inform the category to make use of a special model of a service, that’s proof against failures, and furthermore, it may all the time return what we would like. With this method, you possibly can put together take a look at responses from the coverage system, and from VIN decoding service. This, in flip, will will let you write assessments that don’t care about interruptions in entry to providers, thus your assessments received’t randomly fail.

Some individuals have a tendency to put in writing code within the “hearth and overlook” vogue, however I personally discover refactoring can provide a variety of satisfaction, particularly once I can cowl it with some assessments — there’s nothing higher than inexperienced shade within the take a look at report :).

As a result of we already perceive the results of our earlier resolution, let’s have some enjoyable with refactoring.

To make the code above testable once more, we have to determine exterior dependencies, that we’d prefer to isolate, in our case SomePolicyService and SomeVinDecodingService.

Step 1: Present dependencies from exterior

Earlier than offering dependencies from exterior
After refactoring

Utilizing this method, you possibly can do away with hardcoded dependencies and supply them from the surface by constructor injection in RegisterMotorClaimService

In case you use libraries like Mockito, you can probably end the refactoring course of proper right here, as a result of your mocking library ought to will let you mock concrete courses.

If for some purpose, nonetheless, you might be unable to mock concrete courses, proceed to step #2.

Step 2: Extract interfaces for dependencies

It is advisable carry out this additional step should you’re unable to mock concrete courses, or should you can’t use mocking libraries in any respect so that you just’re going to mock objects manually.

Let’s create then interfaces that might be applied by SomePolicyService and SomeVinDecodingService, respectively.

Extracted interfaces for providers

The very last thing is to implement these interfaces in our current courses:

Companies implementing extracted interfaces

Step 3: Change the kind of the handed dependencies

In case you’ve carried out step #2, you additionally want to vary the parameter forms of objects handed by way of constructor in RegisterMotorClaimService. Do it by altering parameter sorts from concrete courses to their interface equivalents.

After:

Altering parameter sorts from concrete courses to interfaces

By doing this, you possibly can instantiate RegisterMotorClaimService with actual dependencies for the PROD setting, or cross faux dependencies for unit assessments.

Step 4: Write a pattern take a look at

Testing enterprise logic offering faux dependencies

On this take a look at, I mock providers manually by creating nameless implementations of them.

Then I cross these dependencies to our service below the take a look at RegisterMotorClaimService.

Lastly, I confirm if the registered declare is related to a coverage with the given coverage quantity.

To summarize:

  • we must always think about the best way to write implementation to be testable earlier than writing the precise code
  • dependencies needs to be supplied from exterior the category, so we will simply exchange them for unit testing functions
  • it’s by no means too late to refactor code and add assessments for current performance

More Posts