Jnuit单元测试
针对最小的功能单元编写测试代码:Java中最小的单元是方法,java单元测试针对的是方法的测试
main()测试的缺点
- 无法将测试的代码分离
- 无法打印测试结果和期望值对比
因此需要给测试框架,进行单元测试
Junit
开源的Java语言测试框架,可以给出成功的测试和失败的测试,还可以生成测试报告,不仅可以统计测试的成功率,还可以统计测试的代码覆盖率。
使用
-
创建maven项目
maven作用: a.规范目录结构 b.代码仓库
-
引入junit最新依赖
<dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-api</artifactId> <version>5.7.0</version> <scope>test</scope> </dependency> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter-params</artifactId> <version>5.6.2</version> <scope>test</scope> </dependency>在mvnrepository.com(mvn仓库)查找
3.方法上加 @Test注解,表明此方法为测试方法
常用Assertion中的断言方法
- assertEquals :验证是否相等
- assertTrue: 期待结果为true
- assertFalse:期待结果为false
- assertNotNull:期待结果非null
- assertArrayEquals:期待结果为数组,并且每个元素的值均相等
- ......
public class Factorial { public static long fact(long n) { long r = 1; for (long i = 1; i <= n; i++) { r = r * i; } return r; } } //阶乘 3!= 1*2*3测试
public class FactorialTest { @Test void testFact() { assertEquals(1, Factorial.fact(1)); assertEquals(2, Factorial.fact(2)); assertEquals(6, Factorial.fact(3)); } }4.实际方法中很多都不是静态方法,需要初始化,这是该怎么办呢?
public class Calculator { private long n = 0; public long add(long x) { n = n + x; return n; } public long sub(long x) { n = n - x; return n; } }可以在每个测试方法前初始化,这样很麻烦
JUnit提供了@BeforeEach @AfterEach,会在每个@Test前后运行,适用于大多数情况
public class CalculatorTest { Calculator calculator; @BeforeEach public void setUp() { this.calculator = new Calculator(); } @AfterEach public void tearDown() { this.calculator = null; } @Test void testAdd() { assertEquals(100, this.calculator.add(100)); assertEquals(150, this.calculator.add(50)); assertEquals(130, this.calculator.add(-20)); } @Test void testSub() { assertEquals(-100, this.calculator.sub(100)); assertEquals(-150, this.calculator.sub(50)); assertEquals(-130, this.calculator.sub(-20)); } }@BeforAll @AfterAll在所有@Test方法前后仅运行一次,只能标注在静态方法上。只有初始化资源特别耗时时才使用
5.异常测试
当我们想判断抛出异常的情况,JUnit提供了 assertThrows方法
public class Factorial { public static long fact(long n) { if (n < 0) { throw new IllegalArgumentException(); } long r = 1; for (long i = 1; i <= n; i++) { r = r * i; } return r; } }@Test void testNegative() { assertThrows(IllegalArgumentException.class, new Executable() { @Override public void execute() throws Throwable { Factorial.fact(-1); } }); }第二个参数是一个函数式接口,可以使用 () ->{} 替代
@Test void testNegative() { assertThrows(IllegalArgumentException.class, () -> { Factorial.fact(-1); }); }6.条件测试
跳过某些测试方法,会在结果显示跳过
@Disabled @Test void testBug101() { // 这个测试不会运行 }判断当前运行的操作系统
public class Config { public String getConfigFile(String filename) { String os = System.getProperty("os.name").toLowerCase();//WINDOWS windows if (os.contains("win")) { return "C:\\" + filename; } if (os.contains("mac") || os.contains("linux") || os.contains("unix")) { return "/usr/local/" + filename; } throw new UnsupportedOperationException() ; } }@Test void testWindows() { assertEquals("C:\\test.ini", config.getConfigFile("test.ini")); } @Test void testLinuxAndMac() { assertEquals("/usr/local/test.cfg", config.getConfigFile("test.cfg")); }一个只能在windows上跑,领域给只能在Linux上跑,加上@EnableOnOs(OS.XXX)注解
@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")); }只能在Java 9或更高版本执行的测试,可以加上
@DisabledOnJre(JRE.JAVA_8):@Test @DisabledOnJre(JRE.JAVA_8) void testOnJava9OrAbove() { // TODO: this test is disabled on java 8 }7.参数化测试
如果想同时测一组数据,这时就要考虑给测试方法传入参数了
@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)); }如果返回值比较复杂,例如把一个字符串首字母大写,其余字母小写
public class StringUtils { public static String capitalize(String s) { if (s.length() == 0) { return s; } return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase(); } }@ParameterizedTest void testCapitalize(String input, String result) { assertEquals(result, StringUtils.capitalize(input)); }测试要给出输入输出
参入如何传入呢?有三种方式
方式一: 使用@MethodSource 注解,可以编写要给同名静态方法
@ParameterizedTest @MethodSource void testCapitalize(String input, String result) { assertEquals(result, StringUtils.capitalize(input)); } static List<Arguments> testCapitalize() { List<Arguments> list = new ArrayList<>(); list.add( Arguments.arguments("abc","Abc")); list.add(Arguments.arguments("APP","App")); return list; }方式二:使用@CsvSource
@ParameterizedTest @CsvSource({ "abc, Abc", "APPLE, Apple", "gooD, Good" }) void testCapitalize(String input, String result) { assertEquals(result, StringUtils.capitalize(input)); }方式三:数据量如果比较大,使用外部文件@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目录下pple, Apple HELLO, Hello JUnit, Junit reSource, Resource规范
单元测试的代码本身要非常简单,明了
每个单元测试应当相互独立,不依赖运行的顺序
测试时不仅要覆盖常用的测试用例,还要特别注意测试的边界条件,例如 0,null,“”等情况