乐趣区

关于测试:前端测试的反模式-IDCF

一、过于关注实现细节的测试

在为前端我的项目编写测试用例的时候,你兴许和我一样,曾遇到过以下困扰:

  • 明明进行了性能正确的改变,测试却挂了。修复测试有时候得认真浏览各种 mock 的细节,或者去理解很多本没有必要晓得的代码逻辑。最初修测试花的工夫比进行业务改变花的工夫还要长(甚至长很多)。
  • 对代码进行提取形象之后,为各个组件或函数增加测试,实际上是用测试工具的 API 去反复业务代码的外部实现逻辑(有时候还很麻烦!)。任何失常的重构都会导致测试失败,你原本心愿测试能通知你什么样的批改是对的,后果当初测试只能通知你代码的确有被批改。
  • 测试写好,覆盖率进步,本应信心十足地认为代码变得强壮了,可是扪心自问,你晓得本人写的这个测试弱点在什么中央,或者说还有多少细节没有涵盖。你精心模仿了一个条件,去触发逻辑流程,并且测试通过,可是在实在的浏览器交互中用户兴许并不能触发这个条件。因而,同样的情理,你在本人的代码通过了别人写的测试之后,也不能确定实在场景下没有问题,只好把后续的重任交给 QA。

造成下面三个问题的起因不止一个,但测试过于关注实现细节在我看来是最次要的。

  • 第一个问题,明明是正确的改变,可是测试不止是验证业务性能,还对实现细节提出了不该提出的要求,比方要求你的函数承受跟以前一样的参数,返回值必须是字符串而不能是数组等等。可是这个函数只是实现流程中一个小小的环节,兴许在下次重构时就会不复存在。
  • 第二个问题很相似,如果测试代码去反复实现细节,不论进行正确还是谬误的重构,你都得把测试改一遍,那原先的测试又能提供什么价值呢?
  • 第三个问题有时产生在,测试的实现细节,不能笼罩整个实在交互流程的时候。用户点击的是屏幕上的 button 按钮,而测试的终点是 onClick 事件被触发。前面的逻辑被验证胜利,可问题偏偏产生在点击环节,实在的点击兴许因为按钮状态而无奈触发 onClick 事件。

因而,才会有人提出前端的测试应尽量去模仿实在的用户行为,Testing-Library 就在其官网的“领导准则”章节,激励使用者尽量仿照利用实在的应用形式去编写测试,并明确提出,你的测试越靠近用户的实在应用形式,它就能给你越多的信念。换句话说,你的测试应该尽量少用函数去手动触发,而要尽量多地利用测试框架给你的 API,去模仿 Input 框的输出,按钮的点击,表单的提交等等。

如此一来,有的函数,你也无需写测试证实它的返回值如你所愿,须要写的,是页面显示了期待的文字,产生了预期的变动,进行了对应的跳转。你会发现,这时的测试就像写在卡里的 AC 一样。只有测试是通过的,你就有理由置信主体性能没有毁坏,而不只是函数工作失常。

二、没有独立业务含意的测试单元

看到下面的计划,你可能会立马会想到一些问题。

首先就是测试流程可能会很长,从用户填完表单,点击提交,到期待的变动呈现,当中可能经验了好几个函数的执行,连带着一系列的副作用。模仿这一系列行为,仿佛是集成测试与 E2E 测试该干的事件。如果我的项目中大部分逻辑都是由这种测试去笼罩,看起来与测试金字塔所说的由单元测试作为地基是矛盾的。

我认为,当实在遇到的问题碰到了某种教条标准时,后者该适当地退让。

激励多写单元测试的起因在于它们成本低,有针对性。可是在前端我的项目外面,很多模式上的单元并没有独立的业务含意。

拿 React 我的项目举例,好多函数只是因为它们在模式上能够被抽取进去,就被拎到一个独自的文件里,从而升高主函数的复杂度。如果给它写单元测试,你就不得不手动触发它的参数变动,或者检测它的参数函数是否有被调用。

咱们写的 React hook 尤其如此。很多时候抽取自定义的 hook 是出于逻辑上的起因,把相干的逻辑和数据聚合到一起,加重 UI 组件的累赘,但这些 hook 往往没有一个能够轻易解释分明的业务含意,而且它们也不会被其它中央应用。

所以这类“单元”只是长得像单元而已,它们其实只是一个实现环节。这里残缺的 UI 操作流程,才更像一个有价值的单元,只管它们在模式上可能超过了单个函数的领域。

