Now lets start using the Fakes Framework by implementing a real live scenario. In the following example I am going to show you some legacy code, which has some software design flaws. I am going to apply TDD and SOLID concepts to improve code quality and then use Fakes Framework Stubs for testing it.
Then in the next blog post of the series I am going to show you how to test source code without any code modifications by using Fakes Framework Shims. This is especially interesting for code that cannot be modified or that requires too many modifications and which is thus too expensive to improve.
Refactoring badly designed source code
The BusinessService class has the responsibility to store Deals in the database if they are valid :
As you see it uses two other business classes : the DealValidator class for deal validation and the DealRepository class for saving a deal to the database. If a deal is not valid an exception is thrown. Furthermore, a bool with the False value is returned, if a deal cannot be saved to the database.
Here are the implementations of the classes, without any actual source code. The methods only contain a not implemented exception. We are going to mock those objects later, so no need to implement them completely here. Even more, by doing it like this, we have the opportunity to validate that they are not actually called. We do not want to have them called, when we unit test the BusinessService class, since they are part of its external dependencies (as explained in the first part of the blog series).
The Deal class is a simple data transfer object (DTO) :
Lets analyze the software design of this simple example : the BusinessService class and the DealValidator, DealRepository and Deal classes are tightly coupled as it is implemented here. This is bad design and a violation of the SOLID principles. To be more specific it violates the Dependency Inversion Principle (DIP).
The first thing to do now, is to modify the code to make it adhere to the DIP principle and thus improving its quality. When applying TDD and writing unit tests, you have to isolate the code that has to be tested (System Under Test). So if we want to unit test all the methods of the class, we have to decouple its methods from the rest of the source code. In the end the DIP principle gets automatically respected.
To achieve this goal we are going to do some dependency injection. First we use Interfaces to serve as abstraction layer between the BusinessService class and the DealValidator, DealRepository and Deal classes :
The business classes and interfaces code looks now like this :
You are now ready to use the Fakes Framework to test your code. This is what we are going to do in the next step.
Fakes Framework Stubs
Now that the BusinessService class has been completely decoupled, we can use Fakes Framework Stubs for unit testing it. After creation of a new unit test project and after adding a reference to our business code project, we generate the Fakes Framework library by right-clicking on the reference and selecting “Add Fakes Assembly”.
Visual Studio 2012 automatically adds the following to the project :
- A library called CodeSample.Fakes, which contains the auto-generated Stubs and Shims classes. Those are going to be used as proxies in our unit tests.
- A reference to Microsoft.QualityTools.Testiong.fakes.dll, which contains all the core components of the Fakes Framework.
- A Fakes folder, that contains a file called xml CodeSample.fakes. This file allows to impact the automatic generation of Stubs and Shims.
For our example we declare that Stubs have only to be generated for Interfaces :
We may now implement unit tests using the Stubs that were auto-generated above in the CodeSample.Fakes.dll.
The following unit test will return True when the TrySave(…) method is called and a deal has been validated and saved (which is the case in our example) :
Lets see how that works in detail : in the Setup step we initialize the IRepository and IValidator interfaces by using the corresponding Stubs. Note that the classname is “Stub” and then the name of the interface to substitute.
Then we simulate a save and validation operation that has been successfully finished. To do that we define that the Save(…) and IsValid(…) methods return always True.
In terms of naming conventions, methods are suffixed by the type name of their input parameters. In our example the IsValid(…) and Save(…) methods have both as input parameter IDataObjet, so they are named IsValidIDataObject(…) and SaveIDataObject(…).
Finally, we use the Stubs from above to instantiate a BusinessService object (also doing some constructor injection) and we test the TrySave(…) method to validate that the result is conform with our scenario.
In the next example, we test that the TrySave(…) methods calls a validator :
To achieve this, we are using the variable validatorWasCalled, which we initialize to the False value. The test is validating that the variable is set to True during the processing in the corresponding Stub when calling the TrySave(…) method.
No comments:
Post a Comment