JUnit

JUnit是一个编写测试代码的框架,它是单元测试框架的xUnit体系结构中的一个,目前主要使用的是JUnit 4JUnit 5

测试代码验证相应的代码是否产生了预期的状态/结果(状态测试)或按照预期的顺序执行事件(行为测试)

单元测试中测试的单元是一个方法,类等,其外部依赖会被移除
单元测试不适合测试复杂的用户界面或组件交互,这种情况应该使用集成测试(TestNG)。

测试代码单独放在一个文件夹中,Maven和Gradle构建工具默认路径是src/test/java

依赖

添加JUnit 4依赖

Gradle:

dependencies {
  testCompile 'junit:junit:4.12'
}

Maven:


  junit
  junit
  4.12
  test

定义测试方法

一个JUnit测试是写在专门用于测试的类内部的方法,这个类叫做Test类,通常以Test作为类名称的结尾
JUnit使用注释将方法标记为测试方法并对其进行配置,测试方法使用@Test注解标明

下表是了JUnit中4.x和5.x版本中基本的注释,这些注解都作用于方法

名称 描述
import org.junit.* 注解都位于该包下
@Test(expected = xxx.class , timeout = xx) 表明该方法是测试方法,如果不抛出异常,就表示测试成功过,可以添加参数,expected指定异常类型,如果不抛出异常或者抛出其他异常都算作是失败,只有抛出指定类型的异常才算成功,如果执行时间超过timeout,算作失败
@Before 用于public void方法,在类中的每个测试之前执行,用于输入数据,初始化类等
@After 用于public void方法,该方法在执行每个测试后执行,用于释放资源等
@BeforeClass 用于 public static void 的无参方法,在类中的任何测试方法之前运行一次 ,用于test之间共享的数据
@AfterClass 用于 public static void 的无参方法,在类中的任何测试方法之前运行一次 ,用于释放BeforClass申请的资源
@Ignore("Why disabled") 用于禁止测试方法执行,例如测试代码需要修改,执行代价比较大

断言

测试方法内部经常使用assert,JUnit为每种基本数据类型,Object和数组都提供了断言方法,方法位于org.junit.Assert类,参数顺序是期望值,之后是实际值,可以把String类型作为第一个参数,当期望值和实际值不一致时,抛出AssertionException异常,String类型参数作为失败信息输出

方法示例:

assertArrayEquals(boolean[] expecteds, boolean[] actuals)//各种数据类型都有
assertArrayEquals(String message, boolean[] expecteds, boolean[] actuals)
assertTrue(boolean condition)
assertNotNull(String message, Object object)
assertThat(String reason, T actual, Matcher matcher)
import org.junit.Test;

public class AssertTests {
  @Test
  public void testAssertArrayEquals() {
    byte[] expected = "trial".getBytes();
    byte[] actual = "trial".getBytes();
    org.junit.Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
  }
  @Test
  public void testAssertTrue() {
    org.junit.Assert.assertTrue("failure - should be true", true);
  }
}

JUnit测试套件

如果有多个测试类,可以创建一个测试套件把它们结合到一起,然后会按照顺序运行套件内的所有的测试类

import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;

@RunWith(Suite.class) //表明使用 org.junit.runners.Suite 运行测试类
@SuiteClasses({  //  告诉Suite runner运行的的测试类及顺序
        MyClassTest.class,
        MySecondClassTest.class })
public class AllTests {
// 这个类用作占位符,不需要具体的方法
}

禁用测试

一种是使用@Ignore注解,另外可以在测试方法内部调用org.junit.Assume类的方法,如果假定失败,中断测试,不再执行

// 在Linux系统下不再进行测试
Assume.assumeFalse(System.getProperty("os.name").contains("Linux"));

参数化测试

参数化测试类特点

  • 只含有一个Test函数,这个函数会使用不同的参数多次执行
  • 类名称通过@RunWith(Parameterized.class)注解标注
  • 一个静态方法使用@Parameters注解,生成并返回测试数据,
  • 使用构造函数,接收输入数据,或者直接使用@Parameter注解public字段,将输入数据直注入到字段中
import static org.junit.Assert.assertEquals;

import java.util.Arrays;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;

@RunWith(Parameterized.class)
public class FibonacciTest {
    // 创建测试数据
    @Parameters(name = "{index}: fib({0})={1}") // 可以通过测试数据生成测试名称,index 索引,{0}第一个参数值
    public static Collection data() {
        return Arrays.asList(new Object[][] {
                 { 0, 0 }, { 1, 1 }, { 2, 1 }, { 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 }  
           });
    }

    //使用注解注入参数,Parameter(value)中的value对应于Parameters产生的输入参数数组的索引
    @Parameter // 第一个参数,默认索引为0,省略
    public /* NOT private */ int fInput;

    @Parameter(1) 
    public /* NOT private */ int fExpected;

      // 使用构造函数,处理输入输入
//    private int input;
//    private int expected;

//    public FibonacciTest(int input, int expected) {
//        this.input = input;
//        this.expected = expected;
//    }

    @Test
    public void test() {
        assertEquals(fExpected, Fibonacci.compute(fInput));
    }
}

public class Fibonacci {
    ...
}

JUnit 规则

@Rule注解用来标出测试类的公共字段,字段类型为TestRule

用来控制一个或一组测试方法如何运行并报告,它可以实现之前通过方法注解(org.junit.Beforeorg.junit.After等)完成的所有功能,并且更加有效

