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

转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持!

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

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

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

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

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

copy
 

public calculate {

  • add( a,  b) {
  • a + b;
  • divide( a,  b)  Exception {
  • ( == b) {
  • Exception();
  • }
  • a / b;
  • }
  • }

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

copy
 

import org.junit.Assert.*;

  • org.junit.After;
  • import import public calulateTest {
  • calculate cal;
  • before()  Exception {
  • cal =  calculate();
  • );
  • System.out.println(
  • after()  Exception {
  • addTest() {
  • System.out.println(  result = cal.add(, );
  • , result);
  • }
  • (expected = Exception.)
  • div()  Exception {
  • System.out.println( , );
  • }
  • }

执行结果为:

before test
do add test
after test
------------------
before test
do divide test
after test

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

下面我们就用反射和注解的知识来模拟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。如果对于定义注解不清楚的同学请看我之前写的文章。

copy
 

@Target
@Retention
public Before {

  • }
copy
 

@Target
@Retention
public Test {

  • Class<?  Object> expected()  String.;
  • }
copy
 

@Target
@Retention
public After {

  • }

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

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

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

copy
 

public MyJUnit {

  • List<Method> beforeMethod;
  • List<Method> afterMethod;
  • List<Method> testMethod;
  • List<Exception> exceptions;
  • Object object;
  • Class<?> testClass;
  • MyJUnit(String testName) {
  • ();
  • {
  • beforeMethod =  ArrayList<>();
  • ArrayList<>();
  • testMethod =  ArrayList<>();
  • ArrayList<>();
  • testClass = Class.forName(testName);
  • getAllMethods();
  • (Exception e) {
  • getAllMethods() {
  • (Method m : methods) {
  • (m.isAnnotationPresent(Before.)) {
  • (m.isAnnotationPresent(After.)) {
  • (m.isAnnotationPresent(Test.)) {
  • run() {
  • (Method method : testMethod) {
  • runTest(method);
  • (exceptions.size() == ) {
  • );
  • }  {
  • (Exception e : exceptions) {
  • System.out.println();
  • );
  • e.printStackTrace();
  • runTest(Method method) {
  • {
  • runBefores();
  • (Exception e) {
  • e.getMessage();
  • RuntimeException(
  • runAfters()  Exception {
  • (Method m : afterMethod) {
  • Object[] {});
  • }
  • runBefores()  Exception {
  • (Method m : beforeMethod) {
  • m.invoke(object,  Object[] {});
  • runTestMethod(Method method) {
  • passCheck = ;
  • {
  • Test testAnnotation = method.getAnnotation(Test.);
  • (testAnnotation.expected().newInstance()  Exception) {
  • ;
  • }
  • (Exception e) {
  • (passCheck) {
  • ;
  • }  {
  • addExceptions(Exception e) {
  • assertEquals(Object expected, Object actual) {
  • (expected.equals(actual)) {
  • ;
  • }  {
  • Exception());
  • }
  • }

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

copy
 

import gxy.test.Junit.MyJUnit.*;

  • public MyCalulateTest {
  • Calculate cal;
  • before()  Exception {
  • Calculate();
  • System.out.println();
  • after()  Exception {
  • System.out.println(
  • addTest() {
  • result = cal.add(, );
  • assertEquals(, result);
  • (expected = Exception.)
  • divTest()  Exception {
  • , );
  • }
  • }

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

下面看最后的运行类。

copy
 

public  main(String[] args)  Exception {

  • MyJUnit myJUnit =  MyJUnit();
  • }

只有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反射学习总结二(用反射调用对象的私有属性和方法)


下一篇:Java反射学习系列-绪论