Java单元测试
程序员文章站
2022-06-05 12:12:04
...
在阿里巴巴Java开发手册,对于单元测试有如下描述,我大致抽象了下:
错误意识
- 那是测试同学干的事情,单元测试代码是多余的!汽车的整体功能与各单元部件的测试正常与否是强相关?(认知需转变)
- 单元测试代码不需要维护!一年半载后,那么单元测试几乎处于废弃状态。(跟随业务维护)
- 单元测试与线上故障没有辩证关系!好的单元测试能够最大限度地规避线上故障(做比没做要好)
AIR 原则
单元测试在线上运行时候,感觉像空气(air)一样并不存在,但是在质量保证上却又十分关键。
- A:Automatic(测试框架通常是定期执行的,执行过程必须完全自动化才有意义)
- I:Independent(单元测试用例之间决不能互相调用,也不能依赖执行的先后次序)
- R:Repeatable(单元测试是可以重复执行的,不能受到外界环境的影响)
BCDE 原则
- B: Border,边界值测试,包括循环边界、特殊取值、特殊时间点,数据顺序。
- C: Correct,正确的输入,并得到预期的结果。
- D: Design,与设计文档相结合,来编写单元测试。
- E: Error,单元测试的目的是证明程序有错,而不是证明程序无错。为了发现代码中潜在的错误,我们需要在编写测试用例时有一些强制的错误输入(如非法数据、异常流程、非业务允许输入等)来得到预期的错误结果
规范
强制
- 粒度尽量小,最大至方法级别
- 核心业务、核心应用、核心模块确保有单元测试
- 代码必须写在如下工程目录:src/test/java
推荐
- DAO层,Manager层,可重用度高的Service,都应该进行单元测试
- 不手动操作数据库,请使用用程序准备符合业务的数据
- 数据库相关的单元测试,可以设置自动回滚机制,不造脏数据
- 不可测的代码需要做重构,不要为了要求测试而写不规范的测试代码
- 在设计评审阶段,开发人员需要和测试人员一起确定单元测试范围,单元测试最好覆盖所有测试用例(UC)
业务代码
为了更方便地进行单元测试,业务代码应避免以下情况
- 构造方法中做的事情过多
- 存在过多的全局变量和静态方法
- 存在过多的外部依赖
- 存在过多的条件语句
Junit+Mockito
基于springBoot 1.5.19
一般我们开发都是使用springBoot,引入了类似如下的依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
- JUnit —用于 Java 应用程序的单元测试的事实上的标准。(4.x版本)
- Spring Test 和 Spring Boot 测试—对 Spring Boot 应用程序的 Util 和集成测试支持。
- Hamcrest —匹配器对象库(也称为约束或谓词)。
- Mockito — Java 模拟框架。(1.x版本)
- AssertJ —流畅的 assert 库。
- JSONassert — JSON 的 assert 库。
- JsonPath — JSON 的 XPath。
Junit
参考:https://github.com/junit-team/junit4/wiki/Getting-started
JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展,是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
相关注解
注解 | 说明 |
---|---|
@Test(excepted==xx.class,timeout=毫秒数) | 修饰一个方法为测试方法,excepted参数可以忽略某些异常类 |
@Before | 在每一个测试方法被运行前执行一次 |
@BeforeClass | 在所有测试方法执行前执行 |
@After | 在每一个测试方法运行后执行一次 |
@AfterClass | 在所有测试方法执行后执行 |
@Ignore | 修饰的类或方法会被测试运行器忽略 |
@RunWith | 更改测试运行器 |
常规用法
套件
public class TaskOneTest {
@Test
public void test() {
System.out.println("Task one do.");
}
}
public class TaskTwoTest {
@Test
public void test() {
System.out.println("Task two do.");
}
}
public class TaskThreeTest {
@Test
public void test() {
System.out.println("Task Three.");
}
}
// 1. 更改测试运行方式为 Suite
@RunWith(Suite.class)
// 2. 将测试类传入进来
@Suite.SuiteClasses({TaskOneTest.class, TaskTwoTest.class, TaskThreeTest.class})
public class SuitTest {
/**
* 测试套件的入口类只是组织测试类一起进行测试,无任何测试方法,
*/
}
参数化测试
参数化测试允许开发人员使用不同的值反复运行同一个测试,遵循 5 个步骤来创建参数化测试。
- 用
@RunWith(Parameterized.class)
来注释 test 类 - 创建一个由
@Parameters
注释的公共的静态方法,始化所有需要测试的参数对 - 为测试类声明几个变量,分别用于存放期望值和测试所用数据
- 创建一个公共的构造函数,为上述声明的几个变量赋值
- 类有一个测试,它需要注解@Test到方法。
//1
@RunWith(Parameterized.class)
public class FibonacciTest {
//2
@Parameterized.Parameters
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{
{0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}
});
}
//3
private int fInput, fExpected;
//4
public FibonacciTest(int input, int expected) {
this.fInput = input;
this.fExpected = expected;
}
//5
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
// 被测试的类的方法
public class Fibonacci {
public static int compute(int n) {
int result = 0;
if (n <= 1) {
result = n;
} else {
result = compute(n - 1) + compute(n - 2);
}
return result;
}
}
若参数为单个的还可以这样做:
@Parameters
public static Iterable<? extends Object> data() {
return Arrays.asList("first test", "second test");
}
@Parameters
public static Object[] data() {
return new Object[] { "first test", "second test" };
}
断言
junit 断言,表格里面和assertThat。
断言 | 说明 |
---|---|
assertArrayEquals(expecteds, actuals) | 查看两个数组是否相等。 |
assertEquals(expected, actual) | 查看两个对象是否相等。类似于字符串比较使用的equals()方法。 |
assertNotEquals(first, second) | 查看两个对象是否不相等。 |
assertNull(object) | 查看对象是否为空。 |
assertNotNull(object) | 查看对象是否不为空。 |
assertSame(expected, actual) | 查看两个对象的引用是否相等。类似于使用“==”比较两个对象。 |
assertNotSame(unexpected, actual) | 查看两个对象的引用是否不相等。类似于使用“!=”比较两个对象。 |
assertTrue(condition) | 查看运行结果是否为true。 |
assertFalse(condition) | 查看运行结果是否为false。 |
assertThat与Matcher
包括两个包org.hamcrest.CoreMatchers和org.hamcrest.Matchers
- org.hamcrest.CoreMatchers
- assertThat( number, allOf( greaterThan(10), lessThan(15) ) ):相当于“与”(&&),即allOf中条件都必须成立
- assertThat( number, anyOf( greaterThan(30), lessThan(1) ) ):相当于“或”(||),即只要anyOf中有一个条件成立就可以了
- assertThat( number, anything() ):无论什么条件,永远为true
- assertThat( string, is( “engine” ) )和assertThat( string, equalTo( expectedValue ) ):两个作用是一样的,只是写法不同,都是Object的equals方法
- assertThat( string, not( “engine” ) ):not匹配符和is匹配符正好相反
- org.hamcrest.Matchers
(1)字符串相关匹配符
- assertThat( string, containsString( “engine” ) ):包含子字符串”engine”则测试通过
- assertThat( string, endsWith( “engine” ) ):以子字符串”engine”结尾则测试通过
- assertThat( string, startsWith( “engine” ) ):以子字符串”engine”开始则测试通过
- assertThat( testedString, equalToIgnoringCase( “engine” ) ); 忽略大小写的情况
- assertThat( testedString, equalToIgnoringWhiteSpace( “engine” ) ):忽略头尾的任意个空格
- assertThat( string, is( “engine” ) ):判断两个字符串相等
(2)数值相关匹配符
- assertThat( number, closeTo( 10.0, 0.1 ) ):在10.0±0.1范围之内则测试通过
- assertThat( number, greaterThan(11) ):大于11则测试通过
- assertThat( number, lessThan (11) ):小于11则测试通过
- assertThat( number, greaterThanOrEqualTo (11) ):大于等于11则测试通过
- assertThat( number, lessThanOrEqualTo (11) ):小于等于11则测试通过
(3)collection相关匹配符
- assertThat( map, hasEntry( “key”, “value” ) ):mapObject含有一个键值为”key”对应元素值为”value”测试通过
- assertThat( list, hasItem ( “element” ) ):iterableObject含有元素“element”项则测试通过
- assertThat( map, hasKey ( “key” ) ):mapObject含有键值“key”则测试通过
- assertThat( map, hasValue ( “key” ) ):mapObject含有元素值“value”则测试通过
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 testAssertEquals() {
org.junit.Assert.assertEquals("failure - strings are not equal", "text", "text");
}
@Test
public void testAssertFalse() {
org.junit.Assert.assertFalse("failure - should be false", false);
}
@Test
public void testAssertNotNull() {
org.junit.Assert.assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNotSame() {
org.junit.Assert.assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertNull() {
org.junit.Assert.assertNull("should be null", null);
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
org.junit.Assert.assertSame("should be same", aNumber, aNumber);
}
// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
org.junit.Assert.assertThat("albumen", both(containsString("a")).and(containsString("b")));
}
@Test
public void testAssertThathasItemsContainsString() {
org.junit.Assert.assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
}
@Test
public void testAssertThatEveryItemContainsString() {
org.junit.Assert.assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
}
// Core Hamcrest Matchers with assertThat
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}
@Test
public void testAssertTrue() {
org.junit.Assert.assertTrue("failure - should be true", true);
}
}
上一篇: .NET单元测试(三):隔离框架
下一篇: 如何进行单元测试(二)