当前位置: 首页 > 科技观察

前端单元测试,我们应该测试什么?

时间:2023-03-14 15:49:55 科技观察

相信很多前端开发者在写单体测试的时候,最大的疑问就是:“我应该测试什么?”是的,最难的不是解决问题,而是发现问题!知道要测试哪些远比如何测试重要得多!今天看了Kent的博客《How to know what to test》的这篇博文,觉得豁然开朗,所以今天就把这篇博文分享给大家。开始知道如何进行测试对阳性来说是好的和重要的。我教过很多人测试的基础知识,如何配置工具,如何针对不同情况编写测试等等。但是知道如何测试只是成功的一半,知道要测试什么才是更重要的一半。永远记住我们为什么要测试我们编写测试以确保我们的应用程序在用户使用它们时能够正常工作。有些人可能会使用测试用例来提高工作流效率,但我更感兴趣的是提高代码的信心,即:我们的测试应该直接增加我们的代码信心。这也是我希望你在编写测试时考虑的重点:不要太执着于你正在测试的代码,而是多考虑这些代码能够支持的真实用例。如果只考虑代码本身,很容易很自然地走到不归路去测试代码的细节。我们应该考虑更贴近用户的真实使用场景来编写测试。代码覆盖率<用例覆盖率在测试时,代码覆盖率是一个指标,表明我们的代码执行了多少行。举个例子:functionarrayify(maybeArray){if(Array.isArray(maybeArray)){returnmaybeArray}elseif(!maybeArray){return[]}else{return[maybeArray]}}现在,我们还没有给出这个该函数编写测试,因此该函数的覆盖率为0%。这种情况下的代码覆盖率报告让我们知道我们需要立即编写测试,但它没有告诉我们这个功能有哪些重要部分,也没有告诉我们这个功能支持的真实用例(即我们正在编写测试的目的)。什么时候最需要关注什么?事实上,当我们在思考应该测试整个应用的哪些部分时,覆盖率报告对于“我们应该在哪里花更多的时间”这个问题并没有多大帮助。覆盖率报告只能帮助我们了解哪些代码没有被包含在测试中。所以,当你看这份覆盖率报告时,不要总是想着那些if/else、循环或生命周期,而是问问自己:这几行代码实现了哪些用例?我应该添加哪些测试用例来覆盖它们?“用例覆盖率”可以告诉我们当前测试支持哪些用例。不幸的是,没有“用例覆盖率报告”这样的东西。我们只能自己实现。然而,代码覆盖率报告有时可以告诉我们哪些用例没有被覆盖。以上面的函数为例,乍一看,我们可以立刻想到它的第一个真实用例:“传入一个数组,返回一个数组”。这可以用作我们测试用例的标题:test('传递一个数组并返回一个数组',()=>{expect(arrayify(['Elephant','Giraffe'])).toEqual(['Elephant','Giraffe'])})有了上面的测试用例,我们的覆盖情况如下(高亮部分是被覆盖的部分):现在,我们看一下没有被覆盖的部分,发现有两个这样尚不支持UseCase的种类:当传入一个虚假值时,返回一个空数组。当传入一个非假值并且它不是数组时,返回一个数组,其中包含输入值。现在让我们添加所有的测试用例,然后看看覆盖率:test('传入一个假值,然后返回一个空数组',()=>{expect(arrayify()).toEqual([])})可以很快被覆盖!test(`传入非假值不是数组,返回包含输入值的数组`,()=>{expect(arrayify('Leopard')).toEqual(['Leopard'])})好吧,现在只要我们保证不改变函数的这些使用方法,那么我们就可以自信地说:这些测试是可以通过的。代码覆盖率不是一个完美的指标,但它可以帮助我们制定自己的“用例覆盖率”。代码覆盖率也可以隐藏用例有时代码覆盖率是100%,但这并不意味着用例也被100%覆盖。这就是为什么我有时会在编写测试之前考虑所有用例。例如,假设有一个arrayify函数:test(`当传入一个非falsy值且不是数组时,返回一个包含输入值的数组`,()=>{expect(arrayify('Leopard')).toEqual(['Leopard'])})我们使用这两个用例来实现100%的代码覆盖率:输入数组,返回数组输入不是数组,返回数组,其中包含输入如果我们考虑真正的用例,会发现少了一个case:输入一个Falsy值,返回一个空数组。如果用户直接使用arrayify(),那么这样的测试用例无法让我们对代码有足够的信心。虽然现在看起来还可以,即使我们不为这个Case写测试,我们的代码也支持这样的用例,但是我们写测试的原因是因为我们要确保在我们进行代码更改之后,它能够支持我们的想。所需的用例。我们继续看这个测试的结果:如果现在有人看到这个linefilter(Boolean),会想:这是SB想到的一种奇怪的写法。终于去掉了这里。然而,我们的测试仍然通过了,但是所有依赖于这个Case的“inputfalsyvalue”的代码都挂了。你是在测试用例,而不是你的代码如何应用于你的React代码?在编写测试时,您应该始终牢记两种用户:真实用户和开发人员。说得更啰嗦一点,如果你在做测试的时候还想着业务代码,而不是真正的用例,那么你很容易陷入测试“代码实现细节”的陷阱。这样做的结果是您的代码将无形中创建第三种类型的用户:测试用户。很多人在做React代码测试的时候,往往会想到一些可以让自己不断测试“实现细节”的测试点。对此,不要过多关注要测试的业务代码,而要多想想什么会对真正的用户和开发者产生影响。这是你应该思考的UseCase,比如:生命周期方法元素事件回调相对于组件的内部状态,上面两类用户相关的一些东西也需要测试,比如他们会更改DOM、发送HTTP请求、在Prop中执行回调或生成一些可观察到的副作用,使用它们进行测试很有帮助:用户交互(在@testing-library/user-event中使用userEvent):渲染的组件?修改Prop(使用ReactTestingLibraryrerender中):如果其他开发人员使用新的Props来渲染你的组件怎么办?修改上下文(在React测试库中使用重新渲染):如果其他开发人员修改上下文并导致您的组件重新渲染怎么办?修改订阅:如果组件订阅的事件中心被修改了怎么办?(例如:firebase、reduxstore、router、mediaquery)我们应该从哪里开始测试?现在我们都知道了单体测试组件或者页面组件应该测试什么,那么应该从哪里开始测试呢?这确实是一件很头疼的事情,尤其是当你要测试一个庞大的应用程序时。好的,现在你要做的是:从真实用户的角度来看它并问:如果应用程序崩溃,最烦人的部分是什么?或者问另一个问题:应用程序崩溃的最糟糕的部分在哪里??我建议您按照此标准对应用程序的支持功能进行优先排序。您可以与您的团队和Leader一起这样做,这将是一个很好的尝试。而这种尝试会带来很多好处:帮助每个人了解测试的重要性,并让他们相信测试是重中之重。一旦你有了这个优先级列表,我建议你编写一个端到端测试来覆盖用户最常用的场景。通常,此方法涵盖了此列表中的前几个功能。您可能需要更多时间来完成此测试,但这是值得的。虽然这个E2E测试不会给你100%UseCasecoverage(你绝对不能试图得到它),也不会给你100%codecoverage(你甚至不考虑记录E2Ecoverage),但它会给你一个良好的开端,并立即增强您对当前代码的信心。一旦有了几个端到端的测试用例,就可以对一些不在端到端范围内的边缘案例做集成测试,然后在使用的功能中对更复杂的业务逻辑做单元测试。从现在开始,剩下的就是继续测试。不要再考虑100%的覆盖率,这不值得。总结只要有足够的时间和经验,您就会对要测试的内容产生直觉。你可能会犯错或感觉不好,不要放弃!坚持住,我们能赢。好了,这篇外文就为大家带来了。总的来说,我也同意“更关注UseCases的覆盖率而不是代码覆盖率”。毕竟如果完全按照代码覆盖率这个指标来,作弊的手段太多了,跟写测试是完全不一样的。初衷是相反的。写测试的目的应该是增强我们对代码的信心,而不是功利地看某个指标。后来,Kent提到了如何将测试引入团队。同样值得尝试的是:先按照功能的优先级做一个列表,然后写E2E覆盖最重要的部分,然后加集成测试,再加单元测试,一切准备就绪,剩下的就是堆测试cases,最后测试用例可以慢慢的融入到代码中。