JUnit

作者: wangdy12 | 来源:发表于2018-04-14 17:01 被阅读0次

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

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

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

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

    依赖

    添加JUnit 4依赖

    Gradle:

    dependencies {
      testCompile 'junit:junit:4.12'
    }
    

    Maven:

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    

    定义测试方法

    一个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<? super T> 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<Object[]> 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

        本文链接:https://www.haomeiwen.com/subject/cccfkftx.html