JUnit

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,504评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,434评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,089评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,378评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,472评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,506评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,519评论 3 413
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,292评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,738评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,022评论 2 329
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,194评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,873评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,536评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,162评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,413评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,075评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,080评论 2 352

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • 简介 测试 在软件开发中是一个很重要的方面,良好的测试可以在很大程度决定一个应用的命运。软件测试中,主要有3大种类...
    Whyn阅读 5,739评论 0 2
  • JUnit Intro Android基于JUnit Framework来书写测试代码。JUnit是基于Java语...
    chandarlee阅读 2,255评论 0 50
  • JUnit是一个开源的java自动化单元测试框架。由 Erich Gamma 和 Kent Beck 与1997年...
    zhaozhiwen阅读 2,074评论 2 13
  • 亲爱的宝宝: 这是妈妈给你写的第一封信。当你能读懂这封信的时候,应该比现在大很多了,不仅学会认字,估计还摔了不少跤...
    马尔菲阅读 243评论 0 1