热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

EffectiveJava(35)--注解优先于命名模式(从零构建JUnit测试类)

在java1.5版本之前的代码中,一般使用命名模式表明哪些程序元素需要通过某种工具或框架进行特殊处理.但是它有严重的缺点–以Junit为例1.由于JUn

在 java1.5 版本之前的代码中 , 一般使用命名模式表明哪些程序元素需要通过某种工具或框架进行特殊处理 . 但是它有严重的缺点 – 以 Junit 为例
1.由于 JUnit 要求测试方法的开头必须为test , 所以类名的文字拼写会导致运行失败 , 但是编译器不会报错或提示
2.无法确保它们只用于相应的程序元素上
3.命名模式没有提供将参数值与程序元素关联起来的方法 例如想要支持只在抛出异常时才会运行成功的测试类
而注解则很好地解决了这些问题
例如我们自定义一个 JUnit 的 Test 注解

//元注解:注解注解的注解
@Retention(RetentionPolicy.RUNTIME) //它注明的注解应该在运行时保留
@Target(ElementType.METHOD) //他注明的注解表明Test注解只在方法声明中才是合法的
public @interface Test {
}

由于Test注解没有参数 , 只是”标注”被注解的元素 , 所以它被称作标记注解 接下来我们测试我们自定义的Test注解 , 在没写测试方法之前 , 我们可以通过上面的解释猜到一下测试方法的运行结果

    public class Sample {
@Test
public static void t1() {
// 运行成功或失败
}
@Test
public static void t2() {
//抛出异常
throw new RuntimeException("BOOM");
}
@Test
public void t3() {
//运行成功或失败
}
@Test
public static void t4() {
//抛出异常
throw new RuntimeException("Crash");
}
}

这是由于Test注解只能被用作无参的静态方法标注
接下来我们完成测试方法 , 检验我们的猜测

public class RunTests {
public static void main(String[] args) {
int tests = 0;
int passed = 0;
try {
Class testClass = Class.forName(args[0]);
for (Method m : testClass.getDeclaredMethods()) {
//isAnnotationPresent告知该工具要运行哪些方法
if (m.isAnnotationPresent(Test.class)) {
tests++;
try {
//反射式的运行所有标注了Test的方法
m.invoke(null);
passed++;
//如果测试方法抛出异常,反射机制就会将他封装在InvocationTargetException并打印报告 如t2 t4
} catch (InvocationTargetException e) {
Throwable exc = e.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);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

控制台输出

    Invalid @Test:public static void Sample.t1()
public static void Sample.t2() failed:RuntimeException:BOOM
public static void Sample.t4() failed:RuntimeException:Crash
passed:1,Failed:3

结果正如我们所料
那么可以不可利用注解忽略异常组 , 使程序在抛出指定异常时依旧执行成功呢 ?让我们来测试一下
注解方法 –

    @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
//Classextends Exception>某个扩展Exception的类的Class对象 ; value:注解中的方法
Classextends
Exception>[] value();
}
Sample --
public class Sample2 {
@ExceptionTest({ArithmeticException.class,NullPointExcepition.class})
public static void t1() {
int i = 0;
i = i / 0;
}
}
main方法 --
public class RunTests {
public static void main(String[] args) {
int tests = 0;
int passed = 0;
try {
Class testClass = Class.forName(args[0]);
for (Method m : testClass.getDeclaredMethods()) {

//isAnnotationPresent告知该工具要运行哪些方法
if (m.isAnnotationPresent(Test.class)) {
tests++;
try {
//反射式的运行所有标注了Test的方法
m.invoke(null);
System.out.printf("测试 %s 失败:没有注解这个异常%n",m);
//如果测试方法抛出异常,反射机制就会将他封装在InvocationTargetException并打印报告
} catch (InvocationTargetException e) {
//提取注解参数的值 , 并用它检验该测试抛出的异常是否为正确的类型
Throwable exc = e.getCause();
Classextends Exception>[] excTypes = m.getAnnotation(ExceptionTest.class).value();
int oldPassed = passed;
for(Classextends
Exception> 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);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

以上的例子不揭露了注解的冰山一角 , 但它鲜明了表达了一个观点 , 既然有了注解 , 就不必再用命名模式了

总结:除了特定的程序员之外 , 大多数程序员都不必定义注解类型 . 但是所有的程序员都应该使用Java平台所提供的预定义的注解类型 . 还要考虑 IDE 或者静态分析工具所提供的任何注解 . 这种注解可以提升由这些工具所提供的诊断信息的质量 . 但是要注意这些注解还没有表转化 , 因此如果变换工具或者形成标准 , 就需要做更多地工作 .


推荐阅读
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • 本文详细解析了Java类加载系统的父子委托机制。在Java程序中,.java源代码文件编译后会生成对应的.class字节码文件,这些字节码文件需要通过类加载器(ClassLoader)进行加载。ClassLoader采用双亲委派模型,确保类的加载过程既高效又安全,避免了类的重复加载和潜在的安全风险。该机制在Java虚拟机中扮演着至关重要的角色,确保了类加载的一致性和可靠性。 ... [详细]
  • Java中不同类型的常量池(字符串常量池、Class常量池和运行时常量池)的对比与关联分析
    在研究Java虚拟机的过程中,笔者发现存在多种类型的常量池,包括字符串常量池、Class常量池和运行时常量池。通过查阅CSDN、博客园等相关资料,对这些常量池的特性、用途及其相互关系进行了详细探讨。本文将深入分析这三种常量池的差异与联系,帮助读者更好地理解Java虚拟机的内部机制。 ... [详细]
  • JVM钩子函数的应用场景详解
    本文详细介绍了JVM钩子函数的多种应用场景,包括正常关闭、异常关闭和强制关闭。通过具体示例和代码演示,帮助读者更好地理解和应用这一机制。适合对Java编程和JVM有一定基础的开发者阅读。 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • 本文探讨了如何在 Java 中将多参数方法通过 Lambda 表达式传递给一个接受 List 的 Function。具体分析了 `OrderUtil` 类中的 `runInBatches` 方法及其使用场景。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • AIX编程挑战赛:AIX正方形问题的算法解析与Java代码实现
    在昨晚的阅读中,我注意到了CSDN博主西部阿呆-小草屋发表的一篇文章《AIX程序设计大赛——AIX正方形问题》。该文详细阐述了AIX正方形问题的背景,并提供了一种基于Java语言的解决方案。本文将深入解析这一算法的核心思想,并展示具体的Java代码实现,旨在为参赛者和编程爱好者提供有价值的参考。 ... [详细]
  • Netty框架中运用Protobuf实现高效通信协议
    在Netty框架中,通过引入Protobuf来实现高效的通信协议。为了使用Protobuf,需要先准备好环境,包括下载并安装Protobuf的代码生成器`protoc`以及相应的源码包。具体资源可从官方下载页面获取,确保版本兼容性以充分发挥其性能优势。此外,配置好开发环境后,可以通过定义`.proto`文件来自动生成Java类,从而简化数据序列化和反序列化的操作,提高通信效率。 ... [详细]
  • 在分析Android的Audio系统时,我们对mpAudioPolicy->get_input进行了详细探讨,发现其背后涉及的机制相当复杂。本文将详细介绍这一过程及其背后的实现细节。 ... [详细]
  • 本文主要探讨了Java中处理ActionEvent事件的接口,以及一些常见的编程问题和解决方案,包括方法重载、成员变量访问、镜片质量检测等。 ... [详细]
author-avatar
萧海豚泳_756
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有