龙空技术网

大厂技术专家手把手教你如何写好单元测试

编程技术分享 236

前言:

目前姐妹们对“单元测试断言怎么写”大约比较关注,同学们都需要了解一些“单元测试断言怎么写”的相关资讯。那么小编在网摘上搜集了一些对于“单元测试断言怎么写””的相关知识,希望同学们能喜欢,你们一起来学习一下吧!

前言

说起单元测试,很多小公司不注重单元测试,从而导致了上线后出现各类奇怪问题,很大一部分开发人员都没有编写单元测试的习惯,但去了大厂之后才知道差距在哪里!单测是系统稳定的保障,不容不引起重视,写好单测是优秀的程序员的必备技能。本期给大家带来以下几个部分:

单元测试基本概念讲解常用单元测试框架讲解单元测试规范及需要注意的内容实战代码讲解各类情况单元测试使用 Mockito 进行复杂业务单测大厂单元测试实战经验总结分享

注:本期的实战演练以 Spring Boot 为基础,版本为 2.5.1(官网最新稳定版本)。

单元测试的基本概念

我们先看一下百度百科是如何对单元测试进行概括的:

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如 C 语言中单元指一个函数,Java 里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。—— 百度百科

简单来说,单元测试就是对一个最小的功能单元进行测试,单元测试和程序是相互隔离的。那么在 Java 当中,最小的功能单元无非就是方法(函数)了。一个类中有很多的方法,而一个类往往对应一个单元测试类,类中每个方法,都在测试类中需要对应一个单元测试。也就是一个标注了 @Test 注解的方法。

在单元测试中我们可以设定程序预期执行返回的结果,如果一些正常,则单元测试会表现为 pass,而如果单元测试的执行结果和我们预期的结果不一致,则会表现为 fail。提示我们,代码逻辑存在问题,需要进行改正。

下面说一下单元测试的文件结构,现在 Java 项目几乎都是使用 Maven 来进行构建,其实我们参照 Maven 的文件标准就可以了。下面是 Maven 的单元测试结构:

源代码一般存放在 src/main/java 目录下,单元测试一般存放在 src/test/java 目录下。

注意: 单元测试类的包层级和被测类的包层级是一样的,且一般以被测类名后加 Test 或 Tests。

单元测试初体验

1. 先创建一个 Spring Boot 项目,版本为官网最新稳定版 2.5.1,如上图所示,然后加入以下依赖(一般使用 Spring Boot 初始化向导是默认自带的,没有的可以添加下面的依赖)。

        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>            <scope>test</scope>        </dependency>

参考官方说明:

我们再加入测试框架 Mockito、PowerMockito 的相关依赖:

    <dependency>            <groupId>org.mockito</groupId>            <artifactId>mockito-core</artifactId>        </dependency>        <dependency>            <groupId>org.powermock</groupId>            <artifactId>powermock-module-junit4</artifactId>            <version>RELEASE</version>            <scope>test</scope>        </dependency>

2. 创建以下文件,如图所示:

3. 在 JunitService.java 中写入以下代码:

/** * Junit Service */@Servicepublic class JunitService {    /**     * 返回用户传入的内容     *     * @param content 内容     * @return 用户传入的内容     */    public static String retString(String content) {        return content;    }}

4. 在 JunitServiceTest.java 中写入一下代码,并添加 @Test 注解。

public class JunitServiceTest {    @Test    public void testRetString() {        String content = "content";        String res = JunitService.toResString(content);        // 使用断言结果        Assert.assertEquals(content,res);    }}

5. 右键执行单元测试,我们可以看到控制台打印如下:显示 Tests passed。

6. 至此我们已经完整的体验了一个单元测试的编写过程。

常用单元测试框架

我们在 Spring Boot 官方文档里,单元测试章节看到以下内容:

大部分企业中,JUnit 一般是基础框架(必须),然后搭配使用 Mockito 和 PowerMockito。我们本节重点说一说 Mockito。

常用测试框架详解什么是 Mockito?

说起 Mockito,大多数人下意识觉得很熟悉,因为这是一款酒的名字,中文名叫莫吉托。Mockito 是一个非常好用的 Mock 框架,它让可以让你使用干净简洁的 api 写出优雅的单元测试。Mockito 不会让你感到宿醉,因为单元测试的可读性很强,而且如果测试结果和预期结果不匹配,它们会产生简洁的验证错误。

官网地址:

GitHub 地址:

Mockito 快速精通引入 Maven 依赖

参考上一节的依赖。

验证交互行为

