Legacy Code to Testable Code #11: MOAR Static Constructors

Standard

The other posts in the series:

The Legacy Code To Testable Code Series

General patternsAccessibilityDealing with dependenciesAdvanced patterns
IntroductionAdd settersExtract methodStatic constructors (initializers)
RenamingMore accessorsExtract classMore static constructors
Add overloadIntroduce parameterInstance constructors
Testable objectConditionals to guard blocks

Where we last left off, we discussed how to dismantle the static constructor (or initializer) booby traps. And I promised you an example. I’ll do that in C#, but the operations apply to any language that uses these constructs.

Before I do that I’ll remind you main problem here: Static initializers are used as short cuts for initialization of a type, before any instances have been created. The price is that they are called by the run-time. As they grow more complex and have more dependencies, the tests need to take that into account. That means assuming and taking into account when the calls take place, and mitigating those calls if needed.

So our job is to make things accessible and replaceable.

Here’s a very helpful BankAccessHelper class:

As we can see, our class has a nice static initializer that reads database configuration data, creates the connection, creates the singleton collection of Banks and fills up the list from the database.

Once initialized all operations of the BankAccessHelper will be based on this initialization. This may be enough for production, but if wanted two separate initialization for two tests, we’re screwed – initialization takes place only once.

Let’s start. The easiest way to do this, is to introduce a static Initialize method and move the content of the static constructor there:

Presto! Now we have control of when to call the Initialize method, if at all. For testing purposes, we can add an accessor the Banks member:

Now we can either initialize from the database, or supply our own list. This makes the class more testable for different scenarios.

But if we’re already making changes, let’s see what we can improve. The first candidate for separation is the database access. While the DB class static calls are obviously in a separate class, there is the configuration issue.

Why would our BankAccessHelper need to read the configuration and pass it to the DB class? One of the possible changes is to move the ConnectionData reading it the DB.Connect method.

So our class will look like:

Side note: Yes, this makes the DB less testable, (which is not the focus of our example, anyway). But you can keep the “overload” to keep it testable (not unlike the Add Overload refactoring), like this:

So we’ve decoupled the BankAccessHelper from the ConnectionData class. This doesn’t seem too help in testability, right? We still have control on the creation of the Banks list as before.

What if I told you the ConnectionData has some nasty code in its static constructor? One that we don’t know, or really care, when it gets called?

Getting rid of dependencies in our tested classes is important for testing. That’s true for the explicit calls, and even more so for the implicit ones. We want to minimize those pot holes as much as we can.

Static constructors are fun and all, but wait until next time, when we’ll experience of joy of code in instance constructors.

You won’t believe what happens next.

 

Leave a Reply

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