Java 注解與單元測試


注解

Java注解是在JDK1.5 之后出現的新特性,用來說明程序的,注解的主要作用體現在以下幾個方面:

  1. 編譯檢查,例如 @Override
  2. 編寫文檔,java doc 會根據注解生成對應的文檔
  3. 代碼分析,通過注解對代碼進行分析[利用反射機制]

JDK 中有一些常用的內置注解,例如:

  1. Override:檢查被該注解修飾的方法是否是重寫父類的方法
  2. Deprecatedd:被該注解標注的內容已過時
  3. SuppressWarnning: 壓制警告,傳入參數all表示壓制所有警告

自定義注解

JDK中雖然內置了大量注解,但是它也允許我們自定義注解,這樣就為程序編寫帶來了很大的便利,像有些框架就大量使用注解。

java注解本質上是一個繼承了 java.lang.annotation.Annotation 接口的一個接口,但是如果只是簡單的使用關鍵字 interface來定義接口,仍然不是注解,僅僅是一個普通的接口,在定義注解時需要使用關鍵字 @interface, 該關鍵字會默認繼承 Annotation 接口,並將定義的接口作為注解使用

注解中可以定義方法,這些方法的返回值只能是基本類型、String、枚舉類型、注解以及這些類型的數組,我們稱這些方法叫做屬性。

在使用注解時需要注意以下幾個事情

  1. 必須給注解的屬性賦值,如果不想賦值可以使用default來設置默認值
  2. 如果屬性列表中只有一個名為value的屬性,那么在賦值時可以不用指定屬性名稱
  3. 多個屬性值之間使用逗號隔開
  4. 數組屬性的賦值使用 {}, 而當數組屬性中只有一個值時, {} 可以省略不寫

元注解

元注解是用來描述注解的注解,Java中提供的元注解有下列幾個

Target

描述注解能夠作用的位置,即哪些Java代碼元素能夠使用該注解,注解的源代碼如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}

這個注解只有一個value屬性,屬性需要傳入一個 ElementType枚舉類型的數組,該枚舉類型可以取下列幾個值

ElementType 含義
TYPE 接口、類(包括注解)、枚舉類型上使用
FIELD 字段聲明(包括枚舉常量)
METHOD 方法
PARAMETER 參數聲明
CONSTRUCTOR 構造函數
LOCAL_VARIABLE 局部變量聲明
ANNOTATION_TYPE 注解類型聲明
PACKAGE 包聲明
Retention

表示該注解類型的注解保留的時長,主要有3個階段: 源碼階段,類對象階段,運行階段;源碼階段是只只存在與源代碼中,類對象階段是指被編譯進 .class 文件中,類對象階段是指執行時被加載到內存.則默認保留策略為RetentionPolicy.CLASS。

它的源碼如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}
Documented

表示擁有該注解的元素可通過javadoc此類的工具進行文檔化。源碼如下:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
Inherited

表示該注解類型被自動繼承

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}

內置注解解讀

下面通過幾個JDK內置注解的解讀來說明注解相關使用

Override
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

該注解用於編譯時檢查,被該注解注釋的方法是否是重寫父類的方法。

從源碼上看,它只能在方法上使用,並且它僅僅存在於源碼階段不會被編譯進 .class 文件中

Deprecatedd
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}

用於告知編譯器,某一程序元素(例如類、方法、屬性等等)不建議使用

從源碼上看,幾乎所有的Java程序元素都可以使用它,而且會被加載到內存中

SuppressWarnning
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}

告知編譯器忽略特定類型的警告
它需要傳入一個字符串的數組,取值如下:

參數 含義
deprecation 使用了過時的類或方法時的警告
unchecked 執行了未檢查的轉換時的警告
fallthrough 當Switch程序塊進入進入下一個case而沒有Break時的警告
path 在類路徑、源文件路徑等有不存在路徑時的警告
serial 當可序列化的類缺少serialVersionUID定義時的警告
finally 任意finally子句不能正常完成時的警告
all 以上所有情況的警告

在程序中解析注解

一般通過反射技術來解析自定義注解,要通過反射技術來識別注解,前提條件就是注解要在內存中被加載也就是要使它的范圍為 RUNTIME;

JDK提供了以下常用API方便我們使用