 // 使用 mock 创建 List 对象 List mockedList = mock(List.class); // 使用 mock 对象操作方法 mockedList.add("one"); mockedList.clear(); // 验证操作 verify(mockedList).add("one"); verify(mockedList).clear();
假设返回结果

这是我认为 Mockito 最厉害的功能了!我们看下面一段代码:

 // mock 对象, 可以是类或者接口 LinkedList mockedList = mock(LinkedList.class); // 当 list 执行 get(0)操作时, 直接模拟它返回 first when(mockedList.get(0)).thenReturn("first"); // 当 list 执行 get(1)操作时, 直接模拟它抛出 RuntimeException 异常 when(mockedList.get(1)).thenThrow(new RuntimeException()); //控制台打印结果 "first" System.out.println(mockedList.get(0)); //控制台打印结果抛出 runtime exception System.out.println(mockedList.get(1)); //控制台打印 “null”,因为我们没有假设 mockedList.get(999) 的返回结果 System.out.println(mockedList.get(999)); // 验证方法 verify(mockedList).get(0);

这里需要特殊说明一下,默认情况下,对于有返回值的所有方法,模拟将会返回 null,基本数据类型或者基本数据包装类型或者空集合会视情况而定。例如返回值类型为 int 的,则默认返回 0。

如果我们模拟方法执行返回结果,其原理就是使用我们模拟的结果,将原有的结果进行覆盖。

参数匹配

//mockedList.get()方法,我们不传入实际的参数,采用 anyInt()。表示传入// 任意一个整形都返回 element when(mockedList.get(anyInt())).thenReturn("element");

如果你想使用参数类型匹配,那么所有的传入的参数都必须匹配。

verify(mock).someMethod(anyInt(), anyString(), eq("3 argument"));// 验证 someMethod 方法, 参数:int,String ,和指定的字符 "third argument"verify(mock).someMethod(anyInt(), anyString(), "3 argument");// 如果我们不使用 eq 方法指定传入的字符,而是直接传入实参,则会抛出异常
验证确切的调用次数,至少 x 次 / 一次都没有
 //使用 mock 创建的 mockedList, 添加一些执行次数 mockedList.add("once"); mockedList.add("twice"); mockedList.add("twice"); mockedList.add("three times"); mockedList.add("three times"); mockedList.add("three times"); //验证方法,以及方法执行次数 - times(1) 表示默认执行一次 verify(mockedList).add("once"); verify(mockedList, times(1)).add("once"); //验证指定调用次数 verify(mockedList, times(2)).add("twice"); verify(mockedList, times(3)).add("three times"); //使用 never()方法验证. never()方法等同于 times(0) verify(mockedList, never()).add("never happened"); //使用 atLeast():至少 多少次 /atMost() 最多 多少次 verify(mockedList, atMostOnce()).add("once"); verify(mockedList, atLeastOnce()).add("three times"); verify(mockedList, atLeast(2)).add("three times"); verify(mockedList, atMost(5)).add("three times");
模拟方法抛出异常

日常开发过程中,我们经常需要对一些异常情况进行从处理,使用模拟抛出异常让测试变得更加轻松。

   // 模拟:当 mockedList 调用 clear()方法时,抛出 RuntimeException 异常    doThrow(new RuntimeException()).when(mockedList).clear();   //执行方法, 抛出 RuntimeException:   mockedList.clear();
验证顺序

注:必须以特定顺序调用其方法的单个模拟。

 // mock 对象 List singleMock = mock(List.class); //使用 mock 对象 singleMock.add("was added first"); singleMock.add("was added second"); //创建一个 inOrder 用于验证 singleMock InOrder inOrder = inOrder(singleMock); // 按方法执行的顺序一个一个验证, 如果未按照顺序则会抛出异常 inOrder.verify(singleMock).add("was added first"); inOrder.verify(singleMock).add("was added second");

这个场景也是比较好用的,例如我们的业务有强制的先后执行顺序,使用这个就比较好验证。

验证从未发生交互

