java单元测试

单元测试

平时的测的时候大多数是加一个Test类写一个main()方法就跑一遍
JUnit
JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计

以Eclipse为例,
当我们已经编写了一个Factorial.java文件后,
我们想对其进行测试,
需要编写一个对应的FactorialTest.java文件,
以Test为后缀是一个惯例,并分别将其放入src和test目录中。
最后,在Project - Properties - Java Build Path - Libraries中添加JUnit 5的库

使用JUnit进行单元测试,我们可以使用断言(Assertion)来测试期望结果,
可以方便地组织和运行测试,并方便地查看测试结果。
此外,JUnit既可以直接在IDE中运行,也可以方便地集成到Maven这些自动化工具中运行


这个类的功能很简单,但是测试的时候,我们要先初始化对象,
我们不必在每个测试方法中都写上初始化代码,而是通过@BeforeEach来初始化,
通过@AfterEach来清理资源:

因此,我们总结出编写Fixture的套路如下:
对于实例变量,在@BeforeEach中初始化,在@AfterEach中清理,它们在各个@Test方法中互不影响,因为是不同的实例;
对于静态变量,在@BeforeAll中初始化,在@AfterAll中清理,它们在各个@Test方法中均是唯一实例,会影响各个@Test方法。
大多数情况下,使用@BeforeEach和@AfterEach就足够了。
只有某些测试资源初始化耗费时间太长,以至于我们不得不尽量“复用”时才会用到@BeforeAll和@AfterAll。
最后,注意到每次运行一个@Test方法前,JUnit首先创建一个XxxTest实例,因此,每个@Test方法内部的成员变量都是独立的,
不能也无法把成员变量的状态从一个@Test方法带到另一个@Test方法

异常测试

@Test
void testNegative() {
    assertThrows(IllegalArgumentException.class, new Executable() {
        @Override
        public void execute() throws Throwable {
            Factorial.fact(-1);
        }
    });
}
JUnit提供assertThrows()来期望捕获一个指定的异常。
第二个参数Executable封装了我们要执行的会产生异常的代码。
当我们执行Factorial.fact(-1)时,必定抛出IllegalArgumentException。assertThrows()在捕获到指定异常时表示通过测试,
未捕获到异常,或者捕获到的异常类型不对,均表示测试失败。

有些童鞋会觉得编写一个Executable的匿名类太繁琐了。实际上,Java 8开始引入了函数式编程,所有单方法接口都可以简写如下:

@Test
void testNegative() {
    assertThrows(IllegalArgumentException.class, () -> {
        Factorial.fact(-1);
    });
}
测试异常可以使用assertThrows(),期待捕获到指定类型的异常;
对可能发生的每种类型的异常都必须进行测试

条件测试

在运行测试的时候,有些时候,我们需要排出某些@Test方法,
不要让它运行,这时,我们就可以给它标记一个@Disabled:

@Disabled
@Test
void testBug101() {
    // 这个测试不会运行
}
为什么我们不直接注释掉@Test,而是要加一个@Disabled?
这是因为注释掉@Test,JUnit就不知道这是个测试方法,而加上@Disabled,JUnit仍然识别出这是个测试方法,只是暂时不运行。它会在测试结果中显示:

Tests run: 68, Failures: 2, Errors: 0, Skipped: 5
类似@Disabled这种注解就称为条件测试,JUnit根据不同的条件注解,决定是否运行当前的@Test方法。

@Test
@EnabledOnOs(OS.WINDOWS) //这个就是判断条件
void testWindows() {
    assertEquals("C:\\test.ini", config.getConfigFile("test.ini"));
}

@Test
@EnabledOnOs({ OS.LINUX, OS.MAC })  //这个就是判断条件
void testLinuxAndMac() {
    assertEquals("/usr/local/test.cfg", config.getConfigFile("test.cfg"));
}
@EnableOnOs就是一个条件测试判断

@Test
@DisabledOnOs(OS.WINDOWS)
void testOnNonWindowsOs() {
    // TODO: this test is disabled on windows
}
只能在Java 9或更高版本执行的测试,可以加上@DisabledOnJre(JRE.JAVA_8):

@Test
@DisabledOnJre(JRE.JAVA_8)
void testOnJava9OrAbove() {
    // TODO: this test is disabled on java 8
}
只能在64位操作系统上执行的测试,可以用@EnabledIfSystemProperty判断:

@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void testOnlyOn64bitSystem() {
    // TODO: this test is only run on 64 bit system
}
需要传入环境变量DEBUG=true才能执行的测试,可以用@EnabledIfEnvironmentVariable:

@Test
@EnabledIfEnvironmentVariable(named = "DEBUG", matches = "true")
void testOnlyOnDebugMode() {
    // TODO: this test is only run on DEBUG=true
}
最后,万能的@EnableIf可以执行任意Java语句并根据返回的boolean决定是否执行测试。下面的代码演示了一个只能在星期日执行的测试:

@Test
@EnabledIf("java.time.LocalDate.now().getDayOfWeek()==java.time.DayOfWeek.SUNDAY")
void testOnlyOnSunday() {
    // TODO: this test is only run on Sunday
}

参数化测试

JUnit提供了一个@ParameterizedTest注解,用来进行参数化测试
使用参数化测试,可以提供一组测试数据,对一个测试方法反复测试。

参数既可以在测试代码中写死,也可以通过@CsvFileSource放到外部的CSV文件中

@ParameterizedTest
@ValueSource(ints = { 0, 1, 5, 100 })
void testAbs(int x) {
    assertEquals(x, Math.abs(x));
}
再用一组负数进行测试
@ParameterizedTest
@ValueSource(ints = { -1, -5, -100 })
void testAbsNegative(int x) {
    assertEquals(-x, Math.abs(x));
}

注意到参数化测试的注解是@ParameterizedTest,而不是普通的@Test



现在问题来了:参数如何传入?
最简单的方法是通过@MethodSource注解,它允许我们编写一个同名的静态方法来提供测试参数:
@ParameterizedTest
@MethodSource
void testCapitalize(String input, String result) {
    assertEquals(result, StringUtils.capitalize(input));
}

static List<Arguments> testCapitalize() {
    return List.of( // arguments:
            Arguments.arguments("abc", "Abc"), //
            Arguments.arguments("APPLE", "Apple"), //
            Arguments.arguments("gooD", "Good"));
}
上面的代码很容易理解:静态方法testCapitalize()返回了一组测试参数,
每个参数都包含两个String,正好作为测试方法的两个参数传入。

 如果静态方法和测试方法的名称不同,@MethodSource也允许指定方法名。但使用默认同名方法最方便。

另一种传入测试参数的方法是使用@CsvSource,它的每一个字符串表示一行,一行包含的若干参数用,分隔,因此,上述测试又可以改写如下:

@ParameterizedTest
@CsvSource({ "abc, Abc", "APPLE, Apple", "gooD, Good" })
void testCapitalize(String input, String result) {
    assertEquals(result, StringUtils.capitalize(input));
}
如果有成百上千的测试输入,那么,
直接写@CsvSource就很不方便。这个时候,
我们可以把测试数据提到一个独立的CSV文件中,然后标注上@CsvFileSource:

@ParameterizedTest
@CsvFileSource(resources = { "/test-capitalize.csv" })
void testCapitalizeUsingCsvFile(String input, String result) {
    assertEquals(result, StringUtils.capitalize(input));
}
JUnit只在classpath中查找指定的CSV文件,因此,test-capitalize.csv这个文件要放到test目录下,内容如下:

apple, Apple
HELLO, Hello
JUnit, Junit
reSource, Resource
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰明子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值