听说优秀的程序员20%的时间都在写UT?

2024-05-24 14:32

本文主要是介绍听说优秀的程序员20%的时间都在写UT?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

导读

在今天的文章中打算和大家聊一聊关于测试的话题,也许有朋友会问,作为一名码农为什么要关注测试的问题?我们把代码开发完基本自测没问题了,扔给测试不就行了?有问题再改呗!也许有很多人都会这么想,的确,目前国内很多程序员并不太关注Unit Test,很多互联网公司也并没有强制要求开发人员必须编写Unit Test Case。究其原因,可能是国内公司都比较有钱,测试团队动辄几十人,甚至上百人的公司大有人在。所以,从很多程序员的心态上看,测试这么多,直接扔给他们测试就好了!而另外一个被提及的原因,则是国内互联网公司产品迭代速度太快,需求太多做不过来,那里有时间写Unit Test呢?

也许原因是多样的,但抛开各种各样的因素,今天我们只从程序员成长的角度来聊一聊该不该写Unit Test?最近这段时间和海外的程序员朋友合作开发项目比较多,给我的感受是他们特别强调Unit Test,用他们的话来说比较在意程序的品质。而反观国内很多公司这一点做的就并不是那么好了!之前也和他们聊过其中的原因,他们认为是国内最近这些年的发展太快了,以至于有些过程被跳过了。

我们知道开发一个软件或者平日里在现有的项目中开发某个需求时,严格来说一般会经历这么一个流程,如下图所示:

从图上可以看到,在这个流程中软件被交付集成测试之前,一定要先跑过Unit Test,而现在很多国内公司的测试流程都绕过Unit Test直接过度到集成测试和QA测试,而从客观的情况看,其实往往开发对逻辑是最了解的,如果开发可以通过覆盖相对完整的Unit Test的话,实际上后续测试流程就会顺利的多,而且写Unit Test还有一个好处,就是能够促使开发人员不断优化代码的设计逻辑,因为一旦你发现代码无法被Unit Test的时候,就说明你的代码不够组件化而需要被重构了!作为一名程序员,如果你能够在这种过程中不断地审视自己写过的代码,相信你的代码编写水平一定会得到不断地提高!

而从软件可维护性的角度看,Unit Test覆盖全面的项目往往都会比较好维护,因为完整的Unit Test实际上已经固化了软件当前的逻辑,一旦有人在后续的开发中破坏了这个逻辑,就会导致Unit Test无法通过,此时如果要求无法被Unit Test跑过的代码不能被编译成功或者提交的话,那么就会强迫修改者去完善Unit Test。这样也从侧面提高了程序员的测试意识,减少了发生重大Bug的几率!

从以上两个角度看,Unit Test一方面可以提高程序员的编码水平,另外一方面也能尽量保证软件的质量,所以Unit Test是一件非常有价值的事情,难怪他们说优秀的程序员20%的时间都在写Unit Test!

Unit Test该怎么写

在前面的内容中,我们讲到Unit Test是一件非常有价值的事情,那么在实际的项目中Unit Test到底该怎么写呢?以使用Spring Boot框架并基于Spring MVC开发的Web服务为例,大部分情况下的代码结构如图所示:


在这个软件结构中一般面向外部调用的是Controller层的服务接口定义,这一层由Spring MVC框架提供支持;而Controller层在接收到请求后需要将参数传递给Service层的业务方法进行处理,而Service层的业务方法逻辑就会比较多样,例如可能需要操作数据库就通过Dao层提供的组件去实现,也可能需要访问个中间件组件之类,如缓存服务Redis、消息服务RocketMQ之类。除此之外,Service层逻辑可能还会涉及到其他第三方服务的调用,例如支付业务还需要调用支付宝之类的接口等等!

所以一般来说Unit Test的重点就是Service层的业务逻辑方法,如果Controller层也涉及到一些流程逻辑之类,也需要被Unit Test覆盖一下!而具体的Unit Test用例编写,遵循Maven工程规约即可。

不过说到这里大家可能会有很大的疑问,那就是我们在进行Unit Test时,正如上图所示Service层本身依赖了很多其他组件,有些需要调用数据库、有些需要访问Redis、有些还需要调用第三方接口,在这种情况下好像很难让Unit Test跑下去,因为不可能每次运行Unit Test的时候这些环境都是在线的,怎么办呢?所以在早期写Unit Test,如果有第三方依赖无法被测试的情况下是需要我们手动编写Mock测试代码的,举个例子假设我们有个业务层的类class A{...}需要被Unit Test,但是A中依赖于第三方组件代码B,由于B需要连接外部网络,所以我们在测试A的时候没有办法直接依赖B的实例,所以我们一般来说需要单独定义个class MockB extend B{@Override ...},这个类继承B并以Mock的方式重写其方法,从而来为A类的Unit Test提供Mock Bean!而这种由于组件依赖复杂的情况,也在某种程度上限制来大家写Unit Test的热情,不过下面要介绍的这个神器会让这件事变得非常容易!