返回值 方法 解釋
T getAnnotation(Class annotationClass) 當存在該元素的指定類型注解,則返回相應注釋,否則返回null
Annotation[] getAnnotations() 返回此元素上存在的所有注解
Annotation[] getDeclaredAnnotations() 返回直接存在於此元素上的所有注解。
boolean isAnnotationPresent(Class<? extends Annotation> annotationClass) 當存在該元素的指定類型注解,則返回true,否則返回false

實戰

下面使用一個完整的例子來說明自定義注解以及在程序中使用注解的例子,現在來模仿JUnit 定義一個MyTest的注解,只要被這個注解修飾的方法將來都會被自動執行

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import java.lang.annotation.ElementType;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface MyTest {
}

首先定義一個注解,后續來執行用這個注解修飾了的所有方法,通過Target來修飾標明注解只能用於方法上,通過Retention修飾標明注解會被保留到運行期

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Test {
    @MyTest
    public void test1(){
        System.out.println("this is test1");
    }

    @MyTest
    public void test2(){
        System.out.println("this is test2");
    }

    public static void main(String[] args) {
        Method[] methods = Test.class.getMethods();
        for (Method method:methods){
            if (method.isAnnotationPresent(MyTest.class)){
                try {
                    method.invoke(new Test());
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

在測試類中定義了兩個測試函數都使用 @MyTest 修飾,在主方法中,首先通過反射機制獲取該類中所有方法,然后調用方法的 isAnnotationPresent 函數判斷該方法是否被 @Test修飾,如果是則執行該方法。這樣以后即使再添加方法,只要被 @MyTest 修飾就會被調用。

Junit框架

在軟件開發中為了保證軟件質量單元測試是必不可少的一個環節,Java中提供了Junit 測試框架來進行單元測試

一般一個Java項目每一個類都會對應一個test類用來做單元測試,例如有一個Person類,為了測試Person類會定義一個PersonTest類來測試所有代碼

JUnit 中定義了一些注解來方便我們編寫單元測試

  1. @Test:測試方法,被該注解修飾的方法就是一個測試方法
  2. @Before:在測試方法被執行前會執行該注解修飾的方法
  3. @After:在測試方法被執行后會執行該注解修飾的方法

除了注解JUnit定義了一些斷言函數來實現自動化測試,常用的有如下幾個:

  1. void assertEquals(boolean expected, boolean actual):檢查兩個變量或者等式是否平衡
  2. void assertTrue(boolean expected, boolean actual):檢查條件為真
  3. void assertFalse(boolean condition):檢查條件為假
  4. void assertNotNull(Object object):檢查對象不為空
  5. void assertNull(Object object):檢查對象為空
  6. void assertSame(boolean condition):assertSame() 方法檢查兩個相關對象是否指向同一個對象
  7. void assertNotSame(boolean condition):assertNotSame() 方法檢查兩個相關對象是否不指向同一個對象
  8. void assertArrayEquals(expectedArray, resultArray):assertArrayEquals() 方法檢查兩個數組是否相等

這些函數在斷言失敗后會拋出異常,后續只要查看異常就可以哪些測試沒有通過

假設先定義一個計算器類,來進行兩個數的算數運算

public class Calc {
    public int add(int a, int b){
        return a + b;
    }

    public int sub(int a, int b){
        return a - b;
    }

    public int mul(int a, int b){
        return a * b;
    }

    public float div(int a, int b){
        return a / b;
    }
}

為了測試這些方法是否正確,我們來定義一個測試類

import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class CalcTest {
    @Test
    public void addTest(){
        int result = new Calc().add(1,2);
        assertEquals(result, 3);
    }

    @Test
    public void subTest(){
        int result = new Calc().sub(1,2);
        assertEquals(result, -1);
    }

    @Test
    public void mulTest(){
        int result = new Calc().mul(1,2);
        assertEquals(result, 2);
    }

    @Test
    public void divTest(){
        float result = new Calc().div(1,2);
        assertEquals(result, 0.5, 0.001); //會報異常
    }
}

經過測試發現,最后一個divTest方法 會報異常,實際值是0,因為我們使用 / 來計算兩個int時只會保留整數位,也就是得到的是0,與預期的0.5不匹配,因此會報異常



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM