第35条:注釈はネーミングモードより優先する

8082 ワード

ネーミング・モードの欠点:1.文字のスペルミスで失敗し、テスト方法が実行されず、エラーも報告されなかった(JUNITテストフレームワークテストの方法はtestで始まる).クラスのすべてのメソッドがテストされるように、クラス名をtestの先頭に付けますが、JUnitはクラスレベルのテストをサポートせず、testの先頭のメソッドでのみ有効になります.パラメータ値をプログラム要素に関連付ける良い方法はありません.テストカテゴリをサポートするには、特別な例外を投げ出した場合にのみ成功します.例外タイプの本質はテストのパラメータです.ネーミングクラスが存在しないか、例外ではない場合は、実行後にのみ発見できます.注記は、名前付きモードの問題を解決します.次に、簡単なテストを指定する注記タイプを定義します.自動的に実行され、例外が投げ出されたときに失敗します(以下のTest注記はカスタムで、JUnitの実装ではありません)
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)

public @interface Test {
   
}


testがRetentionとTargetの2つの注釈を使用しているように、この注釈はメタ注釈@Retention(RetentionPolicy.RUNTIME)と呼ばれ、Test注釈が実行時に保持されていることを示し、保持されていない場合、テストツールはTest注釈@Target(ElementType.METHOD)がメソッド宣言にのみTest注釈が合法であることを示し、クラス宣言、ドメイン宣言、または他のプログラム要素に適用できないことを示している.use only on parameterless static method(パラメータなしの静的メソッドのみ)ですが、コンパイラはパラメータを制限することはできません.Test注釈をインスタンスメソッドに置くか、1つ以上のメソッドに置くと、テストプログラムはコンパイルエラーがなく、テストツールを実行するときにのみ処理できます.
次のSampleクラスではTest注記を使用していますが、Testのスペルを間違えたり、Test注記をメソッド以外の場所に適用したりした場合、コンパイルは通過しません.
public class Sample {
@Test   public static  void  m1() {
}
public static void m2() {
}
@Test public static void  m3() {
throw new   RuntimeException("Boom");
}
public static void   m4() {
}
@Test  public  void  m5() {
}
public  static  void  m6() {
}
@Test  public  static  void  m7() {
 thrownew  RuntimeException("Crash");
}
public  static  void  m8() {
}
}

Sampleには8つのメソッド(ここでm 5は静的メソッドではない)があり、4つのテストに注釈されたメソッドのうち、2つの放出異常:m 3とm 7があり、他の2つは:m 1とm 5ではなく、注釈されたメソッドm 5はインスタンスメソッドであるため、注釈の有効な使用には属さない.タグ付けの方法がない場合、テストツールはtest注釈を無視してSampleクラスの意味に直接影響を及ぼさず、関連プログラムの使用のために情報を提供するだけです.すなわち,注釈は被注釈コードの意味を変えることはないが,ツールによって特殊な処理を行うことができる.例えば、注釈で方法を簡単にテストします.Sampleのテスト実行クラスをテストします.
public class RunTests {

    public static void main(String[] args) throws Exception {

        int tests = 0;

        int passed = 0;

        Class testClass = Class.forName("service.Sample");

        for (Method m : testClass.getDeclaredMethods()) {

            if (m.isAnnotationPresent(Test.class)) {

                tests++;

                try {

                    m.invoke(null);

                    passed++;

                } catch (InvocationTargetException wrappedExc) {

                    Throwable exc = wrappedExc.getCause();

                    System.out.println(m + " failed: " + exc);

                } catch (Exception e) {

                    System.out.println("INVALID @Test: " + m);

                }

            }

        }

        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);

    }

}

テスト実行ツールは、コマンドラインで完全に一致するクラス名を使用し、Methodを呼び出す.invoke反射式の実行クラスにはtestと表記されたすべてのメソッドがあり、isAnnotationPresentメソッドはツールが実行するメソッドを示します.テストメソッドが例外反射メカニズムを放出すると、この例外をキャプチャし、テストメソッドによって放出された元の例外を含む失敗レポートを印刷します.これらの情報は、getCasuseメソッドによってInvocationTargetExceptionから抽出された、反射呼び出しテストメソッドを試みたときにInvocationTargetException以外の例外を放出する場合、表面コンパイル時にTest注記の無効な使用をキャプチャしませんでした.この使用には、インスタンスメソッドの注記、または1つ以上のパラメータを持つメソッドの注記が含まれ、対応するエラー情報の実行結果が印刷されます.
public   static   void   Sample.m3()
 failed: java.lang.RuntimeException: Boom
