Junitソース分析
Junitは非常に短くて精悍なユニットテストフレームワークで、中には大量の設計モードと設計原則が使われています.もちろん、本文はこれらのモードを分析するのではなく、コードからその実行過程を分析するだけです.1.Junit 38 ClassRunnerの構造方法
2.TestSuiteの構造方法
次にaddTestsFromTestCase(theClass)メソッドのソースコードを見てみましょう
次にaddTestMethodメソッドを見てみましょう
isTestMethod(Method m)メソッドは、このメソッドがテストメソッドであるか否かを判断する.すなわち、testの先頭または関連注釈のあるメソッドaddTest(createTest(theClass,name))である.ここではTestCaseのインスタンスを作成し、fTests(Vector)で作成したTestCaseのfnameプロパティに追加します.実際にテストするメソッド名3.TestSuiteのcreateTestメソッドが保存されます.
次にTestの実行プロセスに入ります:4.Junit 38 ClassRunnerのrunメソッド
ここでgetTest()が得たのはTestSuiteで、上のコードはTestSuiteに複数のTestCaseインスタンスが含まれていることを知っています.もちろん、ここで言う比較限界TestSuiteは他のTestSuiteを含めてテストを実行することもできます.それらはfTestsに保存されています.これは実際に組み合わせモードです.では、TestSuiteのrun方法を見てみましょう. 5.TestSuiteのrunメソッド
予想外に、ここではfTestsを巡って各TestCaseインスタンスのrunメソッドを実行します.では、TestCaseのrunメソッドを見てみましょう. 6.TestCaseのrunメソッド
ここでTestCaseは自分をパラメータとしてTestResultに渡すrunメソッドを実行しますが、ここではなぜこのようにするのか、なぜ直接実行しないのでしょうか.この問題はまずここに残って、それから見ます. 7.TestResultのrunメソッド
startTest、endTestメソッドは初期化と破棄の作業にほかならないので、各メソッドに入って見ないでください.TestResultはその名の通りテスト結果を収集するために使用され、Runner、TestSuite、TestCaseの間でずっと伝達され、すべてのテストメソッドの実行結果を保存します. ここでは保護されたProtectableクラスを作成し、TestCaseのrunBareメソッドを実行します.runProtected()はp.protect()メソッドを呼び出します.これはテストメソッドが実際にTestCaseで実行されていることを示しています.では、なぜTestResultに渡され、TestResultクラスでTestCaseのrunBareメソッドを呼び出すのか、runProtected()の具体的な手順を見てみましょう 8.TestResultのrunProtectedメソッド
明らかに、このプロセスは実行結果を収集するためのものであり、catchからAssertionFailureErrorまでのテスト結果が正しくない場合、catchから他の異常表示テストにエラーが発生した場合、addFailureおよびaddErrorメソッドは、実際にはTestResultのfFailuresおよびfErrorsリストにオブジェクトを追加し、対応する観察者に通知することである. メイン・プロシージャに戻り、テスト・メソッドがどのように呼び出されたかを見てみましょう. 9.TestCaseのrunBare()メソッド
ここでは、junitが各メソッドをテストする際にsetup()、実際にテストするメソッド、tearDown()メソッドを前後して実行する理由を発見します.ここで使用するテンプレートメソッドモードでは、setupとtearDownがテストクラスに複写されている場合、実際に実行されるのはテストクラスのsetupとtearDownであり、各クラスをテストする前後でこの2つのメソッドが実行されます.実際にテストを実行する方法はrunTest()で実現されていますが、runTest()の方法を見てみましょう 10.TestCaseのrunTest()メソッド
ここでJunitは実際に私たちがテストする方法を反射してinvokeに行くことがわかります.以上がJunitがテストする全過程です.次に、前の過程で残した問題を分析してみましょう. TestCaseのrunメソッドではテストメソッドを直接実行せずにTestResultのrunメソッドに自分自身を転送して実行するのはなぜですか? この問題は後の実行過程を通じて、みんなも理解したはずで、これは実はJunitの設計の上でとても私達の学習に値するところで、類の単一の職責の特徴で、TestCaseの中の方法はすべてテストに関連するべきで、TestResultの中の方法はすべて結果のためにサービスして、この織りなす過程はテストの結果を得るためです
public JUnit38ClassRunner(Class<?> klass) {
this(new TestSuite(klass.asSubclass(TestCase.class)));
}
2.TestSuiteの構造方法
public TestSuite(final Class<?> theClass) {
addTestsFromTestCase(theClass);
}
次にaddTestsFromTestCase(theClass)メソッドのソースコードを見てみましょう
private void addTestsFromTestCase(final Class<?> theClass) {
fName= theClass.getName();
try {
getTestConstructor(theClass); // Avoid generating multiple error messages
} catch (NoSuchMethodException e) {
addTest(warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()"));
return;
}
if (!Modifier.isPublic(theClass.getModifiers())) {
addTest(warning("Class "+theClass.getName()+" is not public"));
return;
}
Class<?> superClass= theClass;
List<String> names= new ArrayList<String>();
while (Test.class.isAssignableFrom(superClass)) {
for (Method each : superClass.getDeclaredMethods())
addTestMethod(each, names, theClass);
superClass= superClass.getSuperclass();
}
if (fTests.size() == 0)
addTest(warning("No tests found in "+theClass.getName()));
}
次にaddTestMethodメソッドを見てみましょう
private void addTestMethod(Method m, List<String> names, Class<?> theClass) {
String name= m.getName();
if (names.contains(name))
return;
if (! isPublicTestMethod(m)) {
if (isTestMethod(m))
addTest(warning("Test method isn't public: "+ m.getName() + "(" + theClass.getCanonicalName() + ")"));
return;
}
names.add(name);
addTest(createTest(theClass, name));
}
isTestMethod(Method m)メソッドは、このメソッドがテストメソッドであるか否かを判断する.すなわち、testの先頭または関連注釈のあるメソッドaddTest(createTest(theClass,name))である.ここではTestCaseのインスタンスを作成し、fTests(Vector)で作成したTestCaseのfnameプロパティに追加します.実際にテストするメソッド名3.TestSuiteのcreateTestメソッドが保存されます.
static public Test createTest(Class<?> theClass, String name) {
Constructor<?> constructor;
try {
constructor= getTestConstructor(theClass);
} catch (NoSuchMethodException e) {
return warning("Class "+theClass.getName()+" has no public constructor TestCase(String name) or TestCase()");
}
Object test;
try {
if (constructor.getParameterTypes().length == 0) {
test= constructor.newInstance(new Object[0]);
if (test instanceof TestCase)
((TestCase) test).setName(name);
} else {
test= constructor.newInstance(new Object[]{name});
}
} catch (InstantiationException e) {
return(warning("Cannot instantiate test case: "+name+" ("+exceptionToString(e)+")"));
} catch (InvocationTargetException e) {
return(warning("Exception in constructor: "+name+" ("+exceptionToString(e.getTargetException())+")"));
} catch (IllegalAccessException e) {
return(warning("Cannot access test case: "+name+" ("+exceptionToString(e)+")"));
}
return (Test) test;
}
次にTestの実行プロセスに入ります:4.Junit 38 ClassRunnerのrunメソッド
public void run(RunNotifier notifier) {
TestResult result= new TestResult();
result.addListener(createAdaptingListener(notifier));
getTest().run(result);
}
ここでgetTest()が得たのはTestSuiteで、上のコードはTestSuiteに複数のTestCaseインスタンスが含まれていることを知っています.もちろん、ここで言う比較限界TestSuiteは他のTestSuiteを含めてテストを実行することもできます.それらはfTestsに保存されています.これは実際に組み合わせモードです.では、TestSuiteのrun方法を見てみましょう. 5.TestSuiteのrunメソッド
public void run(TestResult result) {
for (Test each : fTests) {
if (result.shouldStop() )
break;
runTest(each, result);
}
}
予想外に、ここではfTestsを巡って各TestCaseインスタンスのrunメソッドを実行します.では、TestCaseのrunメソッドを見てみましょう. 6.TestCaseのrunメソッド
public void run(TestResult result) {
result.run(this);
}
ここでTestCaseは自分をパラメータとしてTestResultに渡すrunメソッドを実行しますが、ここではなぜこのようにするのか、なぜ直接実行しないのでしょうか.この問題はまずここに残って、それから見ます. 7.TestResultのrunメソッド
protected void run(final TestCase test) {
startTest(test);
Protectable p= new Protectable() {
public void protect() throws Throwable {
test.runBare();
}
};
runProtected(test, p);
endTest(test);
}
startTest、endTestメソッドは初期化と破棄の作業にほかならないので、各メソッドに入って見ないでください.TestResultはその名の通りテスト結果を収集するために使用され、Runner、TestSuite、TestCaseの間でずっと伝達され、すべてのテストメソッドの実行結果を保存します. ここでは保護されたProtectableクラスを作成し、TestCaseのrunBareメソッドを実行します.runProtected()はp.protect()メソッドを呼び出します.これはテストメソッドが実際にTestCaseで実行されていることを示しています.では、なぜTestResultに渡され、TestResultクラスでTestCaseのrunBareメソッドを呼び出すのか、runProtected()の具体的な手順を見てみましょう 8.TestResultのrunProtectedメソッド
public void runProtected(final Test test, Protectable p) {
try {
p.protect();
}
catch (AssertionFailedError e) {
addFailure(test, e);
}
catch (ThreadDeath e) { // don't catch ThreadDeath by accident
throw e;
}
catch (Throwable e) {
addError(test, e);
}
}
明らかに、このプロセスは実行結果を収集するためのものであり、catchからAssertionFailureErrorまでのテスト結果が正しくない場合、catchから他の異常表示テストにエラーが発生した場合、addFailureおよびaddErrorメソッドは、実際にはTestResultのfFailuresおよびfErrorsリストにオブジェクトを追加し、対応する観察者に通知することである. メイン・プロシージャに戻り、テスト・メソッドがどのように呼び出されたかを見てみましょう. 9.TestCaseのrunBare()メソッド
public void runBare() throws Throwable {
Throwable exception= null;
setUp();
try {
runTest();
} catch (Throwable running) {
exception= running;
}
finally {
try {
tearDown();
} catch (Throwable tearingDown) {
if (exception == null) exception= tearingDown;
}
}
if (exception != null) throw exception;
}
ここでは、junitが各メソッドをテストする際にsetup()、実際にテストするメソッド、tearDown()メソッドを前後して実行する理由を発見します.ここで使用するテンプレートメソッドモードでは、setupとtearDownがテストクラスに複写されている場合、実際に実行されるのはテストクラスのsetupとtearDownであり、各クラスをテストする前後でこの2つのメソッドが実行されます.実際にテストを実行する方法はrunTest()で実現されていますが、runTest()の方法を見てみましょう 10.TestCaseのrunTest()メソッド
protected void runTest() throws Throwable {
assertNotNull("TestCase.fName cannot be null", fName); // Some VMs crash when calling getMethod(null,null);
Method runMethod= null;
try {
// use getMethod to get all public inherited
// methods. getDeclaredMethods returns all
// methods of this class but excludes the
// inherited ones.
runMethod= getClass().getMethod(fName, (Class[])null);
} catch (NoSuchMethodException e) {
fail("Method \""+fName+"\" not found");
}
if (!Modifier.isPublic(runMethod.getModifiers())) {
fail("Method \""+fName+"\" should be public");
}
try {
runMethod.invoke(this);
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throw e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throw e;
}
}
ここでJunitは実際に私たちがテストする方法を反射してinvokeに行くことがわかります.以上がJunitがテストする全過程です.次に、前の過程で残した問題を分析してみましょう. TestCaseのrunメソッドではテストメソッドを直接実行せずにTestResultのrunメソッドに自分自身を転送して実行するのはなぜですか? この問題は後の実行過程を通じて、みんなも理解したはずで、これは実はJunitの設計の上でとても私達の学習に値するところで、類の単一の職責の特徴で、TestCaseの中の方法はすべてテストに関連するべきで、TestResultの中の方法はすべて結果のためにサービスして、この織りなす過程はテストの結果を得るためです