专栏首页MasiMaro 的技术博文Java 注解与单元测试

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不匹配,因此会报异常


本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • java和groovy混合编程时提示找不到符合错误解决办法

    本人在使用java和groovy混合编程时,发现一个问题,当java和groovy相互调用的过程中在本机执行没有任何问题,但当弄到Jenkins上之后总是报错,...

    八音弦
  • Java程序员最常犯的 10 个错误!

    使用Arrays.asList()方法可以得到一个ArrayList,但是得到这个ArrayList其实是定义在Arrays类中的一个私有的静态内部类。这个类虽...

    Java技术栈
  • Spring Boot - Undertow容器启动

    一觉睡到小时候
  • JS设计模式之单例模式

    首先要说明的是设计模式期初并非软件工程中的概念,而是起源于建筑领域。建筑学大师(克里斯托夫·亚历山大)曾经花了很长时间(传闻说20年)研究为了解决同一问题而采用...

    用户1687375
  • Spark Core源码精读计划12 | Spark序列化及压缩机制浅析

    《Spark Core源码精读计划3 | SparkContext辅助属性及后初始化》

    暴走大数据
  • Spark Core源码精读计划14 | Spark Web UI界面的实现

    《Spark Core源码精读计划3 | SparkContext辅助属性及后初始化》

    暴走大数据
  • 12 个免费又酷炫的 IntelliJ IDEA 插件!

    今天介绍一下IDEA的一些炫酷的插件,IDEA强大的插件库,不仅能给我们带来一些开发的便捷,还能体现我们的与众不同。

    Rookie
  • 开发Gradle插件并上传至本地maven库

    作为一个Android开发者,知道gradle是用来构建Android项目的,一开始在学校的时候,没怎么关注并使用过gradle,现在工作发现gradle配置、...

    用户1108631
  • 线程的阻塞和唤醒

    park方法有两个参数来控制休眠多长时间,第一个参数isAbsolute表示第二个参数是绝对时间还是相对时间,单位是毫秒。

    春哥大魔王
  • ps -ef|grep详解

    grep命令是查找,是一种强大的文本搜索工具,它能使用正则表达式搜索文本,并把匹配的行打印出来。

    week

扫码关注云+社区

领取腾讯云代金券