 //mock 一个 mockOne, 执行 add("one")操作 mockOne.add("one"); //验证 mockOne 从未执行过 add("two")操作 verify(mockOne, never()).add("two");
标准 Mock 创建对象

注:使用 @Mock 注入相关依赖,使用 @InjectMock 注入被测试类。顺序时 @InectMock 在上,@Mock 在下。还必须在单元测试类上标注 @RunWith(MockitoJUnitRunner.class) 注解启动,JUint4 和 JUnit5 有些差别,具体可查阅官方文档。

// 使用 @Mock 注解注入对象@Mock private ArticleCalculator calculator;@Mock private ArticleDatabase database;@Mock private UserProvider userProvider;
doReturn()、doThrow()、doAnswer()、doNothing()、doCallRealMethod() 方法族doReturn():模拟结果返回doThrow():模拟抛出异常doAnswer():模拟执行 void 方法时,返回结果doNothing():模拟什么都不做,一般用的很少doCallRealMethod():模拟调用真实的方法监控真实对象

你可以创建一个真实对象,然后使用 spy() 方法时,将调用真正的方法。除非我们模拟了结果返回。

   List list = new LinkedList();   List spy = spy(list);   //模拟返回结果   when(spy.size()).thenReturn(100);   //调用真实方法   spy.add("one");   spy.add("two");   //第一个属性打印 "one"   System.out.println(spy.get(0));   //打印 size() ,结果 “100”   System.out.println(spy.size());   //也可以进行验证   verify(spy).add("one");   verify(spy).add("two");
验证超时
// 执行超过 100ms 超时  verify(mock, timeout(100)).someMethod();// 执行一次超过 100ms 超时verify(mock, timeout(100).times(1)).someMethod();
单元测试规范及注意事项

俗话说的好,没有规矩,不成方圆。一个程序员专不专业,一看编码风格,代码规范就能看出来。单元测试一样如此,那么我们就来说一说单元测试中的规范和注意事项。

一一匹配

这里说的是有实际意义的一个类,必须有一个对应的单元测试类。大家可以简单理解为男女搭配,干活不累。

包层级匹配,即类路径下的包层级是一样的。例如:业务类是 com.jason.service.MyService.java 那么测试类就是 com.jason.service.MyServiceTest.java,这样做的好处是快速能定位测试类的位置,省去查找的时间。类名匹配:例如:业务类类名 MyService,对应的测试类类名 MyServiceTest,即测试类后加 Test,也是方便查找和定位那些类需要单测

是不是所有的类都需要单测呢?其实不是的,当然,绝大部分都是需要的,只有一些少部分无实际测试意义的类,就不需要单测。除了以下这些,其他的都需要单测,即使是 model、dto 等对象模型也需要。这样我们即使改动了某些字段,单测也能帮我们检测出来,提醒我们其他地方有使用到该模型,需要进行覆盖。

说一个常见的场景: 小明因业务需求,给 dto 增加了一个字段,没有写单测。在很多地方都用到了 dto,但是有一个方法小明忘记对 dto 增加的字段进行 set 操作,导致某些场景下,该字段的值为 null,从而导致了 bug 的产生。如果我们有单测,且覆盖到,就可以完全杜绝这个问题。单测就是白盒测试。

那些类不需要单测:

接口不需要单测,接口的实现类需要单测抽象类不需要单测,抽象类的实现类需要单测常量不需要单测分场景覆盖

if(sex == 0) {    ...}else if(sex == 1) {    ...}else{    ...}

阅读以上代码,我们可以发现,正常一次方法调用只能走一个分支,但是我们的单测需要覆盖所有分支,这样才能确保每种情况下都能执行我们正确的预期。

// 按照不同分支进行测试@Testvoid testExcuteWhenSexIsZero() {    ...}@Testvoid testExcuteWhenSexIsOne() {    ...}@Testvoid testExcuteWhenSexIsOther() {    ...}
异常测试

开发过程中,是不是遇到异常就直接抛出去或者 catch 住?这种情况下,如果产生了异常,代码的执行顺序或许和实际的执行顺序不一样。所以对于方法抛出的异常,也是非常有必要进行覆盖的。

@Testvoid testThrowException() {    when(someMethod()).thenThrows(new NullPointException());}
运行单测

写完单测一定要记得执行,查看运行结果是不是 pass 通过的,如果不是则需要及时修改单测。很多时候写完忘记运行单测,导致可以预期的错误却没有发现,从而产生了线上问题。整体单测,可以使用 Maven 的 mvn test 组件完成。

必须要的断言

我们要求,每一个单测都要有断言,没有断言的单测将毫无意义。断言可以判断执行的结果是不是我们预期的结果,可以保证程序执行的正确性,保障程序的健壮性。

@Testvoid testGetUserInfo() {    Student student = mock(Student.class);    student.setName("zs");    // 断言:student 不为 null    Assert.assertNotNull(student);    // 断言:student 的 name 为 zs    Assert.assertEquals("zs", student.getName());}
单测名称需见名知意

单测的名称极为重要,很多时候你看到一个单测名称都不知道作者想要测得是什么内容,如果加上没有写注释,那更加是云里雾里。有意义的单测名,可以极大地提升代码的可读性,提升效率。

// 错误示范:测试支付订单@Testvoid testPayOrder() {}// 正确示范:具体化// 测试:支付订单, 当用户使用支付宝支付时@Testvoid testPayOrderWhenUseAlipay() {}
实战代码讲解各类情况单元测试

本节讲解采用代码 + 注释的方式进行讲解,请大家阅读源码的同时结合注释进行理解。

测试普通静态方法

@Servicepublic class UserServiceImpl implements UserService {    public static boolean checkStringIsNotNull(String str) {        return str == null;    }}
public class UserServiceImplTest {    /*     * 测试: 检查字符不等于 null     */    @Test    void testCheckStringIsNotNull() {        String str = null;        // 断言:判断成功        Assert.assertTrue(UserServiceImpl.checkStringIsNotNull(str));    }}
测试对象方法
@Servicepublic class UserServiceImpl implements UserService {    public boolean checkStringIsNotNull(String str) {        return str == null;    }}
// 想要使用注解注入对象, 必须要使用@RunWith 启动@RunWith(MockitoJUnitRunner.StrictStubs.class)public class UserServiceImplTest {    // @Mock 注入对象, 注意:@Mock 时根据 id 注入的    @Mock    UserServiceImpl userServiceImpl;    // 执行测试之前执行    @Before    void setup() {        // 初始化 mock 注入环境        MockitoAnnotations.initMocks(this);    }    /*     * 测试: 检查字符不等于 null     */    @Test    void testCheckStringIsNotNull() {        String str = null;        // 断言:userServiceImpl 对象不为 null        Assert.assertNotNull(userServiceImpl);        // 断言:判断成功        Assert.assertTrue(userServiceImpl.checkStringIsNotNull(str));    }}
测试方法中有其他依赖
@Servicepublic class UserServiceImpl implements UserService {    @Autowired    private UserDao userDao;    public User getUserDetail(Long id) {        return userDao.selectUserById(id);    }}

此时 userService 对象依赖了 userDao 对象,我们需要测试的方法包含了其他依赖的情况。

@RunWith(MockitoJUnitRunner.StrictStubs.class)public class UserServiceImplTest {    // @InjectMock 注入被测对象, 一般写在最前面    @InjectMock    UserServiceImpl userServiceImpl;    // @Mock 注入需要的依赖对象    @Mock    UserDao userDao;    // 执行测试之前执行    @Before    void setup() {        // 初始化 mock 注入环境        MockitoAnnotations.initMocks(this);    }    /*     * 测试: 获取用户详情     */    @Test    void testGetUserDetail() {        // mock 一个 User 对象        User user = mock(User.class);        //断言        Assert.assertNotNull(user);        // 设置属性        user.setName("张三");        // 模拟 UserDao 查询返回结果,使用 anyLong()代表任意 Long 类型的参数. 模拟返回我们 mock 的 user 对象        when(userDao.selectUserById(anyLong())).thenReturn(user);        // 执行需要测试的方法        User userInfo = userServiceImpl.getUserDetail(anyLong());        // 断言:userInfo 不为 null        Assert.assertNotNull(userInfo);        // 断言:user.getName() 等于 userInfo.getName()        Assert.assertEquals(user.getName(), userInfo.getName())    }}
测试方法调用工具类

很多时候,我们都会用到静态工具类,这些是不经过对象调用的。

public final class StrUtil{    public static boolean isBlank(Long id) {        return id == null;    }}
@Servicepublic class UserServiceImpl implements UserService {    @Autowired    private UserDao userDao;    public User getUserDetail(Long id) {        if(StrUtil.isBlank(id)) {            return null;        }        return userDao.selectUserById(id);    }}
@RunWith(MockitoJUnitRunner.StrictStubs.class)@PrepareForTest({StrUtil.class})// 添加此注解, 将静态工具类加载public class UserServiceImplTest {    // @InjectMock 注入被测对象, 一般写在最前面    @InjectMock    UserServiceImpl userServiceImpl;    // @Mock 注入需要的依赖对象    @Mock    UserDao userDao;    // 执行测试之前执行    @Before    void setup() {        MockitoAnnotations.initMocks(this);        // mock 静态类        PowerMockito.mockStatic(StrUtil.class);    }    /*     * 测试: 获取用户详情     */    @Test    void testGetUserDetail() {        // mock 一个 User 对象        User user = mock(User.class);        //断言        Assert.assertNotNull(user);        // 设置属性        user.setName("张三");        // 模拟 UserDao 查询返回结果,使用 anyLong()代表任意 Long 类型的参数. 模拟返回我们 mock 的 user 对象        when(userDao.selectUserById(anyLong())).thenReturn(user);        // 模拟判断返回 false        when(StrUtil.isBlank(anyLong())).thenReturn(false);        // 执行需要测试的方法        User userInfo = userServiceImpl.getUserDetail(anyLong());        // 断言:userInfo 不为 null        Assert.assertNotNull(userInfo);        // 断言:user.getName() 等于 userInfo.getName()        Assert.assertEquals(user.getName(), userInfo.getName())    }}
测试抛出异常
@Servicepublic class UserServiceImpl implements UserService {    @Autowired    private UserDao userDao;    public User getUserDetail(Long id) {        if(StrUtil.isBlank(id)) {            throw new NullPointException();        }        return userDao.selectUserById(id);    }}
@RunWith(MockitoJUnitRunner.StrictStubs.class)@PrepareForTest({StrUtil.class})// 添加此注解, 将静态工具类加载public class UserServiceImplTest {    // @InjectMock 注入被测对象, 一般写在最前面    @InjectMock    UserServiceImpl userServiceImpl;    // @Mock 注入需要的依赖对象    @Mock    UserDao userDao;    // 执行测试之前执行    @Before    void setup() {        MockitoAnnotations.initMocks(this);        // mock 静态类        PowerMockito.mockStatic(StrUtil.class);    }    /*     * 测试: 获取用户详情     */    @Test    void testGetUserDetail() {        // mock 一个 User 对象        User user = mock(User.class);        //断言        Assert.assertNotNull(user);        // 设置属性        user.setName("张三");        // 模拟 UserDao 查询返回结果,使用 anyLong()代表任意 Long 类型的参数. 模拟返回我们 mock 的 user 对象        when(userDao.selectUserById(anyLong())).thenReturn(user);        // 模拟判断返回 false        when(StrUtil.isBlank(anyLong())).thenReturn(true);        // 模拟方法抛出异常        when(userServiceImpl.getUserDetail(anyLong())).thenThrows(new NullPointException());        User user = null;        try{            user = userServiceImpl.getUserDetail(anyLong());        }catch(Exception e) {            // 断言:抛出的异常类型是 NullPointException            Assert.assertTrue(e intanceof NullPointException)        }        Assert.assertNull(user);    }}
使用 Mockito 进行复杂业务单测

本节演示如何使用 Mockito 对一些复杂的业务进行单测。很多时候,我们的功能业务也许不是以 RESTful 为单位的,不能直接使用接口调用测试。实际测试中,有很多难以使用接口调用的方式测到,往往是这些难以测试的部分导致了 Bug 的产生。

我们模拟一个转账场景:

@Servicepublic class UserService {    @Autowired    UserHandler userHandler;    public User getUserByPayId(Long payId) {        return userHandler.getUserByPayId(payId);    }    public User getUserByPayeeId(Long payeeId) {        return userHandler.getUserByPayeeId(payeeId);    }}@Componentpublic class UserHandler {    @Autowired    UserDao userDao;    @Autowired    AccountDao accountDao    public User selectUserByPayId(Long payId) {        Account account = accountDao.selectAccountById(payId);        if(null == account) {            return new NullPointException();        }        return userDao.selectUserById(account.getUserId());    }    public User selectUserByPayeeId(Long payeeId) {        Account account = accountDao.selectAccountByPayeeId(payeeId);        if(null == account) {            return new NullPointException();        }        return userDao.selectUserById(account.getUserId());    }   // ...}@Servicepublic class PermissionService {    @Autowired    PermissionHandler permissionHandler;    public boolean hasPermission(User user) {        return permissionHandler.hasPermission(user);    }   // ...}@Servicepublic class AccountService {    @Autowired    AccountHandler accountHandler;    // 转账    public boolean transfer(Long payId,Long payeeId, String payAmount) {        // ...        return true;    }}
@Componentpublic class Transfer {    @Autowired    UserService userService;    @Autowired    AccountService accountService;    @Autowired    PermissionService permissionService;    public void transfer(Long payId,String payAmount, Long payeeId) throws Exception {        // 参数校验        if(null == payId||null == payeeId) {            throw new NullPointException();        }        if(StrUtil.isBlank(payAmount)) {            throw new TransferException("pay amount is null");        }        User payer = userService.getUserByPayId(payId);        User payee = userService.getUserByPayeeId(payId);        // 校验操作权限        if(!permissionService.hasPermission(payer) || !permissionService.hasPermission(payee)) {            throw new TransferException("Don't have operation permission");        }        // 核心转账        accountService.transfer(payId, payAmount, payeeId);    }}

现在它们的依赖关系大致是这个样子的:

我们开始写单测。

// 第一: 创建测试类// 第二: 使用@RunWith 启动@RunWith(MockitoJUnitRunner.StrictStubs.class)// 第三:首先加载静态工具类等@PrepareForTest({StrUtil.class})public class TransferTest {    // 第四:注入相应依赖, 依次注入    @InjectMock    Transfer transfer;    @Mock    UserService userService;    @Mock    AccountService accountService;    @Mock    PermissionService permissionService;    @Mock    UserHandler userHandler;    @Mock    AccountHandler accountHandler;    @Mock    PermissionHandler permissionHandler;    @Mock    UserDao userDao;    @Mock    AccountDao accountDao;    @Mock    PermissionDao permissionDao    // 第五:初始装载一些环境    @Before    void setup() {        // 初始化 mock        MockitoAnnotations.initMocks(this);        // mock 静态类        PowerMockito.mockStatic(StrUtil.class);    }    // 第六:创建测试方法, 添加测试注解    @Test    public void testTransferWhenSuccess() {        // 第七:模拟参数        Long payId = 1L;        String payAmount = 100;        Long payeeId = 1L;        // 第八:模拟静态 isBlank 返回结果        when(StrUtil.isBlank(anyString())).thenReturn(false);        // 第九:模拟查询结果        User payer = mock(User.class);        User payee = mock(User.class);           Assert.assertNotNull(payer);        Assert.assertNotNull(payee);        // 模拟 dao        when(userDao.selectUserById(anyLong())).thenReturn(payer);        when(userDao.selectUserById(anyLong())).thenReturn(payee);        // 模拟 handler        when(userHandler.selectUserByPayId(anyLong())).thenReturn(payer);        when(userHandler.selectUserByPayeeId(anyLong())).thenReturn(payee);        // 模拟 serivce        when(userService.getUserByPayId(anyLong())).thenReturn(payer);        when(userService.getUserByPayId(anyLong())).thenReturn(payee);        // 其他依赖一样如此模拟,重复代码就省略了...        // ....        // 第十:模拟权限校验通过        when(permissionService.hasPermission(payer))..thenReturn(true);        when(permissionService.hasPermission(payee))..thenReturn(true);        // 最后执行目标测试方法        accountService.transfer(payId, payAmount, payeeId);    }}

我们可以清楚的发现,我们想要一步一步执行下去,就必须符合前一步的条件。但是很多时候我们是没有连接数据库,没有生产环境数据的,此时模拟结果返回就很实用了,这样我们就能很轻松的验证我们的业务逻辑了。

大厂单元测试实战经验总结分享

覆盖率

BAT 等公司是非常重视单测的,一般不论是分支代码,还是增量代码最低要求都是要覆盖达到 80%以上。覆盖率可以大大地减少一些低级错误和缺陷。

单测维护

当逻辑代码发生了变更,相应的单测也应该进行修改,还有涉及到改功能的单测也需要进行维护。当然,一般大公司都有自己的检测工具,可以检测到所有单测是否通过。只要有一个单测不通过,就不能发版。

注重细节

我们一直认为功能才是最重要的,往往忽略了单测,当然单测也是非常花时间的,几乎和写功能花费一样的时间。这时候就要看取舍了,想要精益求精,那么单测绝对是让你代码质量更上一层楼的保证。

标签: #单元测试断言怎么写 #java如何编写单元测试类