但我不想矫枉过正,的确有不少状况下,一个 util 函数,一个 hook,一个很小的公共组件,都是有独立存在的价值的,因而,它们也该当被视为真正的单元,的确“有资格”领有本人的专属测试。

testing-library 上面有一个独自的库,叫 react-hooks-testing-library,让你无需通过 UI 行为层面,而是间接以 hook 的形式去测试它们。它的 GitHub 页面上,明确提出了应用以及不应用它的场景:当你的 hook 不与组件强相干,领有独立含意时能够应用;当你的 hook 只被一个组件应用,且和它的定义强相干时,则不倡议应用。

插入一段:只管存在 react-hooks-testing-library 这样的工具,但像 SWR 这样优良的三方库,在用 testing-library 为本人的 hook API 做测试的时候,仍然抉择在 UI 层面进行。办法是,把本人的 hook 置于一个长期的 div 标签里进行 render,把数据的变动映射成 html 文字的变动,最初对文字内容做断言。其实对于独立性强的函数,集体感觉搁置在 UI 外面做测试倒没有太大区别,但 SWR 的例子体现了对“仿照实在应用场景去测试”这一准则的尊重。

将下面的法则套用到 Angular 我的项目中,也是相似的。对于独立性和通用性不强的 pipe,directive,reducer,effect,service,都能够认为它们是实现流程的一部分,从 UI 行为层面写好测试即可。

总之,在构思前端测试的时候,与其死守“单元测试”的字面含意,不如结合实际场景,从新思考什么才是真正有价值的“单元”,就地取材地去写。换种角度表述,与其在意咱们写的测试是不是“单元测试”,不如谋求更外围的货色——咱们的测试有没有以适合的形式去校验逻辑。

另外,当咱们的“单元”过大,一些逻辑可能就会笼罩不上。像 sonar 这类工具,不仅会查看你的行数覆盖率,还会查看你的各项条件语句是否有被测试执行。当一套测试的行为流程囊括了多个函数,而且每个函数都有好几个 if…else 语句时,想要在 UI 操作与 mock 数据上把所有状况都笼罩到,老本就会变得十分昂扬。

对于此,咱们得抵赖,无论用什么形式组织测试,笼罩所有的条件分支都是不太事实的,而且价值也不大。对于“满足条件 A 就执行 XXX”之类的语句,条件为非 A 时没有业务上的规定,如果为了刻意笼罩函数的所有条件,就强行测它在非 A 的状况下返回一个 undefined,则没有太多价值。对这类状况,用 UI 行为测试次要条件即可,如果你切实感觉有重要的逻辑没有被笼罩,无妨回过头来想想,是不是漏掉了某种输出条件,例如特定的用户键入或者非凡的 API mock 返回值。然而,当有过多的条件分支很难用业务场景去表述和模仿的时候,咱们可能须要从新思考代码的实现逻辑是否正当了。

当然,即便按下面这样做,有时候还是会发现要笼罩的条件组合太多,从行为流程上写测试太简单,这时就不得不做肯定的斗争,为那些没有独立性的局部去独自写测试。如果这类测试不太好写,能够参照方才提到的 SWR 官网测试用到的技巧,把要测的函数或者是对象搁置在一个长期的 UI 组件下,以最小的老本做 UI 行为测试。

最初

总结一下下面谈到的几个准则:

  • 从实在用户的行为流程去测试,往往比测函数自身,能给你带来更多的信念。
  • 对于没有独立性和通用性的函数或对象,把它们视作实现的一部分,个别没有必要为它们去写独自的测试。不要拘泥于对“单元测试”的字面了解,不要被模式上的法则所解放。
  • 不要把测试覆盖率视为太过重要的指标,它的目标还是帮忙晋升代码的稳固。有的代码没有笼罩也没关系,有的代码值得你笼罩好多遍。毕竟,咱们不是为了写测试而写测试。

起源:Thoughtworks 洞见
作者:钟立
申明:文章取得作者受权在 IDCF 社区公众号(devopshub)转发。优质内容共享给思否平台的技术伙伴,如原作者有其余思考请分割小编删除,致谢。

IDCF DevOps 黑客马拉松,2021 年度城市公开赛,11 月 6 - 7 日,深圳站,企业组队参赛 & 集体参赛均可,一年等一回,错过等一年,连忙上车~ 公众号回复“黑马”退出

退出移动版