Section III (use MockObject as a reconstruction technology) Using Mock Objects as a Refactoring Technique
Many people are used to think that the unit test should be completely transparent and should not change the Runtime Code to simplify the test. This point is wrong.
The first-order user of the Runtime Code and the other users should be treated with other users. If your code is not elastic, you should modify your code.
According to an example, how do you think of the following code?
[...]
Import java.util.propertyResourceBundle;
Import java.util.resourcebundle;
Import org.apache.commons.logging.log;
Import org.apache.commons.logging.logfactory;
[...]
Public Class DefaultAccountManager Implements AccountManager
{
Private static final log logger =
Logfactory.getlog (AccountManager.class);
// Note One
Public Account Findaccountforuser (String UserID)
{
Logger.debug ("Getting Account for User [" UserID "]");
ResourceBundle bundle =
PropertyResourceBundle.getBundle ("Technical");
// Note Two
String SQL = Bundle.getstring ("Find_Account_for_user");
// some code logic to load a user account using jdbc
[...]
}
[...]
}
Note 1: You get a log object generated by logfactory
Note 2: Use PropertyResourceBundle to get a SQL command.
Do you think this code is good? Let's take two points to study, they are related to the scalability and flexibility of the code. The first question is: It is impossible to use different log objects because it is generated in the class. For example, when you test, you may want to use a log to do Nothing, but you can't.
The general rule is: A class like this should be able to use a LOG object. The purpose of this class is to show JDBC logic instead of coming to the day.
The same discussion also applies to PropertyResourceBundle. He looks normal now, but what will happen when you decide to store configurations? Similarly, this class is not used to process us.
A valid design strategy is not changing when transplanting an object to other objects. The choice of peripheral objects should be controlled at the call. Basically, when you transfer to the call, decide to use Logger or configuration
Should be pushed to the previous layer. This strategy provides an extension type of code and can handle changes. Change is eternal
1. Simple Refactoring:
In order to be domain objects to propagate and reconstruct all code may be a waste of time. For a unit test, you may not be prepared to reconstruct your code
. Fortunately, there is a simple reconstruction technology that allows you to keep your original interface but allow him to spread in the domain object (perhaps no production). As a proof, let's take a look at how to reconstruct the DefaultAccountManager class.
Public class defaultAccountmanager imports accountmanager {
PRIVATE LOG LOGGER;
PRIVATE CONFIGURATION Configuration;
// Note 1
Public defaultAccountmanager ()
{
This (logfactory.getlog (defaultAccountmanager.class),
New DefaultConfiguration ("Technical"));
}
Public DefaultAccountManager (Log Logger,
Configuration Configuration)
{
THIS.LOGGER = Logger;
THIS.CONFIGURATION = Configuration;
}
Public Account Findaccountforuser (String UserID)
{
THIS.LOGGER.DEBUG ("Getting Account for User"
UserID "]");
THIS.CONFIGURATION.GETSQL ("Find_account_for_user");
// some code logic to load a user account using jdbc
[...]
}
[...]
}
Note Note 1, exchange the PropertyResourceBundle class into a new Configuration interface. This makes the code more elastic because he introduces an interface (very easy to simulate), and the Configuration implementation can be flexible (including using resource bundles). The current design is better because you can reuse the DefaultAccountManager class, which can be arbitrarily implemented for logs and configure
Interface (if you use constructor, he has two parameters). This class can be called outside. At the same time, you don't destroy the existing interface, because you only add a constructor. You preserved the original default constructor ---- still initializing the Logger and Configuration two member variables through the default implementation (Implementations).
Through this reconstruction, you have provided a trapdoor to control the domain objects from the test, you keep backward compatibility and paveling the way to rebuild the future. Call classes can use new constructor in their own way.
2 more scalability code (Allowing More Flexible Code)
In the previous section, we introduced a very famous model - control reversal (IOC), mainly because of all the external / methods
The domain object and pass all things to him. Not genetically modified by the class, but an instance is passed to a class (usually through the interface).
In the example, he means that the Log and Configuration objects are delivered to the DefaultAccountManager class, and the defaultAccountManager does not limit what example can be passed or how they are constructed. He only knows they have implemented logs and configure interfaces.
IOC ---- Control reversal definition:
Application IOC mode is meaningful in the class is to remove the initialization of a class - when this class is not immediately required - and instead of passing the necessary instance. These examples may be passed through a special constructor through a setter or as a method of parameters. Calling the code correctly settings these called class domain objects have become a responsibility.
Let's take a look at how to write a test for the FindAccountByUser method. Public void testfindaccountbyuser ()
{
MockLog Logger = new mocklog ();
// Note One
MockConfiguration Configuration = New MockConfiguration ();
Configuration.setsql ("SELECT * [...]");
// Note 2
DEFAULTACCOUNTMANAGER AM = New DefaultAccountManager (Logger, Configuration);
// Note 3
Account Account = Am.Findaccountforuser ("1234");
// perform asserts here
[...]
}
}
Note 1 Using an analog Logger that implements the log interface but does not do anything.
Note 2 Generates a MockConfiguration instance and returns a given SQL query - when calling configure.getsql.
Note 3 Generate a DefaultAcCountManager instance, you can test, pass him to the log and configure instance
You have been able to fully control your Logging and Configuration (from external code) to test, in your code. Therefore, your code is well extended and flexible (allowing any Logging and Configutation implementation) You will see more of these code refactors in this chapter and the following sections.
Finally, it is worth noting that if you write your test code first, you will automatically design your code with scalability. Scalability is a key
When you write a unit test. If you are tested first, you will not cause the code to refactor your code for scalability.