Unit Test神器之Mockito


在上面我们谈到了在编写业务层Unit Test时候会发现复杂的组件依赖需要我们编写很多额外的Mock类,增加来我们编写Unit Test的难度,而Mockito这个测试框架的出现则让Mock这件事变得非常容易了!Mockito是一个模拟测试框架,可以让我们以注解(@MockBean)的方式优雅地进行依赖组件的Mock并对执行逻辑进行验证。使用Mockito的一般步骤如下:

1、模拟任何外部第三方组件依赖,并将这些模拟对象插入测试代码;

2、执行测试中的代码;

3、验证代码是否按照预期执行;

如果我们在Spring Boot的工程中引入了测试依赖Jar,实际上就已经引入了Junit及Mockito这两组测试框架的依赖。如下:

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

下面我们以一个实际的案例来演示下如何编写一个针对Service层代码Unit Test,Service业务逻辑代码如下:

@Service
public class UserAccountTradeServiceImpl implements UserAccountTradeService {

    @Autowired
    WalletOrderDao walletOrderDao;

    @Autowired
    PaymentClient paymentClient;

    @Override
    public AccountChargeTradeResVo accountChargeTrade(AccountChargeTradeReqVo accountChargeTradeReqVo)
            throws Exception {
        //充值交易防重
        WalletOrder walletOrder = walletOrderDao.selectOrderById(accountChargeTradeReqVo.getOrderId());
        if (walletOrder != null) {
            throw new Exception("充值订单重复");
        }
        //构建充值订单
        walletOrder = WalletOrder.builder().orderId(accountChargeTradeReqVo.getOrderId())
                .userId(String.valueOf(accountChargeTradeReqVo.getUserId()))
                .amount(accountChargeTradeReqVo.getAmount())
                .busiType("0").tradeType("charge").currency(accountChargeTradeReqVo.getCurrency()).status("1")
                .isRenew(accountChargeTradeReqVo.getReNew()).tradeTime(new Timestamp(new Date().getTime()))
                .updateTime(new Timestamp(new Date().getTime()))
                .build();
        walletOrderDao.insertOrder(walletOrder);
        //调用支付接口
        paymentClient.consumeAccount(1, "1", "CNY");
        //构建返回参数
        AccountChargeTradeResVo accountChargeTradeResVo = AccountChargeTradeResVo.builder()
                .userId(Long.valueOf(walletOrder.getUserId())).currency(walletOrder.getCurrency())
                .orderId(walletOrder.getOrderId()).businessType(walletOrder.getBusiType()).build();
        return accountChargeTradeResVo;
    }
}

以上业务代码实际上是演示了一个用户钱包充值的大致逻辑的业务层方法,而该方法中有两个依赖组件需要被Mock一个是表示操作数据库的walletOrderDao,另外一个则是表示需要调用支付系统的客户端依赖paymentClient。那么使用Mockito该如何在Unit Test中进行Mock呢?

我们在工程对应的test目录的包结构中,建立一个与业务层逻辑包结构一样的测试代码结构,如下图所示:


一般来说Unit Test类的代码接口与实际源码结构一致就行,以被测试类+Test后缀命名即可。接下来我们编写该测试代码:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {UserAccountTradeServiceImpl.class})
//@ActiveProfiles({"test"})
public class UserAccountTradeServiceImplTest {

    @MockBean
    private WalletOrderDao walletOrderDao;

    @MockBean
    private PaymentClient paymentClient;

    @Autowired
    private UserAccountTradeServiceImpl userAccountTradeServiceImpl;

    @Test
    public void accountChargeTradeTest() throws Exception {
        AccountChargeTradeReqVo accountChargeTradeReqVo = AccountChargeTradeReqVo.builder().orderId("12345")
                .userId(1001).amount(1000).currency("CNY").tradeTime("2019070412102023").reNew("1").build();
        AccountChargeTradeResVo accountChargeTradeResVo = userAccountTradeServiceImpl
                .accountChargeTrade(accountChargeTradeReqVo);
        assertNotNull(accountChargeTradeResVo);
        assertEquals(accountChargeTradeResVo.getOrderId(), accountChargeTradeReqVo.getOrderId());
        given(paymentClient.consumeAccount(any(Long.class), any(String.class), any(String.class))).willReturn(null);
        verify(paymentClient).consumeAccount(any(Long.class), any(String.class), any(String.class));
    }
}

在以上测试代码中我们通过@MockBean这个注解就很容易的Mock了该业务层代码的依赖组件,这样测试代码在执行依赖组件的逻辑时就会被Mock而不会真正调用这个方法。而一般情况下我们也可以验证下Mock对象的方法是否有被调用,但是只是验证下调用本身是否触发而并不是真的调用,可以使用given/verify这两个Mocktio提供的方法来实现。

