1. What's wrong? The objective of unit testing is to verify only one method at a time, with small steps forward and fine-grained tests. However, if a method is dependent on other hard-to-manipulate things, such as network connections and database connections, or servlet container, what should we do? What if your test depends on other parts of the system, or even multiple parts of the system? In this case, if you are not careful, you may eventually find that you have initialized almost every component of the system, this is just to create enough runtime environments for a test to run. After a long time, it seems that we are against the original intention of the test. This not only consumes time, but also introduces a lot of coupling factors to the test process. For example, someone may be eager to change an interface or a table in the database. Suddenly, your humble unit test is mysterious. After this happens several times, even the most patient developer will be discouraged, and even eventually give up all the tests, then the consequences will be unimaginable. Let's look at a more specific situation: in the actual object-oriented software design, we often encounter such a situation. After we build a realistic object, objects are implemented through a series of interfaces. This is the most natural thing in object-oriented design, but with the development of software testing requirements, this will produce some minor problems. For example, user a now obtains an interface provided by user B and implements his own needs based on this interface. However, after user a compiles his own code, he wants to simulate and test it, what should we do? This is also a very practical issue. Can we simply implement a proxy class for this interface to test the simulation and expect the code to generate its own results? Fortunately, there is a test mode that can help us: mock objects. The mock object is a replacement of the real object during the debugging period. 2. Do I need a mock object now? Tim macinnon gave us some suggestions on when to use the mock object: ----- The real object has uncertain behavior (produce unpredictable results, such as stock quotations) ----- Real objects are difficult to create (such as specific Web containers) ----- Some behaviors of real objects are difficult to trigger (such as network errors) ----- The real situation makes the program run slowly ----- Real objects have user interfaces ----- The test needs to ask how the real object is called (for example, the test may need to verify whether a callback function is called) ----- Real objects do not actually exist (this is a common problem when dealing with other development groups or new hardware systems) 3. How to implement a mock object? When testing with a mock object, we need three steps in total: ----- Use an interface to describe this object ----- Implement this interface for the product code ----- Implement this interface in the mock object for the purpose of testing Once again, we see the importance of interface-based programming, because the tested Code only references objects through interfaces, therefore, it does not know whether the referenced object is a real object or a mock object. Let's take a look at the actual example: an alarm is triggered by time, after five o'clock P.M., the audio files will be played to remind everyone to get off work. If we want to use real objects for testing, we have to wait until five o'clock P.M. and put our ears next to the speaker... we don't want to be so stupid. We should use the mock object for testing, so that we can simulate the control time, instead of waiting for the clock to go to five o'clock P.M. The following code is used:
public interface Environmental { private boolean playedWav = false; public long getTime(); public void playWavFile(String fileName); public boolean wavWasPlayed(); public void resetWav(); } |
Real implementation code:
public class SystemEnvironment implements Environmental { public long getTime() { return System.currentTimeMillis(); } public void playWavFile(String fileName) { playedWav = true; } public boolean wavWasPlayed() { return playedWav; } public void resetWav() { playedWav = false; } } |
The following is a mock object:
public class MockSystemEnvironment implements Environmental { private long currentTime; public long getTime() { return currentTime; } public void setTime(long currentTime) { this.currentTime = currentTime; } public void playWavFile(String fileName) { playedWav = true; } public boolean wavWasPlayed() { return playedWav; } public void resetWav() { playedWav = false; } } |
|