junit是一个非常优秀的测试框架,里面给我们提供了大量的注解
常用的有:
@BeforeClass //针对所有测试,只执行一次,且必须为static void @Before //初始化方法 @Test //测试方法,在这里可以测试期望异常和超时时间 @Ignore //忽略的测试方法 @After //释放资源 @AfterClass //针对所有测试,只执行一次,且必须为static void
执行顺序( Junit生命周期):
一个测试类单元测试的执行顺序为: @BeforeClass –> @Before –> @Test –> @After –> @AfterClass 每一个测试方法的调用顺序为: @Before –> @Test –> @After
其中要注意的是在一个测试用例中如果有多个@Test执行@BeforeClass、和 @AfterClass也只会执行一次,我们可以简单测试一下:
public class TestJunit {
static Calculator calculator;
@BeforeClass
public static void BeforeClass(){
System.out.println("BeforeClass");
calculator = new Calculator();
}
@Before
public void before(){
System.out.println("before");
}
@Test
public void testAdd(){
Assert.assertEquals(3,calculator.add(1,2));
System.out.println("add测试成功");
}
@Test
public void testSub(){
Assert.assertEquals(-1,calculator.sub(1,2));
System.out.println("sub测试成功");
}
@Ignore
public void Ignore(){
System.out.println("myIgnore");
}
@After
public void After(){
System.out.println("After");
}
@AfterClass
public static void AfterClass(){
System.out.println("AfterClass");
}
}
class Calculator{
public int add(int a,int b){
return a + b;
}
public int sub(int a,int b){
return a - b;
}
}
运行结果:
2. DiyJunit测试框架仿写注解 2.1 先定义我们自己的注解 我们从上一期可以知道每个注解它必须要带两个注解:@Retention()、@Target(),在这里我们声明的注解仅仅是用来标记一个方法,且必须要让字节码文件能被我们的启动器加载到,所以所有的注解都可以定义为:
//其他注解也是一样,不需要带属性
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface myAfter {
}
其实我们通过Junit的源码可以看到,它里面的绝大部分注解也没有属性,仅仅起到标记作用
2.2 将自己定义的注解加到测试类上public class TestDiyJunit {
static Calculator calculator;
@myBeforeClass
public static void BeforeClass(){
System.out.println("myBeforeClass");
calculator = new Calculator();
}
@myBefore
public void before(){
System.out.println("mybefore");
}
@myTest
public void testAdd(){
System.out.println("add测试:"+calculator.add(1,2));
}
@myTest
public void testSub(){
System.out.println("sub测试:"+calculator.sub(1,2));
}
@myIgnore
public void Ignore(){
System.out.println("myIgnore");
}
@myAfter
public void After(){
System.out.println("myAfter");
}
@myAfterClass
public static void AfterClass(){
System.out.println("myAfterClass");
}
}
class Calculator{
public int add(int a,int b){
return a + b;
}
public int sub(int a,int b){
return a - b;
}
}
2.3 注解解析器
public class myRunner {
public static void run(Class cls){
//1. 取出类的反射实例里的所有方法
Method[] methods = cls.getMethods();
List myBeforeClassMethod = new ArrayList<>();
List myBeforeMethod = new ArrayList<>();
List myTestMethod = new ArrayList<>();
List myAfterMethod = new ArrayList<>();
List myAfterClassMethod = new ArrayList<>();
//2. 遍历所有的方法
for(Method m : methods){
Annotation[] ans = m.getDeclaredAnnotations();
//遍历注解,将注解标记的方法放到对应的集合中
for(Annotation an : ans){
if(an instanceof myBeforeClass){
myBeforeClassMethod.add(m);
}else if(an instanceof myBefore){
myBeforeMethod.add(m);
}else if(an instanceof myTest){
myTestMethod.add(m);
}else if(an instanceof myAfter){
myAfterMethod.add(m);
}else if(an instanceof myAfterClass){
myAfterClassMethod.add(m);
}
}
}
//3. 按照Junit生命周期激活方法
try {
//通过无参构造得到一个反射实例
Object obj = cls.newInstance();
//1. 先执行@myBeforeClass标记的方法,这个方法只能标记在静态方法上面,且只执行一次
for(Method m : myBeforeClassMethod){
m.invoke(null);
}
//2. 根据Junit生命周期我们可以知道,每次@Test执行前后都会执行@myBefore和@myAfter注解的方法
for(Method testMethod : myTestMethod){
//激活所有的@Before方法
for(Method m : myBeforeMethod){
m.invoke(obj);
}
//激活@Test方法
for(Method m : myTestMethod){
m.invoke(obj);
}
//激活所有的@myAfter方法
for(Method m : myAfterMethod){
m.invoke(obj);
}
}
//3. 最后执行@myAfterClassMethod方法,这个方法只能标记在静态方法上面,且只执行一次
for(Method m : myAfterClassMethod){
//静态方法反射激活: m.invoke(null,参数)
m.invoke(null);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
小细节
在实现@myBeforeClass注解标记的方法时笔者并没有对其进行检查,因为Junit官方注解要求其其添加的方法必须为static void,这里牵涉到Java反射中比较有意思的几个点,单独拿出来写一下
首先就是在Java反射中:
实例方法反射激活: m.invoke(实例对象,参数) 静态方法反射激活: m.invoke(null,参数)
那么我们怎么判断一个注解修饰的方法是不是静态的呢?
可以通过:
int modifiers = m.getModifiers();//m是反射拿到的方法
这里返回的是一个int类型的值,此方法返回值的规定为:
什么都不加 是0 , public 是1 ,private 是 2 ,protected 是 4,static 是 8 ,final 是 16。
所以我们可以通过
modifiers & 8 == 8
来判断是不是被static修饰的方法
00001000 00001000 & 00001000 00000001 = 00001000 00000000
当然 Java也给我包装好了方法,底层也是通过逻辑&实现的
怎么判断一个方法的返回值类型呢?
Type type = m.getReturnType(); String typeName = type.getTypeName();
所以完整代码为:
//这里要判断这个方法是否为静态且返回值为空的
int modifiers = m.getModifiers();
Type type = m.getReturnType();
String typeName = type.getTypeName();
if(Modifier.isStatic(modifiers) && "void".equals(typeName)){
myBeforeClassMethod.add(m);
}
到这里不得不感叹一句Java反射的强大之处!!!
2.4 手动注入我们要加载的类
public class TestMain {
//我们这里手动实现
public static void main(String[] args) {
//通过注解解析器myRunner加载我们需要解析的响应注解或者类
myRunner.run(TestDiyJunit.class);
}
}
2.5 测试
2.6 总结
通过上面的小栗子我们可以知道注解有很多作用,例如对某一事物进行添加注释说明,可以存放一些信息等等,其实笔者觉得任何东西上手实操后才能更加的理解深刻,读者可以多自定义一些注解,并进行使用,遇到疑问在带有目的性的学习,这样能够理解更加深刻,笔者的另一篇文章DiyTomcat里面也防写了@WebServlet(),读者可以自己动手尝试写一下



