单元测试的关键是,断言。
Assert.assertEquals等各种各样的断言语句。
没有断言的“单元测试”,可能都是“伪单元测试”。
不知道输入,不知道输出,也是写不好单元测试的。
尽管你自称已经了解了函数的作用。
好处
重复测试:前提是方法的输入参数和函数的作用没有发生大的变化。单元测试代码,写好一次就行了。
下次,函数内部逻辑进行了修改,只要不影响输出,或者说不影响以前的 输入-输出 集合,那么之前写的测试代码就可以服用。
多场景测试:通过构造不用的输入,得到不同的输出,再和预期值进行比较。测试很多种场景。
分工测试:一般来说是当事人自己写。但是“你写我测”这种方式也是可以的,尤其是针对工具库。输入定下来了,函数的作用根据方法名也知道了,输出也就可以明确了。
质量的保证:不写测试,根本不能保证质量。尤其是针对基础库,不可能随便写几个main函数,看看输出就给外界使用。
通过严格的单元测试,覆盖多种场景流程,简单点就是 代码分支覆盖率 要足够高。
坏处
写起来,看起来很累。
一些复杂的业务场景,输入条件难以构造,输出也不太好确定,不太方便测试。
什么场景适用
工具类:Apache Commons之类的工具库,大多是static方法,依赖少,很方便测试。
框架:MybatisPlus等开源框架,都自带基本的单元测试
不经常变的业务:业务代码,简单一点的,或者不经常变化的,可以写测试用例。
输入复杂,场景难以完整构造,就没必要了。
怎样写
构造数据、执行、断言、回滚
普通项目
public static String hideMobile(String mobile) {
if (StringUtils.isBlank(mobile) || mobile.length() != 11) {
return mobile;
}
String header = mobile.substring(0, 3);
String end = mobile.substring(mobile.length() - 4, mobile.length());
String hideMobile = header + "****" + end;
return hideMobile;
}
单元测试
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import com.common.kit.JtnKit;
public class JtnKitTest {
@Test
public void testHideMoble() {
String mobile = "15727305904";
String mobileAfterHide = JtnKit.hideMobile(mobile);
Assert.assertEquals("157****5904", mobileAfterHide);
String mobile2 = "15827105904";
String mobileAfterHide2 = JtnKit.hideMobile(mobile2);
Assert.assertEquals("158****5904", mobileAfterHide2);
String mobile3 = "1572730590";
String mobileAfterHide3 = JtnKit.hideMobile(mobile3);
Assert.assertEquals(mobile3, mobileAfterHide3);
}
@BeforeClass
public static void oneTimeSetUp() {
// one-time initialization code
System.out.println("@BeforeClass - oneTimeSetUp");
}
@AfterClass
public static void oneTimeTearDown() {
// one-time cleanup code
System.out.println("@AfterClass - oneTimeTearDown");
}
@Before
public void setUp() {
System.out.println("@Before - setUp");
}
@After
public void tearDown() {
System.out.println("@After - tearDown");
}
}
核心是@test标记的方法。
这个例子简单,没啥需要构造的输入输出数据,直接在 方法里 构造了。
setUp等方法都是可选的。
单元测试,核心是Assert断言,不需要用肉眼去看控制台输出。
而是看JUnit方法执行成功的个数。
Web项目
Dao和Service,SpringBoot,Spring项目。
这里的add方法,算作准确数据。(有争议的,add方法本身还没经过测试。最最严格的单元测试,我也不太知道怎么写)
@Rollback
@Transactional
2个注解,开启事务,最后回滚。不对数据库有影响。
注解放在类上,所有方法都自动回滚。
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import lombok.extern.slf4j.Slf4j;
// 连接,测试专用的数据库
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
@Rollback
@Transactional
public class JtnPostServiceTest {
@Autowired
private JtnPostService jtnPostService;
@Test
public void testJtnPostCrud() {
// 添加
JtnPostCreate jtnPostCreate = new JtnPostCreate();
String title = "title";
String content = "content";
jtnPostCreate.setTitle(title);
jtnPostCreate.setContent(content);
// 添加成功返回true,id应该大于0
RpcResult<Integer> addRpcResult = jtnPostService.add(jtnPostCreate);
Integer id = addRpcResult.getData();
Assert.assertTrue("id > 0", id > 0);
// 查询,title和content保持一致
RpcResult<JtnPostVo> detailRpcResult = jtnPostService.getDetailById(id);
JtnPostVo jtnPostVo = detailRpcResult.getData();
Assert.assertEquals(title, jtnPostVo.getTitle());
Assert.assertEquals(content, jtnPostVo.getContent());
// 更新
String titleUpdate = "titleUpdate";
String contentUpdate = "contentUpdate";
JtnPostUpdate jtnPostUpdate = new JtnPostUpdate();
jtnPostUpdate.setId(id);
jtnPostUpdate.setTitle(titleUpdate);
jtnPostUpdate.setContent(contentUpdate);
RpcResult<Boolean> updateRpcResult = jtnPostService.updateById(jtnPostUpdate);
boolean updateSucceed = updateRpcResult.getData();
Assert.assertTrue("updateSucceed shoud be true", updateSucceed);
RpcResult<JtnPostVo> detailRpcResultAfterUpdate = jtnPostService.getDetailById(id);
JtnPostVo jtnPostVoAfterUpdate=detailRpcResultAfterUpdate.getData();
Assert.assertEquals(titleUpdate, jtnPostVoAfterUpdate.getTitle());
Assert.assertEquals(contentUpdate, jtnPostVoAfterUpdate.getContent());
// 删除成功
RpcResult<Boolean> removeRpcResult = jtnPostService.removeById(id);
boolean removeSucceed = removeRpcResult.getData();
Assert.assertTrue("removeSucceed shoud be true", removeSucceed);
}
@Test
public void testJtnPostQuery() {
// 添加
Integer appId = 12;
JtnPostCreate jtnPostCreate = new JtnPostCreate();
jtnPostCreate.setAppId(appId);
String title = "title";
String content = "content";
jtnPostCreate.setTitle(title);
jtnPostCreate.setContent(content);
jtnPostService.add(jtnPostCreate);
// 列表
JtnPostQuery jtnPostQuery = new JtnPostQuery();
jtnPostQuery.setAppId(appId);
RpcResult<List<JtnPostVo>> listRpcResult = jtnPostService.list(jtnPostQuery);
List<JtnPostVo> jtnPostVo = listRpcResult.getData();
Assert.assertNotNull(jtnPostVo);
Assert.assertEquals(1, jtnPostVo.size());
// 分页
jtnPostQuery.setCurrent(1);
jtnPostQuery.setSize(10);
RpcResult<Page<JtnPostVo>> listPageRpcResult = jtnPostService.listPage(jtnPostQuery);
Page<JtnPostVo> page = listPageRpcResult.getData();
Assert.assertEquals(1, page.getTotal());
Assert.assertEquals(1, page.getRows().size());
}
}
Controller
上面的例子中,直接注入 Controller实例,应该也是可以的。(没有尝试)
以上是用了SpringBoot的Spring Web项目,如果没有用SpringBoot,也是有单独的扩展工具的。
具体忘了,嘿嘿。
JUnit5新语法
有兴趣的自行了解。
比如,支持参数化。
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
// TODO 升级Eclipse才能用 JUnit5
public class ValueSourcesExampleTest {
@Test
@ParameterizedTest
@ValueSource(ints = { 2, 4, 8 })
void testNumberShouldBeEven(int num) {
Assertions.assertEquals(0, num % 2);
}
@ParameterizedTest
@ValueSource(strings = { "Radar", "Rotor", "Tenet", "Madam", "Racecar" })
void testStringShouldBePalindrome(String word) {
Assertions.assertEquals(isPalindrome(word), true);
}
@ParameterizedTest
@ValueSource(doubles = { 2.D, 4.D, 8.D })
void testDoubleNumberBeEven(double num) {
Assertions.assertEquals(0, num % 2);
}
//回文数
boolean isPalindrome(String word) {
return word.toLowerCase().equals(new StringBuffer(word.toLowerCase()).reverse().toString());
}
}
Maven配置
<!-- junit4 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- junit5 -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>5.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.2.0</version>
<scope>test</scope>
</dependency>