文|码术张
本文旨在说明Junit4源代码的设计思想。
说明方式上,使用的是自己的一个创新的想法:
用一个ut来说明。
代码如下;
package test;
import org.junit.Test;
public class IpTest {
@Test
public void should_True() throws Exception{
MyNotifier notifier = new MyNotifier();
MyResult result = new MyResult();
notifier.addListener(result.createListener());
notifier.addListener(new Detail());
MyDescription description = MyDescription.createMyDescriptionn("IpTest", "");
notifier.fireTestRunStarted(description);
MyDescription descriptionForA = MyDescription.createMyDescriptionn("IpTest", "methodA");
notifier.fireTestStarted(descriptionForA);
System.out.println("Hi..., I am method A");
notifier.fireTestFinished(descriptionForA);
MyDescription descriptionForB = MyDescription.createMyDescriptionn("IpTest", "methodB");
notifier.fireTestStarted(descriptionForB);
System.out.println("Hi..., I am method B");
notifier.fireTestFinished(descriptionForB);
notifier.fireTestRunFinished(result);
}
}
类图:
交互图:
在class IpTest中,有一个should_True方法。
这个方法的实现,即演示了Junit4的实现原理。
MyNotifier、MyResult、MyDescription,
对就Junit源码中类Notifier、Result、Description。
程序运行时,首先发布一个事件fireTestRunStarted;
运行结束时,发布一个一个事件fireTestRunFinished。
类的每一个方法运行时,首先发布一个事件fireTestStarted;
类的每一个方法结束时,会发现一个事件fireTestFinished。
Listener收到事件后,会做一些操作。
这些操作的结果会在程序结时,打印出来:
在Junit源代码中,每个方法会有一个用职责链模式构建一个Statement类的对象。
一个方法的执行,即是依次执行这个链表上的语句。
这点没在本文中体现。
本文所用的其他代码如下:
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyNotifier {
private final List fListeners =
Collections.synchronizedList(new ArrayList());
public void addListener(MyRunListener listener) {
fListeners.add(listener);
}
public void fireTestRunStarted(final MyDescription description) throws Exception {
for (MyRunListener each : fListeners) {
each.testRunStarted(description);
}
}
public void fireTestRunFinished(final MyResult result) throws Exception{
for (MyRunListener each : fListeners) {
each.testRunFinished(result);
}
}
public void fireTestStarted(final MyDescription description) throws Exception{
for (MyRunListener each : fListeners) {
each.testStarted(description);
}
}
public void fireTestFinished(final MyDescription description) throws Exception{
for (MyRunListener each : fListeners) {
each.testFinished(description);
}
}
}
package test;
import java.util.concurrent.atomic.AtomicInteger;
public class MyResult {
private AtomicInteger fCount = new AtomicInteger();
private long fRunTime = 0;
private long fStartTime;
public int getRunCount() {
return fCount.get();
}
public long getRunTime() {
return fRunTime;
}
private class Listener extends MyRunListener {
@Override
public void testRunStarted(MyDescription description) throws Exception {
fStartTime = System.currentTimeMillis();
}
@Override
public void testRunFinished(MyResult result) throws Exception {
long endTime = System.currentTimeMillis();
fRunTime += endTime - fStartTime;
}
@Override
public void testFinished(MyDescription description) throws Exception {
fCount.getAndIncrement();
}
}
public MyRunListener createListener() {
return new Listener();
}
}
package test;
import org.junit.runner.notification.Failure;
public class Detail extends MyRunListener {
public void testRunStarted(MyDescription description) throws Exception {
println("==>JUnit4 started with description: \n" + description);
println();
}
public void testRunFinished(MyResult result) throws Exception {
println("==>JUnit4 finished with result: \n" + describe(result));
}
public void testStarted(MyDescription description) throws Exception {
println("==>Test method started with description: " + description);
}
public void testFinished(MyDescription description) throws Exception {
println("==>Test method finished with description: " + description);
println();
}
public void testFailure(Failure failure) throws Exception {
println("==>Test method failed with failure: " + failure);
}
public void testAssumptionFailure(Failure failure) {
println("==>Test method assumption failed with failure: " + failure);
}
public void testIgnored(MyDescription description) throws Exception {
println("==>Test method ignored with description: " + description);
println();
}
private String describe(MyResult result) {
StringBuilder builder = new StringBuilder();
builder.append("\tRunCount: " + result.getRunCount())
.append("\n");
;
builder.append("\tRunTime: " + result.getRunTime())
.append("\n");
;
return builder.toString();
}
private void println() {
System.out.println();
}
private void println(String content) {
System.out.println(content);
}
}
package test;
public class MyDescription {
private String fclassName;
private String fMethodName;
public String getMethodName() {
return fMethodName;
}
public String getClassName() {
return fclassName;
}
@Override
public String toString() {
return getDisplayName();
}
/**
* @return a user-understandable label
*/
public String getDisplayName() {
return formatDisplayName(fMethodName, fclassName);
}
private MyDescription(String className, String methodName) {
fclassName = className;
fMethodName = methodName;
}
public static MyDescription createMyDescriptionn(String className, String methodName) {
return new MyDescription( className, methodName);
}
private static String formatDisplayName(String name, String className) {
return String.format("%s(%s)", name, className);
}
}