Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)

本文是Java反射学习总结系列的最后一篇了,这里贴出之前文章的链接,有兴趣的可以打开看看。

http://blog.csdn.net/a396901990/article/category/2302221

本文介绍了如何利用反射和注解去简单的模拟JUnit4单元测试框架,之所以选择JUnit4是因为4.0以后最大的改进就是使用了注解。需要注意的是这里并不是完全的模拟,只是简单实现了一下Runner类和JUnit注解相关的工作流程。所以本文的主要目的是介绍反射和注解的使用。废话不多说,直接进入正文。

首先来看一个Junit单元测试的小例子:

先定义一个简单的类,里面只有一个add计算加法的方法和一个divide计算除法的方法,divide方法需要判断除数不能为0否则抛出异常。

[java] view
plain
copy

  1. public class calculate {
  2. public int add(int a, int b) {
  3. return a + b;
  4. }
  5. public int divide(int a, int b) throws Exception {
  6. if (0 == b) {
  7. throw new Exception("除数不能为0");
  8. }
  9. return a / b;
  10. }
  11. }

接着写一个简单的JUnit测试类,对他进行单元测试

[java] view
plain
copy

  1. import static org.junit.Assert.*;
  2. import org.junit.After;
  3. import org.junit.Before;
  4. import org.junit.Test;
  5. public class calulateTest {
  6. private calculate cal;
  7. @Before     //使用JUint提供的注解标注此方法在执行测试方法前执行
  8. public void before() throws Exception {
  9. cal = new calculate();
  10. System.out.println("------------------");
  11. System.out.println("before test");
  12. }
  13. @After      //使用JUint提供的注解标注此方法在执行测试方法后执行
  14. public void after() throws Exception {
  15. System.out.println("after test");
  16. }
  17. @Test       //使用JUint提供的注解标注此方法为需要测试的方法
  18. public void addTest() {
  19. System.out.println("do add test");
  20. int result = cal.add(10, 20);
  21. //判断result和预期的值是否相等,在此例中如果result等于30则测试通过
  22. assertEquals(30, result);
  23. }
  24. @Test(expected = Exception.class)  //使用JUnit的Test注解,并且判断预期值是否是Exception
  25. public void div() throws Exception {
  26. System.out.println("do divide test");
  27. cal.divide(1, 0);   //调用1除以0,抛出异常
  28. }
  29. }

执行结果为:

before test

do add test

after test

------------------

before test

do divide test

after test

下面我们就用反射和注解的知识来模拟JUnit对于上面例子的实现。

这里先不着急看代码,先看梳理一下思路。

1.JUnit只可以知道一件事,那就是待测试类的名字,其他的一概不知。所以我们只能利用测试类的名字作为切入口

2.通过测试类的名字,使用反射去获取他的Class对象

3.然后通过该Class对象获得当前类中所有方法的Method数组

4.遍历这个Method数组,取得每一个Method对象

5.调用每一个Method对象的isAnnotationPresent(Annotation.class)方法,判断该方法是否被指定注解所修饰

6.本例中根据不同的注解,来判断调用方法的顺序。

7.如果Test注解有属性的话,则判断方法执行后的返回值,如果返回值等于预期的注解属性也就是expected = Exception.class则测试通过。

8.最后还有一个assertEquals方法,他去判断预期值和实际值是否相等来决定测试是否通过。

大致的思路有了,我们就可以开始模拟它了。

首先定义3个注解,分别是Before,Test,After。如果对于定义注解不清楚的同学请看我之前写的文章。

[java] view
plain
copy

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Before {
  4. }

[java] view
plain
copy

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Test {
  4. Class<? extends Object> expected() default String.class;
  5. }

[java] view
plain
copy

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface After {
  4. }

三个很简单的注解,都标注只能修饰方法,保留策略为运行时,这样可以被反射读取到。

只有Test注解中定义了一个属性,类型可以为任何类型的Class对象,默认值为String类型的Class对象。

接下来定义我们模拟的JUnit类,这里为了方便我将所有能用到的都写在一个MyJUnit类中。他对外只有一个构造方法和一个run方法。还有一个对比用的assertEquals方法

