For refactoring in JUnit, improving testability is a top priority. Classes with complicated states to hold, methods with many arguments, methods with wide responsibilities, etc. It is a complicated test target that is difficult to verify. Besides, the implementation is not completed yet, the service is stopped, and the test target depends on it. There are also situations where unit tests cannot be executed, such as when an external Web service cannot be used.
Refactoring in JUnit provides testability for these test cases To improve, change only the ** internal structure ** without changing the ** behavior ** under test.
Testability is the ease of testing. Generally, execution timing, execution environment, execution order, etc. Test cases that are affected by the external environment are said to have low testability.
As mentioned above, refactoring means changing only the ** internal structure ** without changing the ** behavior ** of the test target. This is a technique for organizing source code.
Supplement: Precautions for refactoring When refactoring It is desirable that a unit test of the target class / method exists. That's because refactoring shouldn't change the behavior. Therefore, by changing the internal structure while checking the behavior by unit test Developers can rest assured that they can improve their production code.
Since this is just an introduction to simple refactoring techniques, See specialized books for more detailed refactoring techniques.
Here, the degree of module coupling is weakened by extracting the process, Replace stubs and mock that return reliable results instead of uncertainties I will introduce the method.
Below, the design becomes weaker as it goes down. Regarding the degree of coupling, the article "About module coupling" was helpful.
The optimal pattern does not necessarily mean that the degree of coupling is weak. There is also the danger of over-engineering in refactoring. The implemented implementation is too large for what you want to achieve. When actually refactoring, what you want to achieve and the implementation cost Determine if it is balanced and perform appropriate refactoring.
The following is an example of a test case that confirms that the system time is set. We will introduce various refactoring methods.
Production code
public class Dateutils {
// (1)Class initialization(=Instantiation)Set at the time
Date date = new Date();
public void doSomethingToDate() {
// (2)Set at method execution
This.date = new date();
//Some processing
}
}
Test code
public class DateUtilsTest {
@Test
public void doSomethingToDate sets date to current time() throws Exception {
// (1)Class instantiation sets date to system time
DateUtils sut = new DateUtils();
// (2)The process in the method overwrites the system time on date
sut.doSomethingToDate();
// (3)The system time passed to the is method is the system time at this point
assertThat(sut.date, is(new Date()));
}
}
What's detrimental to testability in this implementation is The result of the execution can be either successful or unsuccessful.
The system time assumed by the assertThat method is the system time set at the time of (3), The `` `sut.date``` passed as the measured value is the value set at the time of executing the method (2). Since the java.util.Date class holds the date and time up to milliseconds, the execution time from (2) to (3) If it takes more than a millisecond, the test will fail. (It rarely occurs in the above implementation, but it fails if processing is stopped for 1 second in doSomethingToDate)
In the following implementation, ** Get system time ** is cut out as a method. It is replaced with a stub by overriding in the test code. In this way, the test case that depended on the uncertain factor of ** method execution timing ** It has been refactored and changed so that a reliable value can be set from the test code side.
Strictly speaking, getting the system time is also a method under test. From the point of view of rewriting the value from the test code side, These are not perfect tests. However, leaving uncertainties behind, ** Tested Class ** Rather, the problem is that the test results of the larger framework are not stable. From a testability point of view, processing that is difficult to handle during testing It is good to design it so that it can be extracted as an individual method and easily operated from the test code.
Production code
public class ExtractMethod {
Date date = new Date();
public void doSomethingToDate() {
// (1)By(2)The system time at the time of test execution is set instead of the system date at the time of method execution
this.date = getNowDate();
//Some processing
}
Date getNowDate() {
// (2)The original code sets the system time when the method is executed
return new Date();
}
}
Test code
public class ExtractMethodTest {
@Test
public void doSomethingToDate sets date to current time() throws Exception {
final Date current = new Date();
ExtractMethod sut = new ExtractMethod() {
// (1)Changed to overwrite the process of setNowDate and set the instance of current
@Override
Date getNowDate() {
return current;
}
};
sut.doSomethingToDate();
assertThat(sut.date, is(sameInstance(current)));
}
}
As for making, the target of extraction and acquisition is just a class from a method. By extracting as a class, it can be used from multiple test target classes. However, from the viewpoint of test independence, do not over-commonize. (Since the amount of coding has increased for what I want to do, it seems to be over-designed)
Production code(Extracted class)
public class DateFactory {
Date getNowDate() {
return new Date();
}
}
Production code(Tested class)
public class ExtractClass {
DateFactory dateFactory = new DateFactory();
Date date = new Date();
public void doSomethingToDate() {
this.date = dateFactory.getNowDate();
//Some processing
}
}
Test code
public class ExtractClassTest {
@Test
public void doSomethingToDate sets date to current time() {
final Date current = new Date();
ExtractClass sut = new ExtractClass();
sut.dateFactory = new DateFactory() {
@Override
Date getNowDate() {
return current;
}
};
sut.doSomethingToDate();
assertThat(sut.date, is(sameInstance(current)));
}
}
By extracting as an interface, not only can it be used in common by multiple classes, but also The processing performed in the getNowDate method can now be changed flexibly. (Completely over-engineered in the implementation below)
Production code(Extracted interface)
public interface DateFactory {
Date getNowDate();
}
Production code(Interface implementation class)
public class DateFactoryImpl implements DateFactory {
@Override
public Date getNowDate() {
return new Date();
}
}
Production code(Tested class)
public class ExtractClass {
DateFactory dateFactory = new DateFactoryImpl();
Date date = new Date();
public void doSomethingToDate() {
this.date = dateFactory.getNowDate();
//Some processing
}
}
Test code
public class ExtractClassTest {
@Test
public void doSomethingToDate sets date to current time() {
final Date current = new Date();
ExtractClass sut = new ExtractClass();
sut.dateFactory = new DateFactory() {
@Override
Date getNowDate() {
return current;
}
};
sut.doSomethingToDate();
assertThat(sut.date, is(sameInstance(current)));
}
}
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