The other posts in the series:
The Legacy Code To Testable Code Series
General patterns | Accessibility | Dealing with dependencies | Advanced patterns |
---|---|---|---|
Introduction | Add setters | Extract method | Static constructors (initializers) |
Renaming | More accessors | Extract class | More static constructors |
Add overload | Introduce parameter | Instance constructors | |
Testable object | Conditionals 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:
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.
0 Comments