以下のjavaプログラムをdJUnitを使用してテストしてみます。
01 public class HelloWorld { 02 03 public HelloWorld() { 04 } 05 06 public static void main(String args[]) { 07 08 if (false) { 09 System.out.println("don't never pass..."); 10 } 11 12 System.out.println("HelloWorld."); 13 } 14 } |
対するテストケースは
HelloWorldのテストで使用したテストケースを使用します。
(デフォルトコンストラクタのCoverageを通すためのコードを追加してます。→18行目)
01 import jp.co.dgic.testing.framework.DJUnitTestCase; 02 03 public class HelloWorldTest extends DJUnitTestCase { 04 05 public HelloWorldTest(String name) { 06 super(name); 07 } 08 09 protected void setUp() throws Exception { 10 super.setUp(); 11 } 12 13 protected void tearDown() throws Exception { 14 super.tearDown(); 15 } 16 17 public void testMain() { 18 new HelloWorld(); 19 HelloWorld.main(null); 20 21 String arg = (String) getArgument("java.io.PrintStream", "println", 0); 22 assertEquals("HelloWorld.", arg); 23 } 24 } |
このテストを実行するにあたって、以下のことが予想できます。
- テストは通るはずである。
- CoverageReportは100%にはならないはずである。
1は
HelloWorldのテストで検証済みなので問題ないと思います。
2は、プログラム内の
1 if (false) { 2 System.out.println("don't never pass..."); 3 } |
という部分が実行されず、CoverageReportでは
未実行とされるはずです。
以下の画像はこのテストを実行して生成されたCoverageReportです。
バッチリ100%になりました。。。
これは、JCoverageにも言えることなのですが、
javaコンパイラによる最適化が影響しているようです。
今回のような
if(false)
という絶対に通らない部分(デッド・コード)を含むソースをコンパイルすると、コンパイラがデッド・コード除去という最適化を行い、
決して実行されないブロックのバイトコードを生成しないようにするらしいのです。
ということは、コンパイル後にはもうこの分岐は存在せず、単に「
HelloWorldを出力するプログラム」になってしまうのです。
そこで、ある実験をしてみます。
まず、今回のテストによるCoverageReportのlinesは
4行となっていることを確認しておいて下さい。
ちょこっとソースを変更してまた同じテストを実行します。
以下は変更後のテスト対象ソースコードです。
01 public class HelloWorldForDeadCode { 02 03 public HelloWorldForDeadCode() { 04 } 05 06 public static void main(String args[]) { 07 08 if (false) { 09 System.out.println("don't never pass..."); 10 System.out.println("don't never pass..."); 11 System.out.println("don't never pass..."); 12 System.out.println("don't never pass..."); 13 System.out.println("don't never pass..."); 14 System.out.println("don't never pass..."); 15 System.out.println("don't never pass..."); 16 System.out.println("don't never pass..."); 17 System.out.println("don't never pass..."); 18 System.out.println("don't never pass..."); 19 System.out.println("don't never pass..."); 20 System.out.println("don't never pass..."); 21 System.out.println("don't never pass..."); 22 System.out.println("don't never pass..."); 23 System.out.println("don't never pass..."); 24 System.out.println("don't never pass..."); 25 System.out.println("don't never pass..."); 26 System.out.println("don't never pass..."); 27 System.out.println("don't never pass..."); 28 System.out.println("don't never pass..."); 29 } 30 31 System.out.println("HelloWorld."); 32 } 33 } |
デッド・コード対象のif文の行数を増やしてみました。
もしデッド・コードのバイトコードが生成されていないのならば、このテストのCoverageReportのlinesも
4になるはずです。
そしてこれがそのCoverageReportです。
デッド・コードの除去は
コンパイル時に式の値が false になる場合
に動作するようです。
例)
if (false) ・・・あきらかにfalseの場合
→ デッド・コード対象です。
if (1 + 1 == 0) ・・・計算式の結果が必ずfalseの場合
→ デッド・コード対象です。
if (callReturnFalseMethod()) ・・・実行時に式の値がfalseになる場合
→ デッド・コード対象外(バイトコードが生成される)です。
JCoverage(CoverageReport)は、コンパイルされたclassファイルに対して行番号を与えてテストの実行・未実行を検証するので、このような現象が起こってしまったと考えられます。
参考:
http://www-6.ibm.com/jp/developerworks/java/000818/j_pr29.html