INVALID @Test:public void  Sample.m5()
public  static   void  Sample.m7()  
failed: java.lang.RuntimeException: Crash
Passed:1, Failed: 3

特殊な例外が放出された場合にのみ成功する注記:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)
public @interface ExceptionTest {
    Class  extends Exception>value();
}
Sample --
public class Sample1 {

    @ExceptionTest(ArithmeticException.class)
    public static  void  m1() {
    
    }

    public static void m2() {
    
    }
    
    @ExceptionTest(ArithmeticException.class)
    public static void  m3() {
    
        throw new   RuntimeException("Boom");
    
    }

    public static void   m4() {
    
    }
    @ExceptionTest(ArithmeticException.class)
    public  void  m5() {
    
    }
    
    public  static  void  m6() {
    
    }
    @ExceptionTest(ArithmeticException.class)
    public  static  void  m7() {
    
        throw new  RuntimeException("Crash");
    
    }
    
    public  static  void  m8() {

}

このコードはTest注釈を処理するために使用されるコードと似ていますが、このコードは注釈パラメータの値を抽出し、テストで放出された異常が正しいタイプであるかどうかを検証する点で異なります.表示されていない変換なので、タイプ変換異常が発生する危険はありません.コンパイルされたテストプログラムは、その注釈パラメータが有効な異常タイプを示していることを確認します.注釈パラメータパラメータがコンパイル時に有効である可能性がありますが、特定の異常タイプを示すクラスファイルが実行時に存在しないことを注意する必要があります.このような希望が少ない場合、テスト実行クラスはTypeNotPresentException異常を放出します.上記の異常テストの例をさらに深くすると、テストは任意の指定された異常を投げ出して合格することができます.exceptionTest注記のパラメータタイプをClassオブジェクトの配列に変更します.
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)

@Target(ElementType.METHOD)
public @interface ExceptionTest1 {
    Class  extends Exception> [] value();
}

注記の配列パラメータの構文は非常に柔軟です.最適化されたセル配列です.ExceptionTestの新しい配列パラメータを使用した後も、これまでのすべてのExceptionTest注記は有効であり、単一の要素で囲まれています.複数の要素の配列を指定するには、({})で要素を保護し、{,}で区切る必要があります.
public class Sample2 {

    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public static  void  m1() {
    
    }

    public static void m2() {
    
    }
    
    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public static void  m3() {
    
        throw new   RuntimeException("Boom");
    
    }

    public static void   m4() {
    
    }
    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public  void  m5() {
    
    }
    
    public  static  void  m6() {
    
    }
    @ExceptionTest1({ArithmeticException.class,NullPointerException.class})
    public  static  void  m7() {
    
        throw new  RuntimeException("Crash");
    
    }
    
    public  static  void  m8() {

}

}
public class RunTests2 {

    public static void main(String[] args) throws Exception {

        int tests = 0;

        int passed = 0;

        Class testClass = Class.forName("service.Sample2");

        for (Method m : testClass.getDeclaredMethods()) {

            if (m.isAnnotationPresent(ExceptionTest1.class)) {
                tests++;

                try { // Test 

                    m.invoke(null);

                } catch (InvocationTargetException e) {
                    //InvocationTargetException Method.invoke(obj, args...) 。 , 。
                    Throwable exc = e.getCause();
                    Class[] excTypes = m.getAnnotation(ExceptionTest1.class).value();
                    int oldPassed = passed;
                    for (Class excType : excTypes) {
                        if (excType.isInstance(exc)) {
                            passed++;
                            break;
                        }
                    }
                    if (passed == oldPassed) {
                        System.out.printf(" %s :%s %n", m, exc);
                    }

                } catch (Exception e) {

                    System.out.println("Invalid @Test: " + m);

                }

            }

        }

        System.out.printf("Passed: %d, Failed: %d%n", passed, tests - passed);

    }

}


以上の例は注釈の氷山の一角を明らかにしないが、注釈がある以上、特定のプログラマーを除いて、多くのプログラマーは注釈のタイプを定義する必要はないという観点を鮮明に表現している.しかし、すべてのプログラマーはJavaプラットフォームが提供する事前定義の注釈タイプを使用するべきである.また、IDE(Integrated Development Environment)は、プログラム開発環境を提供するためのアプリケーションであり、一般的にコードエディタ、コンパイラ、デバッガ、グラフィックユーザインタフェースなどのツールを含む)または静的解析ツールが提供する注釈も考慮する.この注釈は、これらのツールによって提供する診断情報の品質を向上させることができる.しかし、これらの注釈はまだ標準化されていないので、ツールを変換したり、標準を形成する場合は、より多くの作業が必要である.
.