[java] view
plain
copy

  1. public class MyJUnit {
  2. // 存放所有标注了before注解的集合
  3. private List<Method> beforeMethod;
  4. // 存放所有标注了after注解的集合
  5. private List<Method> afterMethod;
  6. // 存放所有标注了test注解的集合
  7. private List<Method> testMethod;
  8. // 存放需要捕捉的异常集合
  9. private static List<Exception> exceptions;
  10. // 被测试类的实例化对象
  11. private Object object;
  12. // 被测试类的Class对象
  13. private Class<?> testClass;
  14. //自定义MyJUnit类的构造方法,用于接收被测试类的名字然后初始化需要的变量和方法
  15. public MyJUnit(String testName) {
  16. super();
  17. try {
  18. beforeMethod = new ArrayList<>();
  19. afterMethod = new ArrayList<>();
  20. testMethod = new ArrayList<>();
  21. exceptions = new ArrayList<>();
  22. //使用反射根据传递的类名生成对象
  23. testClass = Class.forName(testName);
  24. object = testClass.newInstance();
  25. //获取所有的方法并根据注解进行分类
  26. getAllMethods();
  27. } catch (Exception e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. // 根据注解获取所有的方法
  32. private void getAllMethods() {
  33. Method[] methods = testClass.getMethods();
  34. for (Method m : methods) {
  35. // 找到被before修饰的方法,放入before方法的集合中
  36. if (m.isAnnotationPresent(Before.class)) {
  37. beforeMethod.add(m);
  38. }
  39. // 找到被After修饰的方法,放入after方法的集合中
  40. if (m.isAnnotationPresent(After.class)) {
  41. afterMethod.add(m);
  42. }
  43. // 找到被test修饰的方法,放入test方法的集合中
  44. if (m.isAnnotationPresent(Test.class)) {
  45. testMethod.add(m);
  46. }
  47. }
  48. }
  49. // run方法
  50. public void run() {
  51. // 运行所有的测试方法
  52. for (Method method : testMethod) {
  53. runTest(method);
  54. }
  55. // 判断捕捉的异常集合,如果大小为0则没有异常,测试通过。有异常则表示测试不通过并打印异常信息
  56. if (exceptions.size() == 0) {
  57. System.out.println("通过测试");
  58. } else {
  59. for (Exception e : exceptions) {
  60. System.out.println("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
  61. System.out.println("测试不通过,错误的内容为:");
  62. e.printStackTrace();
  63. }
  64. }
  65. }
  66. // 按照before test after的顺序执行
  67. private void runTest(Method method) {
  68. try {
  69. runBefores();
  70. runTestMethod(method);
  71. runAfters();
  72. } catch (Exception e) {
  73. e.getMessage();
  74. throw new RuntimeException(
  75. "test should never throw an exception to this level");
  76. }
  77. }
  78. // 执行所有after标注的方法
  79. private void runAfters() throws Exception {
  80. for (Method m : afterMethod) {
  81. m.invoke(object, new Object[] {});
  82. }
  83. }
  84. // 执行所有before标注的方法
  85. private void runBefores() throws Exception {
  86. for (Method m : beforeMethod) {
  87. m.invoke(object, new Object[] {});
  88. }
  89. }
  90. // 执行test方法
  91. private void runTestMethod(Method method) {
  92. // 判断测试是否通过的标志
  93. boolean passCheck = false;
  94. try {
  95. // 获得Test注解
  96. Test testAnnotation = method.getAnnotation(Test.class);
  97. // 判断test注解标注的属性是否为需要的类型,这里可以根据需要加入不同的判断条件
  98. if (testAnnotation.expected().newInstance() instanceof Exception) {
  99. passCheck = true;
  100. }
  101. method.invoke(object);
  102. } catch (Exception e) {
  103. // 判断通过则测试通过,否者将异常加入到异常集合中
  104. if (passCheck) {
  105. return;
  106. } else {
  107. addExceptions(e);
  108. }
  109. }
  110. }
  111. private static void addExceptions(Exception e) {
  112. exceptions.add(e);
  113. }
  114. // 自定义的assertEquals方法,判断两个值是否相等,可以根据需要加入更多类型的判断,如果相等则通过,否则new一个异常加入到异常集合中
  115. static public void assertEquals(Object expected, Object actual) {
  116. if (expected.equals(actual)) {
  117. return;
  118. } else {
  119. addExceptions(new Exception("预期值与实际值不相等"));
  120. }
  121. }
  122. }

注解和JUnit类都定义好后可以写测试的方法了,和之前的测试方法没有区别,只是这次导包导入的都是我们自定义的包。

[java] view
plain
copy

  1. import static gxy.test.Junit.MyJUnit.*;
  2. public class MyCalulateTest {
  3. private Calculate cal;
  4. @Before
  5. public void before() throws Exception {
  6. cal = new Calculate();
  7. System.out.println("------------------");
  8. System.out.println("before test");
  9. }
  10. @After
  11. public void after() throws Exception {
  12. System.out.println("after test");
  13. }
  14. @Test
  15. public void addTest() {
  16. System.out.println("do add test");
  17. int result = cal.add(10, 20);
  18. // 这里的预期值为40,实际为30,所以这个方法通过不了测试
  19. assertEquals(40, result);
  20. }
  21. @Test(expected = Exception.class)
  22. public void divTest() throws Exception {
  23. System.out.println("do divide test");
  24. // 调用1除以0,抛出异常
  25. cal.divide(1, 0);
  26. }
  27. }

为了检验测试效果,这里对于addTest的方法中assertEquals方法传入的预期值和实际值不同。

下面看最后的运行类。

[java] view
plain
copy

  1. public static void main(String[] args) throws Exception {
  2. MyJUnit myJUnit = new MyJUnit("gxy.test.Junit.MyCalulateTest");
  3. myJUnit.run();
  4. }

只有2行代码,传入需要测试的类的名字,然后执行run方法。

测试结果:

------------------

before test

do add test

after test

------------------

before test

do divide test

after test

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

测试不通过,错误的内容为:

java.lang.Exception: 预期值与实际值不相等

at gxy.test.Junit.MyJUnit.assertEquals(MyJUnit.java:139)

at gxy.test.Junit.MyCalulateTest.addTest(MyCalulateTest.java:26)

at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)

at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)

at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)