多个TestRule可以应用到一个测试方法,TestRule接口有很多具体的实现

ErrorCollector:在一个测试方法中收集多个异常
ExpectedException:指定要抛出特定异常
ExternalResource:抽象类,在测试之前启动外部资源,结束后关闭,例如启动和停止服务器,它是TemporaryFolder的父类
TemporaryFolder:创建临时文件,并在测试后删除
TestName:用于在测试方法内部获得当前测试方法的名称
TestWatchman:记录测试操作
Timeout:设定时限,测试超时后失败
Verifier:如果对象状态不正确,则测试失败
RuleChain:用来设定TestRule的执行顺序

public class RuleExceptionTesterExample {

  @Rule
  public ExpectedException exception = ExpectedException.none();

  @Test
  public void throwsIllegalArgumentExceptionIfIconIsNull() {
    exception.expect(IllegalArgumentException.class);
    exception.expectMessage("Negative value not allowed");
    ClassToBeTested t = new ClassToBeTested();
    t.methodToBeTest(-1);
  }
}

自定义规则

大多数规则都可以通过扩展ExternalResource实现,如果需要更多的测试信息,可以自己实现TestRule接口

该例子中,TestLogger为每个test提供一个命名logger

package org.example.junit;

import java.util.logging.Logger;

import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;

public class TestLogger implements TestRule {
  private Logger logger;

  public Logger getLogger() {
    return this.logger;
  }

  @Override
  public Statement apply(final Statement base, final Description description) {
    return new Statement() {
      @Override
      public void evaluate() throws Throwable {
        logger = Logger.getLogger(description.getTestClass().getName() + '.' + description.getDisplayName());
        base.evaluate();
      }
    };
  }
}

使用该TestRule

import java.util.logging.Logger;

import org.example.junit.TestLogger;
import org.junit.Rule;
import org.junit.Test;

public class MyLoggerTest {

  @Rule
  public final TestLogger logger = new TestLogger();

  @Test
  public void checkOutMyLogger() {
    final Logger log = logger.getLogger();
    log.warn("Your test is showing!");
  }

}

类别

对于一组测试类,通过@Category标记类和方法所属的类别,Categories runner会获取类别信息,并且只运行在@IncludeCategory中指定的类别及其子类别

从给定的一组测试类中,只运行使用@IncludeCategory注释给出的类别或该类别的子类型注释的类和方法。
请注意,现在,使用@Category注释套件不起作用。 类别必须使用直接方法或类进行注释。

   public interface FastTests {}  // 类别标记 
   public interface SlowTests {}
   public interface SmokeTests{}
  
   public static class A {
       @Test
       public void a() {
           fail();
       }
  
       @Category(SlowTests.class)
       @Test
       public void b() {
       }
  
       @Category({FastTests.class, SmokeTests.class})
       @Test
       public void c() {
       }
   }
  
   @Category({SlowTests.class, FastTests.class})
   public static class B {
       @Test
       public void d() {
       }
   }
  
   @RunWith(Categories.class)
   @IncludeCategory(SlowTests.class)
   @SuiteClasses({A.class, B.class}) //  Categories 是一种套件Suite
   public static class SlowTestSuite {
       // 运行 A.b 和 B.d, 不运行  A.a 和 A.c
   }
   
   @RunWith(Categories.class)
   @IncludeCategory({FastTests.class, SmokeTests.class})
   @SuiteClasses({A.class, B.class})
   public static class FastOrSmokeTestSuite {
       // 运行 A.c 和 B.d
   }

可以在Gradle内指定要运行的JUnit类别

test {
    useJUnit {
        includeCategories 'org.gradle.junit.CategoryA'
        excludeCategories 'org.gradle.junit.CategoryB'
    }
}

运行测试代码

如果在IDE(NetBeans/Eclipse/IntelliJ IDEA等)内运行JUnit测试代码,直接以内置的图形化形式运行

也可以通过应用程序调用测试类,获取测试结果,核心是org.junit.runner.JUnitCore.runClasses(TestClass1.class, ...);方法

import org.junit.runner.JUnitCore;
import org.junit.runner.Result;
import org.junit.runner.notification.Failure;

public class MyTestRunner {
  public static void main(String[] args) {
    Result result = JUnitCore.runClasses(MyClassTest.class);
    for (Failure failure : result.getFailures()) {
      System.out.println(failure.toString());
    }
  }
}

也可以直接以命令形式运行

 java org.junit.runner.JUnitCore TestClass1 [...other test classes...]

测试执行顺序

JUnit没有指定测试方法调用的顺序,编写测试代码时不应该假定任何顺序,即所有测试方法都可以以任意顺序执行,一个测试不应该依赖于其他测试。4.11 以后可以在测试类上添加注解来定义测试方法的执行顺序

MethodSorters.DEFAULT // 默认情况,执行顺序确定,但不确定具体如何
MethodSorters.JVM // 依照JVM返回的顺序,每次顺序都可能会变化
MethodSorters.NAME_ASCENDING // 通过测试方法的名称排序

// 示例
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MethodOrderTest {
//测试方法
}

JUnit 5

JUnit 5 由几大不同的模块组成,这些模块分别来自三个不同的子项目
JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

具体使用可以参考junit5 中文
Junit 4相关可以参考 junit4 wiki

你可能感兴趣的:(JUnit)