第一章 介绍Vue应用的测试

第一章 介绍Vue应用的测试

本章涉及

  • 什么是测试
  • 为什么测试是有用的
  • 单元测试、e2e测试和快照测试之间的不同
  • Vue核心概念

作为一个开发者,你想发布没有bug的代码。如果你在周一早上发现你周五修改的代码在生产环境上出现问题,那没有比这更遭的了。想要确保你的应用能正确地工作,唯一的办法就是进行测试,所以你完全学会如何测试应用是至关重要的。

一个好的测试方案,能提高开发速度、强化代码质量并且限制你应用中的bug。而不好的测试方案会导致项目出现问题。本书将会教你有效地测试Vue应用,确保你能从测试中收益和避坑。读完本书你将会成为一名Vue测试高手,并且准好好随时测试你遇到的任何Vue应用。

要学习如何测试Vue应用,你将从头到尾的为“极客新闻”的克隆应用编写一个测试套件。”极客新闻“将会像其他大型应用一样,使用Vue、Vuex、Vue-router和服务端渲染。

不单单要教你测试技术,我还要教你我这些年测试方面的思维模式和方案。全书我将会提供训练你测试技术的建议。

第一章是Vue应用测试的基础知识。我将会给你关于通用测试全方面的概览、书中你会学到的不同类型的测试、以及你将要编写的”即可新闻“应用。最后,我会解释一些Vue的核心概念,来确保我们不会鸡同鸭讲。

那第一件事,就是给测试下定义。

1.1 给测试下定义

任何有价值的学术论文会在深入讨论前定义它使用的概念。所以,就像一个好的学者一样,我将会在教你关于不同测试技术前,定义我所指的应用测试

应用测试一个简单的定义就是检查应用正确行为的过程。无疑你应该检查的应用行为的正确性,但当讨论到不同的测试技术时,这个话题就变得有趣了。

有两个主要的方案来进行测试:手工测试和自动化测试。手工测试就是你自己和应用交互,自己检查程序是否正确工作。自动化测试通常做法是写程序来为你执行检查。

本书绝大部分是关于自动化测试。但是要理解自动化测试的收益,你必须理解手工测试。

1.1.1 手动测试

每一位合格的开发者都手工测试代码。它是写完源代码后合乎逻辑的下一步,就像咀嚼完食物的下一步是吞下它一样。

想象你正在创建一个登录表单。当你写完源代码后,你不会直接关闭你的编辑器然后告诉你领导说你写完表单了,不,你会打开浏览器,填写表单,然后确保它能正确完成登录过程。换句话说,你会手工测试代码。

手工测试对于小项目来说是很合适的。如果你有个待办列表应用,你能在2分钟内手工测试完成,你就不需要自动化测试。但是如果你的应用成长到某个量级时,依赖手工测试会变成一项负担。

让我和你讲讲我工作时的第一个大型JavaScript应用,它非常的乱。你有没有听说过意大利面条代码?代码就像各种类型的意大利面混杂卷在一起一样,非常难去梳理应用逻辑,并且也没有任何自动化测试。不必说,里面有很多bug。如果要解决bug,我们必须在发布前手工测试应用。每周三我们会喝掉很多咖啡,打开一个用户体验列表进行测试,并且蜷缩在笔记本电脑前,花4小时时间执行一套指令,真的非常痛苦。

定义 用户体验列表,就是用户在应用程序上会经历的每一步的列表。例如,打开应用、填写表单、点击提交。

考虑到我们要花10%的开发时间来手工测试应用,你可能会像我们会在生产环境上逐步消除bug,不,应用还是充斥着bug。原因是,手工测试大量的功能是非常困难的,它很容易让人失去耐心并且遗漏测试某些点。

有一次,我在测试一个用户体验的时候,我不小心遗漏检查了一个是否会显示音乐路径的点击按钮,其他开发人员也肯定忘记测试这个功能,因为它在线上存在了整整几个月了!

尽管一些手工测试的时间被用在发测试新功能上,但更多的时间被花在测试旧功能是否正常运行,这种测试就是众所周知的回归测试。回归测试对人来说是一件困难的事情,它们重复且乏味,还需要很多注意力而且没有创造价值。简单来说,他们很无聊,幸运的是,计算机很擅长做这种事情,这就是自动化测试的作用!

1.1.2 自动化测试

