前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单元测试:概念、作用与实践

单元测试:概念、作用与实践

作者头像
杜逸先
发布2023-04-13 16:26:24
9100
发布2023-04-13 16:26:24
举报

最近公司越来越多的项目开始推动单元测试,而我在公司里很早就在进行单元测试实践。就用这篇文章作为一次内部技术分享的主题,同时也代表我自己对单元测试的认识和实践。

单元测试的概念

单元测试是软件测试的一种类型,测试对象是最基础的代码单元(函数、类、模块),属于白盒测试。在经典的测试金字塔中,单元测试处于最底层。

testPyramid
testPyramid

最简单的单元测试:

basic.png
basic.png

单元测试的意义

确保代码实现符合预期

单元测试是唯一有可能触达所有代码流程分支的测试手段

提前发现错误,并以最小的成本修复

越早发现错误,修复时间越短。 单元测试的一次发现错误、修复、测试验收循环的周期为数分钟。 集成(验收)测试的循环周期为小时级。 线上错误的发现,排查问题,修复,测试环境验证到上线的周期一般半天起步。

测试代码即文档

测试代码本身可以诠释业务代码的意图

放心重构

单元测试是代码重构的前提

编写高质量的代码(可测试、无副作用)

单元测试引导开发人员编写更容易测试的代码。 更容易测试的代码往往意味着质量更高(SRP,无副作用,圈复杂度低)。

自动化执行

单元测试的高运行速度使之可以集成到自动化流水线中。

范例

下面的代码有一个不明显的逻辑错误。

example-code.png
example-code.png

我为这段代码编写了单元测试。

example-test.png
example-test.png

单元测试执行失败了,原因是/list接口调用find_by_page函数是传参顺序颠倒了。

test-result.png
test-result.png

这个问题在线上是不容易发现的,尤其是在分页是从 0 开始并且页面是自动加载下一页的情况。 此时实际调用的传参是find_by_page(page_no=30,page_size=0),数据库查询语句指定的是skip(0).limit(0)。前端会一次性加载出所有的数据出来,由于前端本身就是自动加载下一页,导致问题难以被发现。

我之前在线上就遇到过类似的问题,原始的错误是页面加载不出来(接口返回数据太大,超过了 grpc 默认的 message 大小)。

调整了grpc的设置后发现前端还是加载不出页面,这时才发现接口返回了三千多条数据,随后研究了数据库查询的逻辑才发现了问题。

如何进行单元测试

单元测试的基本流程

  • 准备测试数据和环境
  • 执行被测试代码单元
  • 检查代码单元行为是否符合预期
  • 清理环境

Given->When->Then

test-process.png
test-process.png

测试代码的行为

单元测试需要验证的是代码的行为符合预期。在简单的情况下,只需要检查函数的返回值是否符合预期。

test-action.png
test-action.png

分支与边界

处理分支和边界是代码逻辑的重要组成部分。 单元测试也需要照顾到这些边界情况,不能只测试主流程。

test-branches.png
test-branches.png
覆盖率

有时候很难直观的判断代码的所有分支都有被测试到 通过代码测试覆盖率报告可以快速找到没有被测试到代码分支与边界情况 覆盖率也分为不同的类型

  • 行覆盖率(coverage)
  • 分支覆盖率
  • 语句覆盖率

内部调用

大部分函数内部都会调用其他函数。

直接测试
inner-call-simple.png
inner-call-simple.png
单元测试的粒度

在上个例子中,我们直接测试了run_commands函数,过程中间接测试了run函数的行为,那么要不要单独为run函数编写单元测试呢? 我的建议是根据实际情况来决定。

  • 如果子函数只被父函数调用过,可以连同父函数一起进行测试。这种情况子函数往往是重构较为复杂的父函数时编写的。
  • 如果子函数被不同的函数调用过,就应该单独测试这个子函数。
重构

有些函数的内部调用不直接反映在父函数的返回值里。这往往代表着函数的纯度不够,有副作用。 可以通过重构来消除这些副作用。

side-effect.png
side-effect.png
side-effect-refactor.png
side-effect-refactor.png
mock

也可以通过对子函数进行 mock 来测试父函数的行为。

side-effect-mock.png
side-effect-mock.png

副作用

纯函数是很好做单元测试的,测试有副作用的代码情况就会变得十分复杂。

exceptions.png
exceptions.png

避免副作用

大多数副作用都是可以避免的。

results.png
results.png

无法避免的副作用

不过也存在一些避免不了的副作用

random.png
random.png
stub

stub 指的是使用一个替身来替代一些在测试过程中的指定对象,这些对象通常会开销比较大(进行了数据库查询或网络连接),或者行为难以控制(返回结果不确定)。

random_stub.png
random_stub.png
Mock.side_effect
random_side_effect.png
random_side_effect.png
转移副作用

有时候可以将函数的副作用转移到外部,从而只需要测试函数的核心逻辑

random_move_side_effect.png
random_move_side_effect.png

参数化测试

在需要测试多种输入参数的时候,可以考虑使用参数化测试

parameters.png
parameters.png

测试异步代码

在 IO 密集型的场景下,异步代码可以显著提高运行效率。

异步代码的单元测试也有一些技巧。

asyncio-test.png
asyncio-test.png

更多 mock

系统函数

测试系统函数基本上是通过mock.patch函数打补丁。

mock-patch.png
mock-patch.png
网络请求
request-mock.png
request-mock.png
数据库

针对数据库查询的单元测试并不需要进行实际的查询,只需要验证代码的行为符合预期。

redis-mock.png
redis-mock.png
文件系统

这里使用了 StubClass 和 mock 两种方式来进行文件系统的单元测试。

io-mock.png
io-mock.png

测试哪些代码

核心业务逻辑

  • 登录注册
  • 充值转账
  • 业务流程

数据库查询

对外接口

  • 身份认证
  • 参数校验
一个例子

示例

何时编写单元测试

建议在完成单个模块时编写模块的单元测试,模块的粒度可以因人而异,可以是一个函数,一个类,或一系列用于完成某个特性的代码片段

测试驱动开发

  • 确定接口
  • 编写测试
  • 运行失败的测试
  • 编写业务代码,通过测试
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-05-242,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单元测试的概念
  • 单元测试的意义
    • 确保代码实现符合预期
      • 提前发现错误,并以最小的成本修复
        • 测试代码即文档
          • 放心重构
            • 编写高质量的代码(可测试、无副作用)
              • 自动化执行
              • 范例
              • 如何进行单元测试
                • 单元测试的基本流程
                  • 测试代码的行为
                    • 分支与边界
                      • 覆盖率
                    • 内部调用
                      • 直接测试
                      • 单元测试的粒度
                      • 重构
                      • mock
                    • 副作用
                      • 避免副作用
                        • 无法避免的副作用
                          • stub
                          • 转移副作用
                        • 参数化测试
                          • 测试异步代码
                            • 更多 mock
                              • 系统函数
                              • 网络请求
                              • 数据库
                              • 文件系统
                          • 测试哪些代码
                            • 核心业务逻辑
                              • 数据库查询
                                • 对外接口
                                  • 一个例子
                              • 何时编写单元测试
                                • 测试驱动开发
                                相关产品与服务
                                数据库
                                云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
                                领券
                                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档