对于大部分情况采用这样的模式进行Unit Test就差不多了,更多其他细节的用法大家可以在好好研究下Mocktio提供的功能!在这里示例中还有个一个小的技巧,就是我们在使用@SpringBootTest的时候如:

@SpringBootTest(classes = {UserAccountTradeServiceImpl.class})

可以直接指定要测试的Service类,这样Spring Boot就不会加载其他乱七八糟的依赖了,这样会节约Unit Test运行的时间。

这篇关于听说优秀的程序员20%的时间都在写UT?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/998678

相关文章

服务器集群同步时间手记

1.时间服务器配置(必须root用户) (1)检查ntp是否安装 [root@node1 桌面]# rpm -qa|grep ntpntp-4.2.6p5-10.el6.centos.x86_64fontpackages-filesystem-1.41-1.1.el6.noarchntpdate-4.2.6p5-10.el6.centos.x86_64 (2)修改ntp配置文件 [r

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

批处理以当前时间为文件名创建文件

批处理以当前时间为文件名创建文件 批处理创建空文件 有时候,需要创建以当前时间命名的文件,手动输入当然可以,但是有更省心的方法吗? 假设我是 windows 操作系统,打开命令行。 输入以下命令试试: echo %date:~0,4%_%date:~5,2%_%date:~8,2%_%time:~0,2%_%time:~3,2%_%time:~6,2% 输出类似: 2019_06

【MRI基础】TR 和 TE 时间概念

重复时间 (TR) 磁共振成像 (MRI) 中的 TR(重复时间,repetition time)是施加于同一切片的连续脉冲序列之间的时间间隔。具体而言,TR 是施加一个 RF(射频)脉冲与施加下一个 RF 脉冲之间的持续时间。TR 以毫秒 (ms) 为单位,主要控制后续脉冲之前的纵向弛豫程度(T1 弛豫),使其成为显著影响 MRI 中的图像对比度和信号特性的重要参数。 回声时间 (TE)

LeetCode:64. 最大正方形 动态规划 时间复杂度O(nm)

64. 最大正方形 题目链接 题目描述 给定一个由 0 和 1 组成的二维矩阵,找出只包含 1 的最大正方形,并返回其面积。 示例1: 输入: 1 0 1 0 01 0 1 1 11 1 1 1 11 0 0 1 0输出: 4 示例2: 输入: 0 1 1 0 01 1 1 1 11 1 1 1 11 1 1 1 1输出: 9 解题思路 这道题的思路是使用动态规划

O(n)时间内对[0..n^-1]之间的n个数排序

题目 如何在O(n)时间内,对0到n^2-1之间的n个整数进行排序 思路 把整数转换为n进制再排序,每个数有两位,每位的取值范围是[0..n-1],再进行基数排序 代码 #include <iostream>#include <cmath>using namespace std;int n, radix, length_A, digit = 2;void Print(int *A,

LeetCode:3177. 求出最长好子序列 II 哈希表+动态规划实现n*k时间复杂度

3177. 求出最长好子序列 II 题目链接 题目描述 给你一个整数数组 nums 和一个非负整数k 。如果一个整数序列 seq 满足在下标范围 [0, seq.length - 2] 中 最多只有 k 个下标i满足 seq[i] != seq[i + 1] ,那么我们称这个整数序列为好序列。请你返回 nums中好子序列的最长长度。 实例1: 输入:nums = [1,2,1,1,3],

未雨绸缪:环保专包二级资质续期工程师招聘时间策略

对于环保企业而言,在二级资质续期前启动工程师招聘的时间规划至关重要。考虑到招聘流程的复杂性、企业内部需求的变化以及政策标准的更新,建议环保企业在二级资质续期前至少提前6至12个月启动工程师招聘工作。这个时间规划可以细化为以下几个阶段: 一、前期准备阶段(提前6-12个月) 政策与标准研究: 深入研究国家和地方关于环保二级资质续期的最新政策、法规和标准,了解对工程师的具体要求。评估政策变化可

用Python实现时间序列模型实战——Day 14: 向量自回归模型 (VAR) 与向量误差修正模型 (VECM)

一、学习内容 1. 向量自回归模型 (VAR) 的基本概念与应用 向量自回归模型 (VAR) 是多元时间序列分析中的一种模型,用于捕捉多个变量之间的相互依赖关系。与单变量自回归模型不同,VAR 模型将多个时间序列作为向量输入,同时对这些变量进行回归分析。 VAR 模型的一般形式为: 其中: ​ 是时间  的变量向量。 是常数向量。​ 是每个时间滞后的回归系数矩阵。​ 是误差项向量,假

linux下修改系统日期与时间

cp /usr/share/zoneinfo/Asia/Shanghai  /etc/localtime