自动化测试就是通过程序来检查你的软件是否工作正常。或者说,你写额外的代码来测试你的应用代码。在测试代码被写出来后,你可以花最少的力气,想几次就几次地测试你的应用。

你可以使用一些不同的技术来编写自动化测试。你可以编写浏览器中的自动化代码,直接而调用你源码中的函数;或者比较你应用渲染后的快照。这些技术有不同的收益,但它们都有一个共同点:它们比你手工测试要省时间。

在前面章节中,我说我参与过没有测试的应用,它的问题之一就是每次我们要发布新版本,都需要花上进行4小时的手工测试。在我加入团队不久,CTO就决定我们应该写自动化测试来代替人的工作。一段时间后,我们测试所花费的时间从4小时人工工作减少到了20分钟的自动化工作。

有了这次经验,对应大型项目,我总是一开始就写好自动化测试。驯服一匹从小就跟着人生活的马总是要比培养一匹囚禁起来的野马要容易。在本书中,你将会学习通过编写测试来创建一个简单的应用。

自动化测试对于检查你的应用是否仍然正常工作非常有用。它也令检查应用代码的变化更加简单。让我们来看看一个使用自动化测试的真实例子——在GitHub测试PR(pull request)。

1.1.3 从GitHub上PR测试

GitHub是一个Git托管仓库网站。很多开源项目比如Vue都在GitHub上托管,很多我就职过的公司都把他们的代码维护在GitHub的私仓上。

定义
Git是一个版本控制系统。我这里假定你曾经用过,并且熟悉合并、分支、提交这些概念。如果你不熟悉,你可以在Git文档查看学习。

PR是GitHub工作流(GitHub flow)的一部分,在开发者将独立的分支合并到主分支之前,它提供一个cv(code review,代码评审)的机会。

说明
如果你不熟悉GitHub工作流,可以阅读这篇文章《理解GitHub工作流

在没有测试的情况下,当你cv的时候,你需要将改动的代码拉到你的设备上,运行应用,然后手工测试代码看看是否能正常运行。这很费时间,而且你也不会因为听到说别人在cv一个PR的时候,略过了这个过程而感到惊讶。

而自动化测试会让这个过程简单得多。当你在项目中配置类自动化测试,你可以设置一个服务器用来下载待合并分支,执行测试套件,最后返回一个是否通过测试的报告(图1.1)。只要你信任测试,那你就不再需要在自己的设备上检查代码了。

tu图1.1

说明
许多开源项目要求开发者在添加新功能的时候加入新的测试,Vue项目只接受那些包含为新代码写了测试的PR。

