Wednesday, January 16, 2008

Unit Testing: Gateways

In an earlier post I gave an overview of Unit Testing and TDD. Now I'm going to start drilling down into some actual examples of how to structure your code to get the most out of Unit Testing.

In the last post I made the case that Unit Testing improved your object oriented design. I stand by that. But there is a stumbling block. You can still write badly designed code and add badly designed unit tests.

So how do you know what's bad? Just answer these two questions:
  1. Are your tests testing exactly one thing?
  2. "Does the object you're testing serve exactly one purpose?

If the answer to both questions is yes, you're doing great. If not, you may need to refactor.

Of course, those questions are easy to ask, but much harder to answer.

My first example will have to do with code that retrieves data from a database or some other data store. When first starting out people tend to write a classes that include data retrieval along side various "business logic" routines. For example, you may create a class that takes a data table as input, validates it for certain restrictions, and inserts it into the database.

This class does two things, it validates and it inserts. If you unit tested it your tests would be testing two things, that the validation was performed correctly and that the insert into the database was performed correctly. So, how do we refactor this?

Use the "Gateway" pattern. A Gateway is a class which serves as the path to and from your data store. The, err, gateway to your data. I always create one Gateway per "functional unit." So in this case because I'll have a Validator class and a ValidatorGateway. So far our example only requires the ValidatorGateway to have an insert method.

Once we perform this refactoring we'll unit test our Validator and we'll use nmock to mock out our dependency on the database by providing a mock Gateway (through dependency injection). Now when we Unit Test our Validator class we wont be testing that it inserted into the database correctly, only that it intended to insert into the database correctly.

However, isn't that still two different things? Validation and the intention to insert? It is. So we should probably take the Gateway out of the Validator all together. Instead, our Validator will just validate the data and tell us if it passed or failed. Then some other code will utilize the Validator and the Gateway.

Clearly those two simple questions can be very powerful in guiding your design if you take the time to ask them. Furthermore, the Gateway pattern is a very simple and very flexible concept that can easily be applied to nearly any situation. As a freebie, the gateway pattern also gives you the ability to swap out what kind of data store you're using very easily. Need to switch from talking directly to the database to talking to a web service? Just update the gateway and you're done.

No comments:

Post a Comment