JUnitをつくってみる
動機
「何かを勉強するには作ってみるのが良い」という言葉を聞いたことがあるでしょうか?
業務でJUnitを利用することはあっても、具体的にどう作られているのかは知りませんでした。
会社を辞めて転職活動中のため、時間があるので、実際に独自バージョンのJUnitを作ってみることにしました。
参考にするJUnitのバージョン
JUnitのソースコードを探したのですが、JUnit4とJUnit5しか見つかりませんでした。(公式以外なら、JUnit4とJUnit5以外にも存在します。)
JUnit4のソースコードのjunit4/src/main/java/
配下には2つのディレクトリが存在します。
中をみてみると、どうも別々のソースが格納されているみたいです。
とりあえずjunit
の方を参考にすることにしました。理由は下記の通りです。
- Kent Beckの"Test Driven Development: By Example"に近い
- コードがシンプル
JUnitの大まかな流れ
どのように調査したか
最初はjunitのソースを1つ1つ読んでいましたが、下記の理由で主要な流れを把握するだけにしました。
- クラスの量がそこそこある。
- クラスの関係を整理するために、ツールが欲しくなったがいいのがなかった。
- ずっと読んでいるだけだとしんどい。
JUnitの実行
JUnitは下記の流れで実行します。業務で使用する場合は、統合開発環境がよしなに実行してくれます。 詳細はJUnitの公式ページでご確認ください。
- テストクラスを作成する。
- テストクラスをコンパイルする。
- TestRunnerを実行する。
テストクラスの作成方法および、テストクラスのコンパイルの説明は省略します。
TestRunnerを実行する。
下記のようなコマンドを実行します。
java -cp 必要なライブラリ テストを実行するクラス(JUnit3ならjunit.textui.TestRunner) テスト対象クラス
重要なことは、テストを実行するクラス及び、テスト対象クラスを指定していることです。
JUnitの解析
JUnitはどのように処理を実行しているのでしょうか?
何か特別な仕組みが存在するのでしょうか?
結論から言うと、何か難しいことをしているわけではなくReflectionを使用しているだけです。
エントリポイントをみてみる
テストを実行するクラスをしていることから、そのクラスがエントリポイントだとわかります。
実際にjunit4/src/main/java/junit/textui/TestRunner.java
を見てみるとmain()
メソッドが存在します。
つまりjunitは単純にmain()
メソッドを読んでいるだけです。何か特別なことをしているわけではありません。
テストメソッドをどのように実行しているか
エントリポイントはわかりましたが、ではどのようにしてテストメソッドを実行しているのでしょうか?
先ほどのmain()
メソッドからはいろいろメソッドが呼ばれていますが、実際にメソッドを実行しているのは下記となります。
junit4/src/main/java/junit/framework/TestCase.javaのrunTest()メソッド
中身をみてみるとmethod.invoke()
しているだけです。(methodはMethod型)
これはReflectionの機能です。
Reflectionとは
Reflectionとは簡単に言うと、実行時にクラスやメソッドの情報を読み取ったり実行したりできる機能です。
結構昔ですが、黒魔術として有名でした。
Reflectionの長所は、柔軟にクラスやメソッドを操作できることです。
短所は長所の裏返しです。
実行時にJVMで情報を集めてくる必要があるため、実行速度が遅くなります。またprivateメソッドも実行できてしまうため、使用方法を間違えるとエラいことになります。
実装をしてみる
クラス構成
上記でJUnitを作ってみるのに、最低限必要な知識は得られました。 最低限の機能を実施するとして、必要なクラスを考えてみました。
- Assert(assertEqualsを提供する)
- TestResult(テスト結果を保持する)
- TestRunner(エントリポイント)
- TestCase(テストクラスのスーパークラス)
コード
GitHubを参照してください。 なお現在は、クラス構成が多少異なります
やってみてどうだったか
やってみてよかった点は下記の通りです。
- クラス設計とか久しぶりで、頭の体操になった。
- なんとなくコードリーティングするよりモチベーションが保ちやすい