自动化测试除了使PR更容易评审之外,也是CI/CD(持续集成/持续交付)等现代工作流成为可能。你可以在[Martin Fowler的博客](http:// mng.bz/nxVK)了解到它们。

目前为止我定义了自动化测试和手工测试,现在来讲更细致些,下一小节我会讲些概览,包含了自动化测试技术,以及你可以如何用它们去测试应用。

说明 就像the Facebook丢掉the变成Facebook一样,现在也该丢掉自动化测试前面的自动化了。从现在起,我会将自动化测试简称为测试

1.2 测试概述

目前位置,我讲的测试还是在一个比较泛的层面。现在,我们来讲讲一些你能具体去写的测试类型。在本书里,你将会学到3种前端应用的测试类型——单元测试、快照测试和e2e测试。

1.2.1 e2e(end-to-end)测试概述

e2e测试是最直观的测试类型。对于前端应用,e2e测试会自动运行浏览器,从用户角度来检查应用是否正确运行。

想象一下你在写一个计算器应用并且想测试它能否正确地将两个数相加。你可以写一个e2e测试来打开浏览器,加载计算器应用,然后点击数组“1”按钮,再点击“+”号,再点击“1”按钮,点击“=”按钮,最后检查屏幕是否正确显示结果“2”。你可以在下面的例子看到,如果是代码实现会是什么样子。

表1.1 一个检查计算器两数之和的e2e测试

function testCalculator(browser) {
  browser
    .url('http://localhost:8080') // 打开在本地运行的应用
    .click('#button-1') // 点击计算器的按钮
    .click('#button-plus')
    .click('#button-1')
    .click('#button-equal')
    .assert.containText('result', '2') // 断言计算器正确显示的结果
    .end();
}

e2e测试是个省时利器。在你写完e2e测试后,你可以随心所欲地运行它,想象下有上百个这样的测试套件能够省下多少时间!

在一开始,e2e测试看起来像是唯一你需要的测试工具,但这里有一点问题。首先,e2e测试非常慢,加载浏览器需要花上几秒,并且网站响应可能会比较慢。通常一个e2e测试套件运行需要花费30分钟,而且如果你单纯靠e2e测试,那你将会花上几个小时运行测试套件。

e2e测试的另一个问题是,它的调试非常困难。如果你要调试e2e测试,,你需要打开浏览器,然后自己模仿用户操作来复现bug。在本地设备运行e2e测试是很不好的,而且如果出现错误的是在你的持续集成服务器而不是你本地设备,此刻你将会很头大。

说明
避免可重复性问题的一个办法,就是在可重复的环境中运行e2e测试,比如docker容器中。docker容器已经超出了本书的范围,但你应该考虑调查它们来运行e2e测试,以避免因为不同机器而导致失败的问题。

e2e测试还有一个问题,那就是它们有可能是不可靠的测试。不可靠的是指经常失败的测试,尽管它们测试的应用是能够正常工作的。也许是因为代码运行太久了,也可能是API接口临时坏掉了。它就像一个不靠谱的朋友,你将不再认真对待一个不靠谱的测试。“哎,测试又失败了!让我看看,噢,又是那个问题,它总是整天失败,这个问题不用太担心。”不靠谱的测试会令你的测试套件不好用,但当你编写e2e测试的时候这是难以避免的。

如果你列一个程序员的吐槽榜,那我打赌e2e测试肯定会排在前三名。尽管它很有用,但不应该只揪着它用。

在本书中,只有一章专门讲e2e测试,一方面是e2e测试的缺点,另一方面是因为e2e测试和框架无关的,不管你的应用是Vue或者MooTools写的,它都能正常工作。

e2e测试自动化了你需要手动操作的部分,你可以设置它基于规律的间隔,在生产环境的网站上或者在合并代码分支之前执行。

e2e测试不是给你一个新的测试方法,它只是更快的手工测试。另一方面,单元测试提供了一个手工测试无法提供的新工具。

1.2.2 单元测试概述

单元测试是基于应用最小的部分(单元)运行测试的过程。一般来说,你测试的单元是函数,但是在Vue应用中,组件也是测试的单元(稍后会介绍)

还记得计算器应用吗?在代码中,应用通过sum函数来计算两数之和。

如果你通过修改了代码以提高可读性,你可能还想测试一下函数是否可以正常运行。你可能会运行e2e测试,但是如果e2e测试失败,你可能无法知道问题是否是因为sun函数,还是源代码的其他部分。确定是否是sum函数出问题的唯一办法就是独立运行这个函数,你可以通过单元测试来实现。

单元测试是在独立环境中的函数,调用你源码中的函数并断言它们的正确行为。看看下面这个简单程序的代码,这段代码导入sum函数、运行并且在它的返回值不是2时抛出异常。

表1.2 一个基础的单元测试

// sum.js
export default function sum(a, b) { // 要被测试的函数
  return a + b;
}

// sum.spec.js
import sum from '../sum' // 将sum函数导入到测试文件中

function testSum() {
  if(sum(1, 1) !== 2) {
    throw new Error('sum(1, 1) did not return 2') // 如果返回值不是2,则抛出异常
  }
}

// 运行测试
testSum()

因为单元测试在独立的环境中运行,所以当一个写得好的单元测试失败,它会像一个闪烁的霓虹灯一样,指向出现问题的代码。

不像e2e测试,单元测试运行得很快。它们运行只要几秒钟,所以你可以在你每次修改代码的时候,运行单元测试快速获得反馈,来确认这些修改是否会影响到代码现有的功能。

单元测试令人满意的一个点是它提供了说明,如果一个新的开发者刚接触项目,并且想知道一个单元的行为,他们可以通过看单元测试来了解一个单元预期的行为是怎样的。

我前面说过e2e测试的不可靠,测试会定期失败尽管应用是正确工作的。写得好的单元测试不会出现这种问题。只要单元测试是确定的,你可以运行它上千次,而且它每次都能通过。

目前为止,我只讲了单元测试的好处,简直让人脸红。但是我不相误导你,像e2e测试和单元测试都有它们自己的问题。

单元测试的一个大问题就是它让代码难以重构,人们不经常讲这个问题,但是我经常遇到这个问题。

定义
重构一般就是为了提高代码质量,重新实现代码的过程(但这取决于是谁来重构代码)。

如果你有一个带有单元测试的复杂函数,并且决定将它分成两个独立的函数,你需要将代码对应的单元测试也做修改。这会让人不愿意去重构,有时候我不想去修改代码结构就是因为有太多额外的单元测试代码需要更新。这没有简答的解决方案,你在判断写测试代码长期来看能否能够节省时间时,这是需要额外考虑的点。

单元测试的另一个问题是它们只检查应用某个独立的部分。你可以检查一辆车每个单独的部分都能正常运行,但是如果你没有检查它们合并起来的运行状态以及引擎没有点火,你的测试是没有用的。单元测试就有这个问题,它们保证了代码的单元能如预期,但是它们没有测试单元间的工作是正确。这就是为什么需要用e2e测试来补充单元测试。

到现在,我向你介绍了e2e测试和单元测试,最后一个你会从本书中学到的测试就是快照测试。

1.2.3 快照测试(Snapshot testing)

你玩过“大家来找茬”吗?“大家来找茬”是一款游戏,它提供2张看起来一样但是有细微不同的图片,目标就是找到图片中不同的地方。

快照测试和”大家来找茬“有点相似,它会在你运行的程序中拍下快照图片并和之前保留的图片做比对。如果它们不一样,那快照测试就失败了。这中测试是一个很有用的方法来确保代码在修改后还能正确渲染。

传统的快照测试会在浏览器中加载应用,并给渲染的页面拍下快照。它们会将新拍下的图片和已经保存的图片做对比,如果存在不同那就会显示一个错误。这类快照测试存在一个问题,那就是不同的操作系统或者浏览器版本会导致测试失败,尽管快照压根就没有变化。

在本书中,我会教你怎么用Jest测试框架写快照测试。Jest快照测试可以比较JavaScript中任何序列化的值来代替对比快照。你可以使用他们来比较从Vue组件中输出的DOM。在第12章,你可以学习到快照测试的细节。

定义
序列化指的是,任何代码可以被转化为字符,然后再转换回原来的值。实际上,它依赖于V8的方法,但是这里不需要去深究里面的细节!

现在你已经了解了每个你将要写的测试类型。现在我们来看看如何通过组合不同的测试方式,来实现一个有用的测试套件(suit)。

1.2.4 有效组合测试方式

如果你将糖、面粉和奶油用正确的比例进行组合,你就能做出美味的曲奇面团。但是如果你弄错了比例,你可能做出的是一团奶团。你需要通过正确的组合比例,对不同的测试套件进行组合,才能确保设计出的是高鲁棒性(译者注:就是高可靠性)的测试套件,而不是一堆乱七八糟的测试代码。

如图1.2,你可以看到前端测试金字塔。这表示了在你的前端代码中,不同的测试方式的比例。

图1.2 前端测试金字塔
图1.2

单元测试是金字塔最主要的组成部分——能在开发过程中提供快速的反馈。快照测试运行起来也很快,而且它覆盖的范围要比单元测试更大,所以你不需要像单元测试那样多的快照测试。

正如前面所说,用e2e测试应用是很棒的,但是它很慢而且容易出奇怪的测试问题。所以避免奇怪测试问题的最好方法就是不写它们,所以前端测试金字塔只有一小部分的e2e测试。

非集成测试(No integration tests)

如果你是一名经验丰富的开发者,你可能听说过集成测试。集成测试是另一种测试的类型,通常是由单元测试和e2e测试组合而成。

但我并不推荐给前端代码写集成测试。集成测试在前端既难以定义,又难以编写,还难以调试。

人们对集成测试有不同的定义,特别是在前端领域。有些认为运行在浏览器中的测试就是集成测试;有些则认为只要测试中,测试的单元依赖于一个模块,就算集成测试。有些还认为,只要是完全渲染一个模块(fully rendered component)就算集成测试。

第13章,为了确保后端服务能够正确地返回http请求,我将会教你如何实现一个服务端测试(我自己的定义的概念)。但是在这本前端测试的书中,你将不会写任何关于集成测试的代码。

在本书中,你将会创建一个测试套件,它的比例结构会和前端测试金字塔一致。我将会教你如何编写一个测试驱动开发工作流的测试套件。理解测试驱动开发的工作流是非常重要的,它将能让你深刻理解本书中代码结构是如何组织的。

1.2.5 测试驱动开发(TDD,Test-driven development)

测试驱动开发(TDD,Test-driven development)是一个工作流,指的是在写源代码(source code,译者注:或者称为业务代码)前,先编写失败测试(failing test)的代码。在写一个组件代码前,先写测试代码来确保组件行为正确。
一个流行的TDD方式是红、绿、重构(译者注:又称”失败-实现-通过“方式,敏捷开发的一种方式)。红、绿、重构是指:写一个失败测试(红),让测试通过(绿),最后重构代码让它更具有可读性。

说明
我理解TDD不是适合每一个人,我也不打算向你推荐它。你不需要因为这本书就信仰TDD来获得好处。我在这本书中使用TDD的主要原因是,它让测试代码先于源代码,而本书的内容中,测试代码是远比源代码重要的。

TDD有很多中口味——香草味、酸橙味、车厘子味、橙子味。我开玩笑的,当然,有很多中方式达成TDD。这本书使用一个关注前端版本的TDD。

一些TDD的拥趸在写源代码前,会写完全部的测试代码,但我不严格遵守TDD。我会在写源代码之前写好单元测试,然后在源代码完成后,再加入e2e测试和快照测试。我一般写一个vue组件的流程是这样的:

  1. 确定我要的组件
  2. 为每个组件编写单元测试和源代码
  3. 给组件编写样式
  4. 为完成了的组件添加快照测试
  5. 在浏览器中手动测试下代码
  6. 编写e2e代码
    在实际的生活中,有时候我并不会为组件编写单元测试,有时候我又会在写测试代码前先写组件代码。TDD的拥趸听到这可能会面露不悦,但是我发现死板地套用TDD流程会让开发速度变慢。

常言道,生活在于过程,而不在于目的地。尽管在一般的生活中这句话是真理,但是在开发应用的情况下就恰恰相反了。只要你写出的是有价值的、节省你时间的测试代码,那怎么写出来就显得不是那么重要了。

我会在书中用大量篇幅告诉你去测试什么,向你展示测试代码,最后是还有那些让测试能通过的源代码。当你在给一个测试代码加入源代码之前,在运行测时预期测试将失败。

到目前位为止,我告诉了你自动化测试的好处,但在你因为过于激动而去建一个什么强化自动化测试(automated-test-appreciation)的社团之前,有一个免责声明,那就是自动化测试不总是必须的。

1.2.6 学习什么时候不测试

在我刚开始写自动化测试的时候,我想要测试全部东西。因为我曾经一开始经受过没有测试代码的应用的痛苦,然后,就像一个宿醉的中年男人一样,我下定决心不再这样。但是我很快又学到一课,那就是测试降低开发速度

当你在写测试代码的时候,时刻牢记你写它们的原因是很重要的。通常来说,测试的目的是节省时间,如果你在工作的项目已经很稳定而且会长时间开发下去,测试代码才会产生收益。

但当编写和维护测试代码所花的时间,比它们所节省下来的时间还要多,那你就压根不应该写这些测试代码。当然,我们很难去知道在你写测试代码之前,确认到底你能省多少时间——随着时间推移,你才会了解到它。但是,例如,如果你在创建一个原型,为一个短期项目工作或者是为一个创业公司迭代想法,你几乎不会从编写测试代码中获益。

就算一个项目能从测试中获益,它也很可能没有你想象中那样,需要那么多的测试。让我来告诉你你一个100%代码覆盖率的误区。

1.2.7 100%代码覆盖率的误区

代码覆盖率是用来衡量你代码库中,有多少代码已经运行过你自动化测试的代码行数。通常代码覆盖率通过百分比衡量:100%的代码覆盖率意味着在运行测试中,每一行代码都被执行了;0%意味着一行也没有被执行到。这是一个有趣的指标,但是它会导致一些可怕的后果。

测试提供的是边际递减的收益。它就像去健身房,当你第一次去健身房,你的肌肉增长会非常快。你只要在几个月内,每周去3个小时的健身房,就可以减掉你的啤酒肚并且看起来身材健美。但是随着你变得愈发强壮,你增肌的时间就越多。你在健身房花的时间越多,你从这些额外花费的时间所获得的收益就越少。

这种规律在测试方面是一样的。你能只花一点时间,就写出一个简单的测试代码来覆盖你应用中的核心功能。在你完成这些测试后,提升你代码覆盖率就会愈发困难。如果你的目标是100%的代码覆盖率(一些开发者的圣杯),那这就像想要从毛巾中拧干最后的一滴水一样难。

大部份时候,100%的代码覆盖率不是目标所在。当然,如果你是在给一个支付应用的核心岗位上工作,一个bug可能会导致百万级别的损失,那100%的覆盖率对你是很有价值的。但在我的工作经验中,大部份的应用并不会从100%的代码覆盖率中受益。

在过去工作的几年,我曾经在0%覆盖率、100%代码覆盖率、以及介于两者间覆盖率的项目工作过。0%代码覆盖率是开发变得困难,但是100%代码覆盖率会导致开发效率降低并且比鼻涕虫攀登一座沙丘还痛苦。

达到传说中的100%代码覆盖率不单单是十分好费时间的,而且就算到到100%代码覆盖率,测试代码也不一定能发现bug。有时候你可能会做出错误的假设,比如你测试的代码调用了一个API,并且你假设它永远不会返回错误;当在生产环境中API真的返回了一个错误,那你的应用就会崩溃。

你不会因为在每个应用中,努力达到100%的代码覆盖率就成为测试高手。就像一个好的MMA运动员知道应该什么时候从战斗中脱身,一个真正的测试高手知道什么时候要写测试,什么时候不写。

在下一章中,你将开始在“极客新闻”(Hacker News) APP上写下你第一个单元测试。在此之前,我会给你“极客新闻”APP整体的概况以及这个应用的样子。

1.3 编写“极客新闻”APP

当我第一次接触前端应用测试的时候,我读的教程教给我的是如何为小型应用编写测试代码。它这样对于单纯学技术是有效的,但是对于我在真实世界中的大型应用测试中所遇到的问题,它并没有提供答案,我只能靠自己寻求答案。在这本书中,我想从头到尾教你如何去测试一个应用,因此你将会为基于真实世界的“极客新闻”的克隆程序,编写测试代码。

“极客新闻”是一个社会新闻网站。它有动态的订阅流,就像新闻故事、博客文章和工作列表(图1.3)。用户可以给一条信息投票加分或者减分。如果你用过Reddit,你对这个概念应该会很熟悉。

图1.3 极客新闻应用
图1.3

说明
弄懂“极客新闻”最好的方式是你自己去访问它:https://news.ycombinator.com

在本书中,你不需要去实现一个投票系统,这的复杂度已经超出了Vue组件测试的范畴。你要做的是创建一个应用,它能显示列表项、评论以及从“极客新闻”API中获取的真实的用户数据。

“极客新闻”的克隆程序将会使用Vue作为视图部分,Vuex作为状态管理部分,以及Vue-router作为客户端路由。不用担心你之前没有使用过Vuex和Vue-router,我会在书的后面详细讲解他们。

用来教学如何测试Vue应用,“极客新闻”克隆程序是一个很好的应用。它既足够复杂让你能学到高级的测试技术,又简单到能避免你陷入到设计的细节中。

现在你已经知道了将会学到什么,差不多来谈谈Vue了。对于一本讲测试Vue的书来说,我们已经离题很远了

1.4 Vue测试概述

这是一本关于Vue测试的数,而非开发Vue应用的书。我不会从头教你如何去使用Vue。如果你是一个对Vue完全0经验的小白开发者,那应该从课外自学一下Vue的基础知识,这样你才能从本书中获得最大的好处。

说明
如果要从零开始学Vue,我推荐由Erik
Hanchett和Benjamin Listwon写的《Vue.js in action》这本书(Manning出版社, 2018年发行)。又或者,可以是我读过的最好的文档之一的Vue官方文档。你可以在这里查看https://vuejs.org/v2/guide

就是说,我将在贯穿全书中用来简要解释Vue特性或者连接资源,是给你需要时用来了解更多细节,并且书中有2章专门给你学习更复杂的主题Vuex和Vue-router。

尽管我不会教Vue.js 101(译者注:这里应该指的是Vue.js 101教程,可以点击链接查看)。但我会在下一章之前教你一些基础的概念,以便我们不会鸡同鸭讲。那我第一个要教你的术语就是Vue实例(Vue instance)

1.4.1 Vue实例

Vue应用是由Vue实例构成,每个应用最少有一个Vue实例。并且当你为组件编写单元测试时,你将在测试中的组件创建一个vue实例。

在 表1.3 中,你可以看到一个Vue应用的简单例子。要启动应用,你需要使用options对象来创建一个新的Vue实例。Vue使用el选项来找到DOM节点,在其中渲染从模板字符串的生成节点。

说明
我将会假定你熟悉DOM,如果你不熟悉DOM,你可以在MDN查到相关的介绍

表1.3 创建一个Vue实例

new Vue({
  el: '#app', // 选择器用于从DOM中找到用来渲染的元素
  template: '<div>{{message}}</div>', // 模板字符串用于生成DOM节点
  data() { // data在模板字符串中使用
    return {
      message: 'hello Vue.js!'
    }
  }
})

生成DOM节点来床创建Vue实例就是众所周知的挂载实例。如果你之前写过Vue应用,那你将挂载一个Vue实例来启动应用程序运行。

说明
如果你还对什么是Vue实例感到困惑,你可以在Vue文档中学习到它。

在 表1.3 的例子中,使用了模板字符串,来描述Vue应该生成的DOM节点。你可以使用不同的方式来描述Vue应该渲染的DOM节点,现在我们来学习它。

1.4.2 模板和渲染函数

Vue提供了声明的方式来渲染DOM。换句话说,你描述Vue应该渲染的DOM节点。

你可以通过两种方式描述DOM节点:模板和渲染函数。模板使用HTML语法来描述一个DOM,就像下面的代码这样:

表1.4 一个模板字符

new Vue({
  // ..
  template: '<div>{{message}}</div>' // 渲染一个消息属性的模板字符串
  // ..
})

为了让Vue从模板中生成一个DOM节点,他需要将模板转换为渲染函数,称为模板编译。而下面的表中,你可以在Vue的options中直接使用渲染函数代替使用模板字符串。

表1.5 使用渲染函数

new Vue({
  // ..
  render(createElement) {
    return createElement('div), this.message
  }
})

Vue运行渲染函数来生成真实DOM的虚拟DOM(JavaScript代理),将在下面的代码展示。然后它会比较虚拟DOM和真实DOM之间的区别,并且更新真实DOM来和虚拟DOM保持一致。

表1.6 虚拟DOM例子

{
  tag: 'div',
  children: [
    {
      text: 'Hello Vue.js'
    }
  ]
}

说明
如果你想学习更多关于渲染函数或者虚拟DOM,你可以在Vue文档中的http://mng.bz/dP7Nhttp://mng.bz/VqwP找到。

渲染函数的可读性比模板差。你在写组件的时候应该尽可能使用模板,但你应该在这么做的时候意识到,Vue必须把模板编译到渲染函数中去。

模板让代码变得易读,但一个大模板也会变得难懂。Vue由一个组件系统组成,所以你可以分割模板成为易读且好维护的独立单元。本书的大部分是关于Vue组件的单元测试,所以你必须对什么是Vue组件有深入的理解。

1.4.3 理解Vue组件系统

组件是你可以在Vue模板中使用的独立模块代码。他们抽象了逻辑,使模板更易读。如果你用过像React或则Angular之类的前端框架,你会对组件的概念感到熟悉。对于大型Vue应用,它自始至终都是组件。

解释组件最简单的方式就是让你看看代码。你可以看到在接下来的代码示例中看到<custom-title>组件。请注意,在使用Vue注册组件后,你就可以像用HTML标签一样在你的代码中使用它。

表1.7 在Vue中全局注册组件
JavaScript

Vue.component('Hello-vue', {
  template: '<div>Hello Vue.js</div>'
})

HTML

<div>
  <hello-vue />
</div>

你可以通过一些不同的方式来定义组件,但在这本书里,我将会让你写单文件组件(single-file components, SFCs。译者注:后面将简称为SFC)。

说明
这本书中所有的技术,对于任意通过正确方式定义的Vue组件,都会以相同的方式工作。

Vue的SFC可以通过它们的.vue拓展符号标识。SFC可以包含一个<template>块(和模板字符串一样)、一个<script>块、一个<style>块以及自定义块(表 1.8)。

说明
在本书中,你不应该使用任何自定义块,但你可以在vue-loader文档中学习到它们。

<script>块中导出的对象,被称为组件选项对象(component options object),它包含了许多Vue根实例可以使用的选项。

表1.8 单文件组件(SFC)

<!-- template块 -->
<template>
  <div>{{message}}</div>
</template>

<!-- script块 -->
<script>
  export default { // 组件选项对象
    data: {
      message: 'Hello Vue.js!'
    }
  }
</script>

<!-- style块 -->
<style>
  div {
    color: red;
  }
</style>

SFC不是合法的JS或HTML,你不能直接在浏览器中运行他们,所以你需要在你发送他们到客户端之前编译他们。

一个编过的SFC,会变成一个JavaScript对象,它带有模板转换而成渲染函数。你可以在下面看到例子。

表1.9 编译过的SFC

Module.exports = default {
  render() {
    var _vm = this;
    var _h = _vm.$createElement;
    var _c = _vm.self._c || _h;
    return _c('p', [_vm._v("I'm a template")])
  },
  name: 'example-template'
}

我希望这段代码没有吓到你,实际上,编译过的SFC并不是设计给人们阅读的。你不需要因为编译过的渲染函数就焦虑,使用渲染函数是Vue框架的工作。这里的要点是SFC被编译成一个带有渲染函数的对象。

这让你很好地了解应该测试的组件的哪一部分。

1.4.4 单元测试组件

决定编写哪些单元测试是很重要的,如果你为组件的每个属性都编写测试,那你的开发将会很慢,并且创建了一个低效的测试套件。

一种决定组件哪部分应该被测试的方法,是使用*组件契约(component contract)*的概念。组件契约是当前组件和应用其他组件之间的约定。

当你开始一份新工作的时候,你会和你的雇主签订一份契约。你同意你每周将会花40小时的时间在工作上,以换取工资。因为你已经在契约中同意了工作40小时,你的雇主也能放心地假设,只要他们支付你工资,他们就能让你每周工作40小时。

同理,当你写了一个组件被用作应用的一部分,你会定义一个组件行为的约定,其他组件会完全假设这个组件会完全按照约定,并且只要提供正确的入参就会产出约定的输出。

输入和输出的概念在组件契约中很重要。一个好的组件单元测试应该总是由一个输入触发,并且断言组件有正确的输出(图1.4)。你应该从一个不清楚组件如何实现的,但使用这个组件的开发者角度来编写测试。

图1.4

组件常见的输入就是用户动作,比如当用户点击一个按钮;最常见的输出就是渲染函数生成DOM节点。但Vue组件中还存在很多种的输入输出,例如,输入可以是:

  • 组件props属性
  • 用户动作(比如按钮点击)
  • Vue事件
  • Vuex储存的数据

Vue组件的输出可以是:

  • 分发事件(emitted events)
  • 外部函数调用

说明
不要担心你不懂分发事件和Vuex存储数据是什么。你将会在本书后面学习到他们。

想象一下,你有一个授权状态的组件,它接收一个authorized的prop。如果authorizedtrue,它将在<div>元素中渲染“你已被授权”,如果authorizedfalse,它将在<div>元素中渲染“你未被授权”。

定义
prop是传递给组件的一段数据。prop是一个父组件到子组件传递数据的方式。你可以在Vue文档中学习更多关于它的知识。

你可以在下面看到AuthorizedMessage组件。

表1.10 AuthorizedMessage.vue

<template>
  <div>
    <!-- 条件渲染文本 -->
    {{ authorized ? '你已被授权' : '你未被授权' }} 
  </div>
</template>

<script>
  export default = {
    name: 'loader',
    props: ['authorized'] // prop声明
  }
</script>

如果你在一个应用中使用了这个组件,你会希望当你给prop authorized赋值为true的时候,它显示“你已被授权”;当你给prop authorized赋值为false的时候,它显示“你未被授权”。这是它的组件契约,也是你应该为之写单元测试的功能。在本书里,我将会使用组件契约的概念来告诉你,应该为组件写什么测试。

现在你已经对测试有了全面的概览,你将会通过自己成为一名测试高手。在下一章,你将会创建一个测试脚本并且写下你第一个单元测试。

总结

  • 有两种类型的测试,自动化测试和手工测试。
  • “前端测试金字塔”测试套件有单元测试、快照测试和e2e测试。
  • 测试不总是有收益的,如果测试不能为你节省时间,那你就不应该写它。
  • Vue应用由Vue实例组成,Vue实例使用模板字符串或则渲染函数来描述DOM。
  • SFC(single-file component)会被编译成带有渲染函数的对象。
  • 你可以使用组件契约来定义要为Vue组件写什么单元测试。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值