A word derived from the English word "double", which means a substitute for a performer or a shadow warrior. Generally called mock or stub.
In the unit test of the system, the test target is There are few cases where it does not depend on other classes or external elements. A test that has a dependency means that it has tested not only the target but also the dependency.
The advantage is that the test is performed in the same state as when the system is running. There is an aspect that the value of the content guaranteed by the test results increases. On the other hand, the disadvantage is that the scope of cause analysis becomes wider when the test fails. If the processing result of the dependent object cannot be predicted, the accuracy of verification will drop.
As a countermeasure against the above disadvantages, instead of the factors that affect the test There is a way to increase the independence of the test by using a substitute that is easy to test. It is the use of stubs and mock (test double) to achieve these.
Both are used as substitutes for the classes and modules that the test target depends on. There is no difference in terms of classes and modules. Therefore, it is often used as a synonym, but in a narrow sense, there are the following differences.
** The purpose is to make dependent objects behave in a predictable way **.
In addition, it is used as a substitute for the following objects.
--Behavior unpredictable --Implementation is not completed --High execution cost and not easy to use --Strongly dependent on the execution environment
** The purpose is to check if the dependent object was called during execution **.
For processes that are difficult to obtain the desired value, such as random numbers and system dates, Extract it as a method and class and switch it with a stub to make it easier to test. There are various ways to implement stubs, but for details, see "[Refactoring method in JUnit](https://qiita.com/maple_syrup/items/c370f3f850972aafd101#junit%E3%81%AB%E3%81%8A%E3". % 81% 91% E3% 82% 8B% E3% 83% AA% E3% 83% 95% E3% 82% A1% E3% 82% AF% E3% 82% BF% E3% 83% AA% E3% 83 % B3% E3% 82% B0% E3% 81% AE% E6% 89% 8B% E6% B3% 95) ".
In the following example, the process of acquiring random numbers is cut out as a method. You have created and overridden a stub method in your test code.
Production code
public class GetRandomElementFromList {
public <T> T choice(List<T> elements) {
if (elements.size() == 0) {
return null;
}
int idx = nextInt() % elements.size();
return elements.get(idx);
}
int nextInt() {
return new Random().nextInt();
}
}
Test code
public class GetRandomElementFromListTest {
private List<String> elements = new ArrayList<>();
@Before
public void setUp() {
elements.add("A");
elements.add("B");
}
@Test
returns A with public void choice() {
GetRandomElementFromList sut = new GetRandomElementFromList() {
@Override
int nextInt() {
return 0;
}
};
assertThat(sut.choice(elements), is("A"));
}
@Test
Returns B with public void choice() {
GetRandomElementFromList sut = new GetRandomElementFromList() {
@Override
int nextInt() {
return 1;
}
};
assertThat(sut.choice(elements), is("B"));
}
}
There are many cases where it is difficult to raise an exception, such as a database connection error. Even in such a case, a method that throws an exception By cutting it into small pieces, it is possible to switch by stub.
The basic unit test verifies the input value and output value like the assertThat method. Therefore, it is easy to test an object whose test target returns a return value and does not retain its state. On the other hand, the test case when the state of the method without return value or the target object changes Verification becomes difficult. Verification becomes even more difficult when the state of dependent objects changes.
A typical example of side effects on dependent objects is There is verification of the contents written to the standard output and logger. (Because the state of dependent objects (standard output, etc.) changes depending on the execution of the test target) In such a case, write a logger or an object that performs standard output. It replaces with an object called a spy and monitors hidden processing. The spy wraps and implements the original object.
The following is an implementation example of test code for verifying the method that outputs logs.
Production code
public class OutputLog {
Logger logger = Logger.getLogger(OutputLog.class.getName());
public void outputLog() {
logger.info("doSomething");
}
}
Test code(Spy class)
public class SpyLogger extends Logger{
final Logger base;
//Class variable for referencing the log from the test side separately from the log output
final StringBuffer log = new StringBuffer();
public SpyLogger(Logger base) {
super(base.getName(), base.getResourceBundleName());
this.base = base;
}
//Method to be a stub
@Override
public void info(String message) {
base.info(message);
//Additional processing → Output to the StringBuffer that the spy has independently
log.append(message);
}
}
Test code(test case)
public class SpyExampleTest {
@Test
public void test() {
OutputLog sut = new OutputLog();
SpyLogger spyLogger = new SpyLogger(sut.logger);
//Replace the logger under test with a spy logger
sut.logger = spyLogger;
sut.outputLog();
assertThat(spyLogger.log.toString(), is("doSomething"));
}
}
This article was written with reference to the following information.
-[Introduction to JUnit Practice ── Systematic Unit Testing Techniques](https://www.amazon.co.jp/JUnit%E5%AE%9F%E8%B7%B5%E5%85%A5%E9 % 96% 80-% E4% BD% 93% E7% B3% BB% E7% 9A% 84% E3% 81% AB% E5% AD% A6% E3% 81% B6% E3% 83% A6% E3% 83% 8B% E3% 83% 83% E3% 83% 88% E3% 83% 86% E3% 82% B9% E3% 83% 88% E3% 81% AE% E6% 8A% 80% E6% B3% 95-WEB-PRESS-plus-ebook / dp / B07JL78S95)
Recommended Posts