at java.lang.reflect.Method.invoke(Unknown Source)

at gxy.test.Junit.MyJUnit.runTestMethod(MyJUnit.java:119)

at gxy.test.Junit.MyJUnit.runTest(MyJUnit.java:85)

at gxy.test.Junit.MyJUnit.run(MyJUnit.java:66)

at gxy.test.Junit.FinalTest.main(FinalTest.java:13)

本文只是简单的模拟了一下在JUnit中反射和注解的使用,而且在很多框架中很多都利用了反射和注解这对黄金组合来实现一些如权限判断,调用等等很多功能。所以说反射还是值得好好学习和研究的。

反射学习总结系列博文断断续续写了一个多月,这篇是最后一篇了。通过这一个月的学习对反射的基本概念和使用算是有了一个了解,有时间还需要深入的学习。

这里需要提一下,学习的资料主要是从网上下的系列视频。主要借鉴了其中中的思路和一些概念类的东西,但是文章中的例子都是我自己写的。最后向大家推荐一下这个视频吧,不是做广告,讲的确实不错,讲课的老师叫张龙,口齿清晰讲的很深入。在大学时看的马士兵的视频,比较适合入门,这个适合晋级。再想继续晋级就得看书了,哈哈。

由于一些烂七八糟的原因我就不提供这个视频的下载地址了,如果需要请自己上网搜,或者留下邮箱我给链接发过去。

最后把本例的代码上传了,导入就可以运行。点击打开下载链接

Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架),布布扣,bubuko.com

时间: 2024-10-23 16:07:56

Java反射学习总结终(使用反射和注解模拟JUnit单元测试框架)的相关文章

Java高级特性 第10节 IDEA和Eclipse整合JUnit测试框架

一.IDEA整合Junit测试框架 1.安装插件 打开File菜单的下拉菜单settings[设置] : 点击左侧Plugins[插件]菜单 在输入框中输入JUnitGenerator 2.0,点击Install 重启IDEA 2.配置插件 打开settings[设置] 点击左侧Other Settings[其他]菜单 点击左侧JUnit Generator菜单 点击Properties[属性]页签 修改Output Path[输出路径]为${SOURCEPATH}/../../test/jav

