Legacy Code to Testable Code #11: More Static Constructors
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:
class BankAccessHelper
{
private static List<Bank> Banks;
static BankAccessHelper()
{
ConnectionData cd = ConnectionData.ReadConfiguration();
DB.Connect(cd);
Banks = new List<Bank>();
DB.FillBankList(Banks);
}
}
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:
class BankAccessHelper
{
private static List<Bank> Banks;
public static void Initialize()
{
ConnectionData cd = ConnectionData.ReadConfiguration();
DB.Connect(cd);
Banks = new List<Bank>();
DB.FillBankList(Banks);
}
}
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:
class BankAccessHelper
{
private static List<Bank> Banks;
public static void Initialize()
{
ConnectionData cd = ConnectionData.ReadConfiguration();
DB.Connect(cd);
Banks = new List<Bank>();
DB.FillBankList(Banks);
}
public static void SetBanks(List<Bank> bankList)
{
Banks = bankList;
}
}
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:
class BankAccessHelper
{
private static List<Bank> Banks;
public static void Initialize()
{
DB.Connect();
Banks = new List<Bank>();
DB.FillBankList(Banks);
}
public static void SetBanks(List<Bank> bankList)
{
Banks = bankList;
}
}
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:
class DB
{
public static void Connect()
{
ConnectionData cd = ConnectionData.ReadConfiguration();
Connect(cd);
}
public static void Connect(ConnectionData cd)
{
...
}
}
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.
Reference: | Legacy Code to Testable Code #11: More Static Constructors from our NCG partner Gil Zilberfeld at the Geek Out of Water blog. |