你的测试写全了吗?

  • 2020-11-24
  • 本文字数:2190 字

    阅读完需:约 7 分钟

QA 设计的测试用例大部分都是面向业务的端到端测试,怎么能保证从 DB 来的数据通过层层 service 能顺利的到达前端并被正确的展示出来呢?我们可以尝试以 UI 和 DB 作为 data flow 的两端串起所有的测试。

场景

想象一个典型的场景,一次 sign off 接近尾声:

QA:这个些 case 都有测试吗?

DEV:打开各种 IDE,UT cover 了 case A,JT cover 了 case B,API test cover 了 case C

Sign off 结束了,但是代码里的测试真的覆盖了 QA 预期的全部用例吗?

假设一个系统的数据都存储在 DB 中,而 UI 是系统与终端用户交互的部分,那么数据在 DB 和 UI 之间通过各种 service 的互相调用而展示或存储的过程就是一种 data flow。

QA 设计的测试用例大部分都是面向业务的端到端测试,怎么能保证从 DB 来的数据通过层层 service 能顺利的到达前端并被正确的展示出来呢?我们可以尝试以 UI 和 DB 作为 data flow 的两端串起所有的测试。

举个例子

某项目有一个叫 FileSharing 的 website,用户可以在上面共享文件。

系统的设计是 FileSharing Frontend 向 FileSharing Backend 发请求获取 file 和 user 信息,FileSharing Backend 向 File Service 发请求获取 file 信息,File Service 从 DB 读取 file 信息。

其中一个需求是当前用户可以看到自己和其他用户上传的文件,而他人上传且未被该用户下载过的文件名应该显示为粗体。

根据这个需求设计出两个 test case:

  • User should see new file in bold

  • User should see downloaded(or upload by himself) file not in bold

以这两个 case 为例,下图展示了数据在代码中的流动过程:

数据的流动方向是从 DB 到 UI,检查测试代码可以先从数据消费终端的 UI 开始,目的是期望测试可以证明每一步正确处理和返回信息。

第一步:FileSharing Frontend

在 file-store 的测试文件里找到两个相关测试

  • should _be _shown _as _new _file

  • should _not _be _new _file _after _download

并且通过 file-store 的代码:

get isNew() {    return !!this.isNewDocumentFromOthers;}
复制代码

可以知道需要后端返回 isNewDocumentFromOthers

结论 : 查看代码已知 file-store 是前端页面的数据源,这个测试是在 file-store level,没有测试保证 html 画出来相应的 item

第二步:FileSharing Backend

打开 user 的 file 页面时,前端发了 3 个请求:

/files/user/types/user/verified
复制代码

根据上一步得到的信息可以知道/files 就是要找的请求

{    "id": "9a4af273-2d4e-4491-8f10-a93d2ba15e42",    "country": "US",    "fileName": "Instruction of FileSharing System",    "documentType": "Message",    "isNewDocumentFromOthers": true,    "uploadedAt": "12/16/2019",    "uploadedBy": "ad77d3fc-e0af-499e-8a71-ab020073db34",    "year": 2000  }
复制代码

在 FileController 的测试文件里找到一个测试

  • should flag new files uploaded from others

并且通过 FileController 的代码:

public bool IsNewDocumentFromOthers(Guid userId){    return IsUploadedFromOthers() && DocumentType != DocumentType.Consent &&           (DownloadHistories == null ||            DownloadHistories.ToList()                .TrueForAll(HasNotDownloadedByUser));}
复制代码

可以知道 FileSharing Backend 需要 File Service 传来 IsUploadedFromOthers/DocumentType 和 DownloadHistory 的信息

结论 :这个测试是在 API level,基本可以覆盖当前测试用例,不过根据测试金字塔,API 更多应该关注是否返回了需要的 property,而 property 的值可以被下层的 UT 验证

第三步:File Service

在 FileController 的测试文件里找到一个测试,但是只验证了 file 的 name/year/country

  • should _get _by _user _id

结论 :没有验证返回 FileSharing Backend 需要的 IsUploadedFromOthers/DocumentType 和 DownloadHistory 信息

测试覆盖分析

整理以上的步骤可以得到下面的图(标出了现有/缺失测试的部分)

如果从改善当前测试的角度出发,能得到当前测试结构下的测试用例覆盖表:

(绿色表示测试基本可以覆盖用例,黄色表示有测试但是覆盖不全,在这个例子里 File Service 需要验证能返回正确的 Uploadby/DocumentType 和 DownloadHistory 的信息)

再从 data flow 的角度看可以发现更多问题:

  • UI 层面没有对 html 做测试(风险较大)

  • 现有的测试大部分集中在 API 层面,service 层以下的 UT 测试比较少

  • 服务之间缺乏契约测试导致修改一个 API 的时候只能人工检查被影响的范围

  • 测试使用的内存 DB 可能跟实际 DB 有差别(风险较小)

理想情况下,所有 data 经过的地方都需要有测试保证 data flow 不会断(数据被正确的处理和传输)。

改进

基于以上项目现状,可以得出几个投入产出比较高的 action:

  • 在 signoff 的时候 DEV 可以试着给 QA 说明现有代码的结构,解释数据流动的方式,帮助 QA 理解目前测试覆盖的范围,评估没有测试的部分风险有多大,找出 manual test 的重点

  • 后端 service 数据传递时如果是靠 API 测试验证,则需要保证每个 property 都被测到

  • 保证前端每个 view model 的行为和状态都有 JT cover

敏捷项目中开发写测试所依据的理论主要是测试金字塔,但是测试本身该写到哪一层并没有明确的标准可以遵循,当试着跳出测试金字塔的理论,从数据这个根源沿着 data flow 去看现有的测试,就会有一些新的发现,从根本上找出缺失或冗余的测试,确保测试做到更有效的覆盖。

作者介绍

王蕾 ,ThoughtWorks 测试工程师,有多年大型复杂系统质量保证经验。

本文转载自 ThoughtWorks 洞见。

原文链接

你的测试写全了吗?