Android(java)学习笔记106:反射机制

反射机制: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制. 要想解剖一个类,必须先要获取到该类的字节码文件对象.而解剖使用的就是Class类中的方法.所以先要获取到每一个字节码文件对应的Class类型的对象. 1 package cn.itcast_01; 2 3 /* 4 * 反射:就是通过class文件对象,去使用该文件中的成员变量

java核心学习(四十) 使用反射生成JDK动态代理

java.lang.reflect包下提供了一个Proxy类和InvocationHandler接口,用于生成动态代理类和动态代理对象. 一.使用Proxy.InvocationHandler创建动态代理 这里要注意,在java.net包下也有一个Proxy类,不过这个类是用于设置代理服务器的,莫混淆.. Proxy提供了如下两个方法创建动态代理类和动态代理实例: 实际上即使采用第一种方法创建动态代理类,如果程序需要通过该代理类来创建对象,依然需要传入一个InvocationHandler对象,

Java Junit测试框架

Java    Junit测试框架 1.相关概念 ? JUnit:是一个开发源代码的Java测试框架,用于编写和运行可重复的测试.它是用于单元测试框架体系xUnit的一个实例(用于java语言).主要用于白盒测试,回归测试. ? 白盒测试:把测试对象看作一个打开的盒子,程序内部的逻辑结构和其他信息对测试人 员是公开的. ? 回归测试:软件或环境的修复或更正后的再测试,自动测试工具对这类测试尤其有用. ? 单元测试:最小粒度的测试,以测试某个功能或代码块.一般由程序员来做,因为它需要知道内部程序设

java中反射学习整理

转载请注明:http://blog.csdn.net/j903829182/article/details/38405735 反射主要是指程序可以访问,检测和修改它本身的状态或行为的一种能力. java中反射是一种强大的工具,它能够创建灵活的代码,这些代码可以在运行时装载,无须在组件之间进行链接.反射允许在编写与执行时,使程序能够接入到jvm中的类的内部信息,而不是源代码中选定的类协作的代码.这使反射成为构建灵活应用代码的主要工具.需要注意的是,如果使用不当,反射的成本会很高. package

Java反射学习总结

广州疯狂软件教育Java培训,iOS培训分享Class类是Reflection API中核心的类,他位于java.lang.Class列出一些常用的方法.- getName() : 获得类的完整名字- getFields() : 获得类的public类型的属性- getDeclaredFields() : 获得类的所有属性- getMethods() : 获得类的public类型的方法- getDeclaredMethods() : 获得类的所有方法- getMethod(String name

Java反射学习总结四(动态代理使用实例和内部原理解析)

通过上一篇文章介绍的静态代理Java反射学习总结三(静态代理)中,大家可以发现在静态代理中每一个代理类只能为一个接口服务,这样一来必然会产生过多的代理,而且对于每个实例,如果需要添加不同代理就要去添加相应的代理类.解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能或者说去动态的生成这个代理类,那么此时就必须使用动态代理完成. 动态代理知识点: Java动态代理类位于java.lang.reflect包下,主要有以下一个接口和一个类: 1.InvocationHandler接口:    

Java反射学习:深入学习Java反射机制

一.Java反射的理解(反射是研究框架的基础之一) Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法:对于任意一个对象,都能够调用它的任意一个方法和属性:这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制. 二.逐步分析 参考:https://blog.csdn.net/u012585964/article/details/52011138 1.关于Class 1.Class是一个类,一个描述类的类(也就是描述类本身),封装了描述方法的Met

黑马程序员--Java基础学习笔记【单例设计模式、网络编程、反射】

------Java培训.Android培训.iOS培训..Net培训.期待与您交流! ------- 设计模式分类: 创建模式:是对类的实例化过程的抽象化,又分为类创建模式和对象创建模式 类创建模式:工厂方法模式 对象-:简单工厂(静态工厂方法)模式.抽象工厂模式.单例模式.建造模式- 结构模式:描述如何将类或者对象结合在一起形成更大的结构 适配器模式.缺省模式.合成模式.装饰模式(包装模式).门面模式- 行为模式:对不同的对象之间划分责任和算法的抽象化 不变模式.策略模式.迭代子模式.命令模