vue.js数据驱动_Vue.js的测试驱动开发简介

vue.js数据驱动

by Sarah Dayan

通过莎拉·达扬

Vue.js的测试驱动开发简介 (An introduction to test-driven development with Vue.js)

Test-driven development (TDD) is a process where you write tests before you write the associated code. You first write a test that describes an expected behavior, and you run it, ensuring it fails. Then, you write the dumbest, most straightforward code you can to make the test pass. Finally, you refactor the code to make it right. And you repeat all the steps for each test until you’re done.

测试驱动开发(TDD)是在编写相关代码之前编写测试的过程。 您首先编写一个描述预期行为的测试,然后运行它以确保其失败。 然后,编写最简单,最直接的代码即可通过测试。 最后,您重构代码以使其正确。 然后对每个测试重复所有步骤,直到完成。

This approach has many advantages. First, it forces you to think before you code. It’s commonplace to rush into writing code before establishing what it should do. This practice leads to wasting time and writing complicated code. With TDD, any new piece of code requires a test first, so you have no choice but take the time to define what this code should do before you write it.

这种方法有很多优点。 首先, 它迫使您在编写代码之前先进行思考 。 在确定应该做什么之前急着编写代码是很平常的事。 这种做法导致浪费时间和编写复杂的代码。 使用TDD时,任何新代码段都需要首先进行测试,因此您别无选择,只能花时间定义该代码在编写之前应该做什么。

Secondly, it ensures you write unit tests. Starting with the code often leads to writing incomplete tests, or even no tests at all. Such a practice usually happens as a result of not having precise and exhaustive specs, which leads to spending more time coding than you should. Writing tests becomes a costly effort, which is easy to undermine once the production code is ready.

其次, 它确保您编写单元测试 。 从代码开始通常会导致编写不完整的测试,甚至根本不会编写任何测试。 这种做法通常是由于没有精确而详尽的规范而发生的,这导致花费更多的时间进行编码。 编写测试成为一项昂贵的工作,一旦准备好生产代码,就很容易破坏测试。

Unit tests are critical to building robust code. Overlooking or rushing them increases chances of your code breaking in production at some point.

单元测试对于构建健壮的代码至关重要 。 忽略或匆匆处理它们会在某些时候增加代码在生产中中断的机会。

为什么要对组件使用TDD? (Why do TDD for components?)

Testing a component can be counter-intuitive. As we saw in Unit Test Your First Vue.js Component, it requires a mental shift to wrap your head around testing components versus testing plain scripts, knowing what to test, and understanding the line between unit tests and end-to-end.

测试组件可能违反直觉 。 正如我们在第一个Vue.js组件单元测试中看到的那样,需要花很多时间才能将头转向测试组件而不是测试普通脚本,知道要测试什么以及理解单元测试和端到端之间的界限。

TDD makes all this easier. Instead of writing tests by examining all bits and pieces of a finished project and trying to guess what you should cover, you’re doing the opposite. You’re starting from actual specs, a list of things that the component should do, without caring about how it does it. This way, you’re ensuring that all you test is the public API, but you’re also guaranteeing you don’t forget anything.

TDD使这一切变得容易 。 与其做的是通过检查完成的项目的所有零碎部分并尝试猜测应该覆盖的内容来编写测试,而是相反。 您从实际的规格开始,列出了组件应做的事情 ,而无需关心它的工作方式。 这样,您可以确保测试的只是公共API,而且还可以确保您不会忘记任何东西。

In this tutorial, we’ll build a color picker. For every swatch, users can access the matching color code, either in hexadecimal, RGB, or HSL.

在本教程中,我们将构建一个颜色选择器 。 对于每个样本,用户都可以访问匹配的颜色代码(十六进制,RGB或HSL)。

Despite its apparent simplicity, there are a bunch of small pieces of logic to test. They require some thinking before jumping into code.

尽管表面上看起来很简单,但仍有许多小逻辑需要测试。 在进入代码之前,他们需要一些思考。

In this article, we’ll deep dive into TDD. We’ll put some specs together before we write a single line of code. Then, we’ll test every public feature in a test-driven fashion. Finally, we’ll reflect on what we did and see what we can learn from it.

在本文中,我们将深入研究TDD。 在编写一行代码之前,我们将把一些规范放在一起 。 然后,我们将以测试驱动的方式测试每个公共功能 。 最后,我们将反思所做的事情, 看看可以从中学到什么

开始之前 (Before we start)

This tutorial assumes you’ve already built something with Vue.js before, and written unit tests for it using Vue Test Utils and Jest (or a similar test runner). It won’t go deeper into the fundamentals, so make sure you get up to speed first. If you’re not there yet, I recommend you go over Build Your First Vue.js Component and Unit Test Your First Vue.js Component.

本教程假定您之前已经使用Vue.js构建了某些东西,并使用Vue Test UtilsJest (或类似的测试运行器)为其编写了单元测试。 它不会更深入地介绍基本原理,因此请确保您先入门。 如果您还不存在,我建议您仔细阅读Build Your First Vue.js Component单元测试Your First Vue.js Component

TL;DR: this post goes in-depth in the how and why. It’s designed to help you understand every decision behind testing a real-world Vue.js component with TDD and teach you how to make design decisions for your future projects. If you want to understand the whole thought process, read on. Otherwise, you can go directly to the afterthoughts at the end, or look at the final code on GitHub.

TL; DR: 这篇文章深入探讨了如何以及为什么。 它旨在帮助您了解使用TDD测试实际Vue.js组件背后的每个决策,并教您如何为将来的项目做出设计决策。 如果您想了解整个思考过程,请继续阅读。 否则,您可以直接转至最后的思路 ,或查看GitHub上的最终代码。

写下您的规格 (Write down your specs)

Before you even write your first test, you should write down an overview of what the component should do. Having specs makes testing much more straightforward since you’re mostly rewriting each spec in the form of tests.

在编写第一个测试之前, 您应该写下组件应执行的操作的概述 。 拥有规格使测试变得更加简单,因为您大多是以测试的形式重写每个规格。

Let’s think about the different parts that compose our component, and what they should do.

让我们考虑一下组成我们组件的不同部分,以及它们应该做什么。

First, we have a collection of color swatches. We want to be able to pass a list of custom colors and display as swatches in the component. The first one should be selected by default, and the end user can select a new one by clicking it.

首先,我们有一些色板 。 我们希望能够传递自定义颜色的列表,并在组件中显示为色板。 默认情况下应选择第一个,最终用户可以通过单击选择一个新的。

Secondly, we have the color mode toggler. The end user should be able to switch between three modes: hexadecimal (default), RGB and HSL.

其次,我们有颜色模式切换器 。 最终用户应该能够在三种模式之间切换:十六进制(默认),RGB和HSL。

Finally, we have the color code output, where the end user can get the code for the currently selected color swatch. This code is a combination of the selected swatch and color mode. Thus, by default, it should display the first swatch as a hexadecimal value. When changing any of these, the code should update accordingly.

最后,我们有颜色代码输出 ,最终用户可以在其中获得当前所选颜色样本的代码。 此代码是所选色板和颜色模式的组合。 因此,默认情况下,它应将第一个色板显示为十六进制值。 更改其中任何一个时,代码应相应更新。

As you can see, we don’t go too deep into details; we don’t specify what the color mode labels should be, or what the active state looks like for the color swatches. We can make most of the small decisions on the fly, even when doing TDD. Yet, we’ve come from a simple definition of what the component should be, to a comprehensive set of specs to start from.

如您所见,我们并没有深入细节。 我们没有指定颜色模式标签应该是什么,或者颜色样本的活动状态是什么样。 即使在进行TDD时,我们也可以即时做出大多数小的决定。 但是,我们已经从对组件应该是什么的简单定义,到从开始的一整套规范

编写测试驱动的代码 (Write test-driven code)

First, you need to create a new Vue project with Vue CLI. You can check Build Your First Vue.js Component if you need a step by step guide.

首先,您需要使用Vue CLI创建一个新的Vue项目。 如果需要逐步指南,可以检查“ 构建您的第一个Vue.js组件”

During the scaffolding process, manually select features and make sure you check Unit testing. Pick Jest as your testing solution, and proceed until the project is created, dependencies are installed, and you’re ready to go.

在脚手架过程中,请手动选择功能并确保选中单元测试 。 选择Jest作为测试解决方案,然后继续进行,直到创建项目,安装依赖项并准备就绪为止。

We’ll need to use SVG files as components, so you also need to install the right loader for them. Install vue-svg-loader as a dev dependency, and add a rule for it in your vue.config.js file.

我们需要将SVG文件用作组件,因此您还需要为其安装正确的加载程序。 将vue-svg-loader安装为开发依赖项,并在vue.config.js文件中vue.config.js添加一条规则。

// vue.config.js

module.exports = {
  chainWebpack: config => {
    const svgRule = config.module.rule('svg')
    svgRule.uses.clear()
    svgRule.use('vue-svg-loader').loader('vue-svg-loader')
  }
}

This loader doesn’t play well with Jest by default, which causes tests to throw. To fix it, create a svgTransform.js file as documented on the website, and edit your jest.config.js as follows:

默认情况下,此加载器无法与Jest配合使用,这会导致测试抛出。 要对其进行修复,请按照网站上的记录创建一个svgTransform.js文件, jest.config.js如下所示编辑jest.config.js

// svgTransform.js

const vueJest = require('vue-jest/lib/template-compiler')

module.exports = {
  process(content) {
    const { render } = vueJest({
      content,
      attrs: {
        functional: false
      }
    })
    
    return `module.exports = { render: ${render} }`
  }
}

// jest.config.js

module.exports = {
  // ...
  transform: {
    // ...
    '.+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
    '^.+\\.svg$': '<rootDir>/svgTransform.js'
  },
  // ...
}

Note that we’ve removed “svg” from the first regular expression (the one that gets transformed with jest-transform-stub). This way, we ensure SVGs get picked up by svgTransform.js.

请注意,我们已经从第一个正则表达式(使用jest-transform-stub正则表达式)中删除了“ svg”。 这样,我们确保svgTransform.js可以接收svgTransform.js

Additionally, you need to install color-convert as a dependency. We’ll need it both in our code and in our tests later on.

此外,您需要安装颜色转换作为依赖项。 稍后我们的代码和测试中都需要它。

Don’t serve the project yet. We’re going to write tests and rely on them passing or not to move on. We don’t want to control whether what we build works by testing it visually in the browser, nor being distracted by how it looks.

还不为这个项目服务 。 我们将编写测试,并依靠测试通过或不继续进行。 我们不想通过在浏览器中进行可视化测试来控制所构建的内容是否起作用,也不希望被其外观分散注意力。

Instead, open your project and create a new ColorPicker.vue single-file component in the src/components/ directory. In tests/unit/, create its associated spec file.

而是打开您的项目,并在src/components/目录中创建一个新的ColorPicker.vue单文件组件。 在tests/unit/ ,创建其关联的规范文件。

<!-- ColorPicker.vue -->

<template>
  <div></div>
</template>

<script>
export default {}
</script>

<style>
</style>

// ColorPicker.spec.js

import { shallowMount } from '@vue/test-utils'
import ColorPicker from '@/components/ColorPicker'

describe('ColorPicker', () => {
  // let's do this!
})

In your terminal, execute the following command to run tests:

在您的终端中,执行以下命令以运行测试:

npm run test:unit --watchAll

For now, you should get an error because you don’t yet have tests. Don’t worry though; we’ll fix this shortly ? Note the usage of the --watchAll flag in the command: Jest is now watching your files. This way, you won’t have to re-run test by hand.

现在,您应该会得到一个错误,因为您还没有测试。 不过不要担心; 我们会尽快解决吗? 请注意命令中-watchAll标志的用法:Jest现在正在监视您的文件。 这样,您就不必手动重新运行测试。

TDD goes in 3 stages:

TDD分为三个阶段:

  1. Red: you write a test that describes an expected behavior, then you run it, ensuring it fails.

    红色 :编写描述预期行为的测试,然后运行该测试以确保其失败。

  2. Green: you write the dumbest, most straightforward code you can to make the test pass.

    绿色 :您编写最简单,最直接的代码即可通过测试。

  3. Refactor: you refactor the code to make it right.

    重构 :重构代码使其正确。

步骤1:红色 (Step 1: Red)

Time to write our first test! We’ll start with the color swatches. For clarity, we’ll wrap all tests for each distinct element in their own suite, using a describe block.

是时候编写我们的第一个测试了! 我们将从色板开始。 为了清楚起见,我们将使用describe块将所有测试包含在其各自套件中的每个不同元素中。

First, we want to make sure that the component displays each color that we provide as an individual swatch. We would pass those as props, in the form of an array of hexadecimal strings. In the component, we would display the list as an unordered list, and assign the background color via a style attribute.

首先,我们要确保组件显示作为单独色板提供的每种颜色。 我们将以十六进制字符串数组的形式将其作为道具传递。 在组件中,我们将列表显示为无序列表,并通过style属性分配背景色。

import { shallowMount } from '@vue/test-utils'
import ColorPicker from '@/components/ColorPicker'
import convert from 'color-convert'

let wrapper = null

const propsData = {
  swatches: ['e3342f', '3490dc', 'f6993f', '38c172', 'fff']
}

beforeEach(() => (wrapper = shallowMount(ColorPicker, { propsData })))
afterEach(() => wrapper.destroy())

describe('ColorPicker', () => {
  describe('Swatches', () => {
    test('displays each color as an individual swatch', () => {
      const swatches = wrapper.findAll('.swatch')
      propsData.swatches.forEach((swatch, index) => {
        expect(swatches.at(index).attributes().style).toBe(
          `background: rgb(${convert.hex.rgb(swatch).join(', ')})`
        )
      })
    })
  })
})

We mounted our ColorPicker component and wrote a test that expects to find items with a background color matching the colors passed as props. This test is bound to fail: we currently have nothing in ColorPicker.vue. If you look at your terminal, you should have an error saying that no item exists at 0. This is great! We just passed the first step of TDD with flying colors.

我们安装了ColorPicker组件并编写了一个测试,该测试希望找到背景颜色与作为道具传递的颜色匹配的项目。 该测试注定会失败ColorPicker.vue目前没有任何内容。 如果查看终端,应该有一个错误,说0处没有项目。这太好了! 我们刚刚通过了TDD的第一步,色彩鲜艳。

步骤2:绿色 (Step 2: Green)

Our test is failing; we’re on the right track. Now, time to make it pass. We’re not much interested in writing working or smart code at this point, all we want is to make Jest happy. Right now, Vue Test Utils complains about the fact that we don’t event have no item at index 0.

我们的测试失败了; 我们走在正确的轨道上。 现在,是时候让它过去了。 目前,我们对编写工作代码或智能代码并不感兴趣,我们所要做的就是让Jest开心。 现在,Vue Test Utils抱怨这样一个事实,我们没有在索引0处没有项目。

[vue-test-utils]: no item exists at 0

The simplest thing we can do to make that error go away is to add an unordered list with a swatch class on the list item.

要使该错误消失,最简单的方法是在列表项上添加带有swatch类的无序列表。

<template>
  <div class="color-picker">
    <ul class="swatches">
      <li class="swatch"></li>
    </ul>
  </div>
</template>

Jest still complains but the error has changed:

Jest仍然抱怨,但错误已更改:

Expected value to equal:
  "background: rgb(227, 52, 47);"
Received:
  undefined

This makes sense; the list item doesn’t have a style attribute. The simplest thing we can do about it is to hardcode the style attribute. This isn’t what we want in the end, but, we aren’t concerned about it yet. What we want is for our test to go green.

这很有道理; 列表项没有style属性。 我们可以做的最简单的事情就是对style属性进行硬编码。 这最终不是我们想要的,但是我们还不关心它。 我们想要的是使测试变得绿色

We can therefore hardcode five list items with the expected style attributes:

因此,我们可以使用所需的样式属性对五个列表项进行硬编码:

<ul class="swatches">
  <li class="swatch" style="background: rgb(227, 52, 47);"></li>
  <li class="swatch" style="background: rgb(52, 144, 220);"></li>
  <li class="swatch" style="background: rgb(246, 153, 63);"></li>
  <li class="swatch" style="background: rgb(56, 193, 114);"></li>
  <li class="swatch" style="background: rgb(255, 255, 255);"></li>
</ul>

The test should now pass.

测试现在应该通过。

步骤3:重构 (Step 3: Refactor)

At this stage, we want to rearrange our code to make it right, without breaking tests. In our case, we don’t want to keep the list items and their style attributes hardcoded. Instead, it would be better to receive swatches as a prop, iterate over them to generate the list items, and assign the colors as their background.

在这个阶段,我们希望重新排列代码以使其正确,而不会破坏测试。 在我们的情况下,我们不想保留列表项及其style属性的硬编码。 相反,最好将色板作为道具,对其进行迭代以生成列表项,然后将颜色分配为其背景。

<template>
  <div class="color-picker">
    <ul class="swatches">
      <li
        :key="index"
        v-for="(swatch, index) in swatches"
        :style="{ background: `#${swatch}` }"
        class="swatch"
      ></li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    swatches: {
      type: Array,
      default() {
        return []
      }
    }
  }
}
</script>

When tests re-run, they should still pass ? This means we’ve successfully refactored the code without affecting the output. Congratulations, you’ve just completed your first TDD cycle!

重新运行测试时,它们仍应通过? 这意味着我们已经成功地重构了代码而不影响输出。 恭喜,您已经完成了第一个TDD周期!

Now, before we go to the next test, let’s reflect a bit. You may be wondering:

现在,在进行下一个测试之前,让我们反思一下。 您可能想知道:

“Isn’t this a bit dumb? I knew the test would fail. Am I not wasting time by running it anyway, then hardcoding the right value, see the test pass, then make the code right? Can’t I go to the refactor step directly?”
“这有点愚蠢吗? 我知道测试会失败。 我是否仍在通过运行时间浪费时间,然后对正确的值进行硬编码,查看测试通过,然后正确编写代码,这是在浪费时间吗? 我不能直接进入重构步骤吗?”

It’s understandable that you’re feeling confused by the process. Yet, try to look at things from a different angle: the point here isn’t to prove that the test doesn’t pass. We know it won’t. What we want to look at is what our test expects, make them happy in the simplest possible way, and finally write smarter code without breaking anything.

对此过程感到困惑是可以理解的。 但是,请尝试从另一个角度看待问题:这里的要点并不是要证明测试没有通过。 我们知道不会。 我们想要查看的是测试所期望的结果 ,以最简单的方式使他们满意,并最终编写更智能的代码而不会破坏任何内容。

That’s the whole idea of test-driven development: we don’t write code to make things work, we write code to make tests pass. By reversing the relationship, we’re ensuring robust tests with a focus on the outcome.

那就是测试驱动开发的全部思想:我们不编写使工作正常的代码而是编写使测试通过的代码 。 通过扭转这种关系,我们可以确保以结果为重点的可靠测试。

我们正在测试什么? (What are we testing?)

Another question that may come to mind is how we’re deciding what to test. In Unit Test Your First Vue.js Component, we saw that we should only be testing the public API of our component, not the internal implementation. Strictly speaking, this means we should cover user interactions and props changes.

可能想到的另一个问题是我们如何决定要测试的内容 。 在单元测试您的第一个Vue.js组件中 ,我们看到我们应该只测试组件的公共API,而不是内部实现。 严格来说,这意味着我们应该涵盖用户交互道具更改

But is that all? For example, is it okay for the output HTML to break? Or for CSS class names to change? Are we sure nobody is relying on them? That you aren’t yourself?

但这就是全部吗? 例如,输出HTML可以中断吗? 还是要更改CSS类名? 我们确定没有人依赖他们吗? 你不是你自己吗?

Tests should give you confidence that you aren’t shipping broken software. What people can do with your program shouldn’t stop working the way they expect it to work. It can mean different things depending on the project and use case.

测试应该使您确信自己不会交付损坏的软件。 人们可以对您的程序执行的操作不应以他们期望的方式停止工作。 根据项目和用例,这可能意味着不同的事情。

For example, if you’re building this color panel as an open source component, your users are other developers who use it in their own projects. They’re likely relying on the class names you provide to style the component to their liking. The class names become a part of your public API because your users rely on them.

例如,如果您将此颜色面板构建为开源组件,则您的用户是在自己的项目中使用它的其他开发人员。 他们可能依赖于您提供的类名称来根据自己的喜好对组件进行样式设置。 类名称成为您的公共API的一部分,因为您的用户依赖它们。

In our case, we may not necessarily be making an open source component, but we have view logic that depends on specific class names. For instance, it’s important for active swatches to have an active class name, because we’ll rely on it to display a checkmark, in CSS. If someone changes this by accident, we want to know about it.

在我们的情况下,我们不一定要制作一个开源组件,但是我们有依赖于特定类名的视图逻辑。 例如,对于活动的色板来说,具有active类名很重要,因为我们将依靠它来在CSS中显示对勾。 如果有人不小心更改了此设置,我们想知道。

Testing scenarios for UI components highly depend on the use case and expectations. Whichever the case, what you need to ask yourself is do I care about this if it changes?

UI组件的测试方案高度取决于用例和期望。 无论哪种情况,您需要问自己的是,如果情况有所变化,我是否会在乎

下次测试 (Next tests)

测试色板 (Testing the swatches)

Let’s move on to the next test. We expect the first swatch of the list to be the one that’s selected by default. From the outside, this is something that we want to ensure keeps on working the same way. Users could, for instance, rely on the active class name to style the component.

让我们继续下一个测试。 我们希望列表中的第一个色板是默认选择的色板。 从外部看, 这是我们要确保以相同方式继续工作的方式 。 例如,用户可以依靠活动的类名来设计组件的样式。

test('sets the first swatch as the selected one by default', () => {
  const firstSwatch = wrapper.find('.swatch')
  expect(firstSwatch.classes()).toContain('active')
})

This test, too, should fail, as list items currently don’t have any classes. We can easily make this pass by adding the class on the first list item.

该测试也应该失败,因为列表项当前没有任何类。 我们可以通过在第一个列表项上添加类来轻松进行此传递。

<li
  :key="index"
  v-for="(swatch, index) in swatches"
  :style="{ background: `#${swatch}` }"
  class="swatch"
  :class="{ 'active': index === 0 }"
></li>

The test now passes; however, we’ve hardcoded the logic into the template. We can refactor that by externalizing the index onto which the class applies. This way, we can change it later.

现在测试通过了; 但是,我们已将逻辑硬编码到模板中。 我们可以通过外部化该类适用的索引来重构它。 这样,我们以后可以更改它。

<template>
  <!-- ... -->
  <li
    :key="index"
    v-for="(swatch, index) in swatches"
    :style="{ background: `#${swatch}` }"
    class="swatch"
    :class="{ active: index === activeSwatch }"
  ></li>
  <!-- ... -->
</template>

export default {
  // ...
  data() {
    return {
      activeSwatch: 0
    }
  }
}

This naturally leads us to our third test. We want to change the active swatch whenever the end user clicks it.

这自然会导致我们进行第三次测试。 我们希望在最终用户单击时更改活动的色板。

test('makes the swatch active when clicked', () => {
  const targetSwatch = wrapper.findAll('.swatch').at(2)
  targetSwatch.trigger('click')
  expect(targetSwatch.classes()).toContain('active')
})

For now, nothing happens when we click a swatch. However, thanks to our previous refactor, we can make this test go green and even skip the refactor step.

现在,当我们单击一个样本时,什么也没有发生。 但是,由于我们之前的重构,我们可以使此测试变为绿色,甚至跳过重构步骤。

<li
  :key="index"
  v-for="(swatch, index) in swatches"
  :style="{ background: `#${swatch}` }"
  class="swatch"
  :class="{ active: index === activeSwatch }"
  @click="activeSwatch = index"
></li>

This code makes the test pass and doesn’t even need a refactor. This is a fortunate side-effect of doing TDD: sometimes, the process leads to either writing new tests that either don’t need refactors, or even that pass right away.

此代码使测试通过,甚至不需要重构。 这是进行TDD的幸运的副作用 :有时,该过程会导致编写不需要重构甚至立即通过的新测试。

Active swatches should show a checkmark. We’ll add it now without writing a test: instead, we’ll control their visibility via CSS later. This is alright since we’ve already tested how the active class applies.

活动色板应显示一个选中标记。 我们现在将其添加而无需编写测试 :相反,稍后我们将通过CSS控制其可见性。 没关系,因为我们已经测试了active类的应用方式。

First, create a checkmark.svg file in src/assets/.

首先,在src/assets/创建一个checkmark.svg文件。

<svg viewBox="0 0 448.8 448.8">
  <polygon points="142.8 323.9 35.7 216.8 0 252.5 142.8 395.3 448.8 89.3 413.1 53.6"/>
</svg>

Then, import it in the component.

然后,将其导入组件中。

import CheckIcon from '@/assets/check.svg'

export default {
  // ...
  components: { CheckIcon }
}

Finally, add it inside the list items.

最后,将其添加到列表项中。

<li ... >
  <check-icon />
</li>

Good! We can now move on to the next element of our component: the color mode.

好! 现在,我们可以进入组件的下一个元素: color mode

测试色彩模式 (Testing the color mode)

Let’s now implement the color mode toggler. The end user should be able to switch between hexadecimal, RGB and HSL. We’re defining these modes internally, but we want to ensure they render correctly.

现在,让我们实现颜色模式切换器。 最终用户应该能够在十六进制,RGB和HSL之间切换。 我们在内部定义这些模式,但是我们要确保它们能正确渲染。

Instead of testing button labels, we’ll rely on class names. It makes our test more robust, as we can easily define a class name as part of our component’s contract. However, button labels should be able to change.

而不是测试按钮标签, 我们将依靠类名 。 由于我们可以轻松地将类名定义为组件合同的一部分,因此它使测试更加健壮。 但是,按钮标签应该可以更改。

Now you may be tempted to check for these three specific modes, but that would make the test brittle. What if we change them? What if we add one, or remove one? That would still be the same logic, yet the test would fail, forcing us to go and edit it.

现在,您可能很想检查这三种特定模式,但这会使测试变脆。 如果我们更改它们怎么办? 如果我们添加一个或删除一个怎么办? 那仍然是相同的逻辑,但是测试将失败,迫使我们继续对其进行编辑。

One solution could be to access the component’s data to iterate on the modes dynamically. Vue Test Utils lets us do that through the vm property, but again, this tightly couples our test with the internal implementation of the modes. If tomorrow, we decided to change the way we define modes, the test would break.

一种解决方案是访问组件的数据以动态地迭代模式。 Vue Test Utils使我们可以通过vm属性来执行此操作,但是,这再次将我们的测试与模式的内部实现紧密结合在一起。 如果明天我们决定更改定义模式的方式,则测试将失败。

Another solution is to keep going with black box testing and only expect the class name to match a given pattern. We don’t care that it’s color-mode-hex, color-mode-hsl or color-mode-xyz, as long as it looks like what we expect from the outside. Jest lets us do that with regular expression matchers.

另一个解决方案是继续进行黑盒测试,只希望类名与给定的模式匹配。 我们不在乎它是color-mode-hexcolor-mode-hsl还是color-mode-xyz ,只要看起来像我们期望的那样即可。 Jest让我们可以使用正则表达式匹配器做到这一点。

// ...
describe('Color model', () => {
  test('displays each mode as an individual button', () => {
    const buttons = wrapper.findAll('.color-mode')
    buttons.wrappers.forEach(button => {
      expect(button.classes()).toEqual(
        expect.arrayContaining([expect.stringMatching(/color-mode-\w{1,}/)])
      )
    })
  })
})

Here, we’re expecting elements with a class that follows the pattern “color-mode-“ + any word character (in ECMAScript, any character within [a-zA-Z_0-9]). We could add or remove any mode we want, and the test would still be valid.

在这里,我们期望元素的类遵循“ color-mode-”模式+任何单词字符(在ECMAScript中, [a-zA-Z_0-9]任何字符)。 我们可以添加或删除所需的任何模式,并且测试仍然有效。

Naturally, right now, the test should fail, as there are no buttons with class color-mode yet. We can make it pass by hardcoding them in the component.

很自然,现在,测试应该会失败,因为还没有按钮具有类color-mode 。 我们可以通过在组件中对它们进行硬编码来使其通过。

<div class="color-modes">
  <button class="color-mode color-mode-hex"></button>
  <button class="color-mode color-mode-rgb"></button>
  <button class="color-mode color-mode-hsl"></button>
</div>

We can now refactor this code by adding the modes as private data in our component and iterate over them.

现在,我们可以通过将模式作为私有数据添加到组件中并对其进行迭代来重构此代码。

<template>
  <!-- ... -->
  <div class="color-modes">
    <button
      :key="index"
      v-for="(mode, index) in colorModes"
      class="color-mode"
      :class="`color-mode-${mode}`"
    >{{ mode }}</button>
  </div>
  <!-- ... -->
</template>

export default {
  // ...
  data() {
    return {
      activeSwatch: 0,
      colorModes: ['hex', 'rgb', 'hsl']
    }
  }
}

Good! Let’s move on.

好! 让我们继续。

As with the swatches, we want the first mode to be set as active. We can copy the test we wrote and adapt it to this new use case.

与色板一样,我们希望将第一个模式设置为活动状态。 我们可以复制我们编写的测试,并将其调整为适应新的用例。

test('sets the first mode as the selected one by default', () => {
  const firstButton = wrapper.find('.color-mode')
  expect(firstButton.classes()).toContain('active')
})

We can make this test pass by manually adding the class on the first list item.

我们可以通过在第一个列表项上手动添加类来使此测试通过。

<button
  :key="index"
  v-for="(mode, index) in colorModes"
  class="color-mode"
  :class="[{ active: index === 0 }, `color-mode-${mode}`]"
>{{ mode }}</button>

Finally, we can refactor by externalizing the index onto which the class applies.

最后,我们可以通过外部化该类适用的索引来进行重构。

<template>
  <!-- ... -->
  <button
    :key="index"
    v-for="(mode, index) in colorModes"
    class="color-mode"
    :class="[{ active: index === activeMode }, `color-mode-${mode}`]"
  >{{ mode }}</button>
  <!-- ... -->
</template>

export default {
  // ...
  data() {
    return {
      activeSwatch: 0,
      activeMode: 0,
      colorModes: ['hex', 'rgb', 'hsl']
    }
  }
}

We need to change the active mode whenever the end user clicks the associated button, as with the swatches.

每当最终用户单击相关按钮时,我们都需要更改活动模式,就像色板一样。

test('sets the color mode button as active when clicked', () => {
  const targetButton = wrapper.findAll('.color-mode').at(2)
  targetButton.trigger('click')
  expect(targetButton.classes()).toContain('active')
})

We can now add a @click directive as we did with the swatches, and make the test go green without having to refactor.

现在,我们可以像对待色板一样添加@click指令,并使测试变为绿色,而无需重构。

<button
  :key="index"
  v-for="(mode, index) in colorModes"
  class="color-mode"
  :class="[{ active: index === activeMode }, `color-mode-${mode}`]"
  @click="activeMode = index"
>{{ mode }}</button>
测试颜色代码 (Testing the color code)

Now that we’re done testing the swatches and color code, we can move on to the third and final element of our color picker: the color code. What we display in there is a combination of the other two: the selected swatch defines the color we should display, and the selected mode determines how to display it.

既然我们已经完成了色样和颜色代码的测试,那么我们就可以继续进行颜色选择器的第三个也是最后一个元素: 颜色代码 。 我们在其中显示的内容是其他两种的组合:选定的色板定义了我们应该显示的颜色,而选定的模式决定了如何显示它。

First, we want to make sure we initially display the default swatch in the default mode. We have the information to build this since we’ve implemented the swatches and the color mode.

首先,我们要确保最初在默认模式下显示默认色板。 由于我们已经实现了色板和颜色模式,因此我们拥有构建此模型的信息。

Let’s start with a (failing) test.

让我们从(失败)测试开始。

describe('Color code', () => {
  test('displays the default swatch in the default mode', () => {
    expect(wrapper.find('.color-code').text()).toEqual('#e3342f')
  })
})

Now, let’s make this pass by hardcoding the expected result in the component.

现在,让我们通过在组件中对预期结果进行硬编码来实现此目的。

<div class="color-code">#e3342f</div>

Good! Time to refactor. We have a raw color in hexadecimal mode, and we’re willing to output it in hexadecimal format. The only difference between our input and output values is that we want to prepend the latter with a hash character. The easiest way of doing so with Vue is via a computed property.

好! 该重构了。 我们在十六进制模式下具有原始颜色,并且我们愿意以十六进制格式输出它。 输入值和输出值之间的唯一区别是,我们要在后者前面加上一个哈希字符。 Vue最简单的方法是通过computed属性。

<template>
  <!-- ... -->
  <div class="color-code">{{ activeCode }}</div>
  <!-- ... -->
</template>

export default {
  // ...
  computed: {
    activeCode() {
      return `#${this.swatches[this.activeSwatch]}`
    }
  }
}

This should keep the test green. However, there’s an issue with this computed property: it only works for hexadecimal values. It should keep on working when we change the color, but not when we change the mode. We can verify this with another test.

这应该使测试保持绿色。 但是,此计算属性存在一个问题:它仅适用于十六进制值。 当我们更改颜色时,它应该继续工作,但是当我们更改模式时,它就不会继续工作。 我们可以通过另一项测试来验证。

test('displays the code in the right mode when changing mode', () => {
  wrapper.find('.color-mode-hsl').trigger('click')
  expect(wrapper.find('.color-code').text()).toEqual('2°, 76%, 54%')
})

Here, we’ve changed to HSL mode, but we’re still getting the hexadecimal output. We need to refactor our code so that our activeCode computed property is not only aware of the current color, but also the current color mode. One way we can achieve this is to create computed properties for each mode and proxy them through activeCode based on the selected mode.

在这里,我们已更改为HSL模式,但仍在获取十六进制输出。 我们需要重构代码,以便我们的activeCode计算属性不仅知道当前颜色,而且知道当前颜色模式。 我们可以实现此目的的一种方法是为每种模式创建计算属性,然后根据所选模式通过activeCode代理它们。

First, we should simplify access to the current color and mode. Right now, we need to do an array lookup, which is repetitive and makes the code hard to read. We can use computed properties to wrap that logic.

首先,我们应该简化对当前颜色和模式的访问。 现在,我们需要进行数组查找,这是重复性的并使代码难以阅读。 我们可以使用计算属性来包装该逻辑。

export default {
  // ...
  computed: {
    // ...
    activeColorValue() {
      return this.swatches[this.activeSwatch]
    },
    activeModeValue() {
      return this.colorModes[this.activeMode]
    }
  }
}

As you can see, we’re not writing tests for these computed properties, as they aren’t part of our public API. We’ll use them later in our dedicated color mode computed properties, which themselves will be proxied in activeCode, which we’re testing in our “Color code” suite. All we care about is that the color code renders as expected so that the user can rely on them. How we get there are implementation details that we need to be able to change if need be.

如您所见,由于这些计算属性不是我们的公共API的一部分,因此我们不会为其编写测试。 稍后,我们将在专用的颜色模式计算属性中使用它们,这些属性将在activeCode进行代理,我们将在“ Color code”套件中对其进行测试。 我们只关心颜色代码按预期呈现,以便用户可以依赖它们。 我们如何获得实现细节,我们需要根据需要更改这些实现细节。

We can now write our dedicated computed properties for each mode. We’ll map their name onto the ones in colorModes, so we can do an array lookup later in activeCode to return the right one.

现在,我们可以为每种模式编写专用的计算属性。 我们将它们的名称映射到colorModes的名称上,以便稍后可以在activeCode进行数组查找以返回正确的名称。

For the hexadecimal output, we can externalize what we currently have in activeCode and refactor it using activeColorValue.

对于十六进制输出,我们可以外部化activeCode当前拥有的内容,并使用activeColorValue对其进行重构。

export default {
  // ...
  computed: {
    // ...
    hex() {
      return `#${this.activeColorValue}`
    }
  }
}

Now, let’s modify activeCode so it proxies the right computed property depending on the active mode.

现在,让我们修改activeCode ,使其根据活动模式代理正确的计算属性。

export default {
  // ...
  computed: {
    // ...
    activeCode() {
      return this[this.activeModeValue]
    }
  }
}

This still shouldn’t make our latest test pass, since we haven’t written a computed property for it. However, our test that checks if the default mode renders correctly is still passing, which is a good sign we’re on the right track.

这仍然不应该使我们的最新测试通过,因为我们还没有为其编写计算属性。 但是,我们检查默认模式能否正确渲染的测试仍在通过中,这是我们走上正确道路的好兆头。

We now want to write a computed property that returns the color output in HSL mode. For this, we’ll use color-convert, an npm package that lets us convert colors in many different modes. We’ve already been using it in our tests, so we don’t have to reinstall it.

现在,我们要编写一个计算属性,该属性返回HSL模式下的颜色输出。 为此,我们将使用color-convert ,这是一个npm软件包,可让我们以许多不同的模式转换颜色。 我们已经在测试中使用了它,因此我们不必重新安装它。

import convert from 'color-convert'

export default {
  // ...
  computed: {
    // ...
    hsl() {
      const hslColor = convert.hex.hsl(this.activeColorValue)
      return `${hslColor[0]}°, ${hslColor[1]}%, ${hslColor[2]}%`
    }
  }
}

Great, our test passes! We can now finish this up adding the missing RGB mode.

太好了,我们的测试通过了! 现在,我们可以添加缺少的RGB模式来完成此操作。

Yet, as you can see, we’re currently not testing the output of our color computed properties in isolation, but through other tests. To make things cleaner, we could decouple that logic from the component, import it as a dependency, and test it separately. This has several benefits:

但是,正如您所看到的,我们目前不是在单独测试色彩计算属性的输出,而是通过其他测试。 为了使事情更简洁,我们可以将该逻辑与组件分离,将其作为依赖项导入,然后分别进行测试。 这有几个好处:

  • it keeps the component from growing every time we want to add a color mode,

    每次我们要添加颜色模式时,它都使组件不再增长,
  • it keeps domains separated: the component focuses on its own view logic, and the color modes utility takes care of testing each mode exhaustively.

    它使域保持分离:该组件专注于其自己的视图逻辑,并且颜色模式实用程序负责详尽测试每种模式。

First, create a new color.js file in the src/utils/ directory, and a matching spec file in tests/unit/.

首先,在src/utils/目录中创建一个新的color.js文件,并在tests/unit/创建一个匹配的spec文件。

// color.spec.js

import { rgb, hex, hsl } from '@/utils/color'

// color.js

import convert from 'color-convert'

export const rgb = () => {}

export const hex = () => {}

export const hsl = () => {}

We can use TDD to test those three functions and make sure they always return the expected value. We can extract the logic we had in our Vue component for the last two, and write the RGB function from scratch.

我们可以使用TDD来测试这三个函数,并确保它们始终返回期望值。 我们可以提取出后两个组件在Vue组件中拥有的逻辑,然后从头开始编写RGB函数。

For the sake of brevity, we’ll cover all three tests at once, but the process remains the same.

为了简洁起见,我们将同时涵盖所有三个测试,但是过程保持不变。

import { rgb, hex, hsl } from '@/utils/color'

const color = 'e3342f'

describe('color', () => {
  test('returns the color into RGB notation', () => {
    expect(rgb(color)).toBe('227, 52, 47')
  })
  test('returns the color into hexadecimal notation', () => {
    expect(hex(color)).toBe('#e3342f')
  })
  test('returns the color into HSL notation', () => {
    expect(hsl(color)).toBe('2°, 76%, 54%')
  })
})

We now have three failing tests. The first thing we can do is to return hardcoded values to go green.

现在,我们有三个失败的测试。 我们可以做的第一件事是将硬编码的值返回绿色。

export const rgb = () => '227, 52, 47'

export const hex = () => '#e3342f'

export const hsl = () => '2°, 76%, 54%'

Now, we can start refactoring by migrating the code from our Vue component.

现在,我们可以通过迁移Vue组件中的代码来开始重构。

export const hex = () => `#${color}`

export const hsl = color => {
  const hslColor = convert.hex.hsl(color)
  return `${hslColor[0]}°, ${hslColor[1]}%, ${hslColor[2]}%`
}

Finally, we can implement our rgb function.

最后,我们可以实现rgb函数。

export const rgb = color => convert.hex.rgb(color).join(', ')

All tests should stay green!

所有测试应保持绿色!

We can now use the color utilities in our Vue component and refactor it a bit. We no longer need to import color-convert in the component, nor do we need dedicated computed properties for each mode, or even for getting the active color and mode values. All we need to keep is activeCode, where we can store all the necessary logic.

现在,我们可以在Vue组件中使用color实用程序,并对其进行一些重构。 我们不再需要在组件中导入color-convert ,也不需要为每种模式甚至甚至为获取活动的颜色和模式值都需要专用的计算属性。 我们需要保留的是activeCode ,我们可以在其中存储所有必要的逻辑。

This is a good example where doing black box testing helps us: we’ve been focusing on testing the public API; thus we can refactor the internals of our component without breaking the tests. Removing properties like activeColorValue or hex doesn’t matter, because we were never testing them directly.

这是一个很好的例子,在其中进行黑匣子测试可以帮助我们:我们一直专注于测试公共API; 因此, 我们可以在不破坏测试的情况下重构组件的内部 。 删除诸如activeColorValuehex类的属性并不重要,因为我们从未直接对其进行测试。

// ...
import { rgb, hex, hsl } from '@/utils/color'

const modes = { rgb, hex, hsl }

export default {
  // ...
  computed: {
    activeCode() {
      const activeColor = this.swatches[this.activeSwatch]
      const activeMode = this.colorModes[this.activeMode]
      return modes[activeMode](activeColor)
    }
  }
}

We now have much terser code in our component, and better domain separation, while still respecting the component’s contract.

现在,我们的组件中有很多代码,更好的域分离,同时仍然尊重组件的合同。

Finally, we can implement a missing test: the one that ensures the color code changes whenever we click a new swatch. This should already go green, but it’s still essential for us to write it, so we can know about it if it breaks.

最后,我们可以实施一项缺失的测试:该测试确保每次单击新色板时颜色代码都会更改。 这本应该已经绿色了,但是对我们来说,编写它仍然是必不可少的,因此我们可以知道它是否破裂。

test('displays the code in the right color when changing color', () => {
  wrapper
    .findAll('.swatch')
    .at(2)
    .trigger('click')
  expect(wrapper.find('.color-code').text()).toEqual('#f6993f')
})

And we’re done! We just built a fully functional Vue component using TDD, without relying on browser output, and our tests are ready.

我们完成了! 我们只是使用TDD构建了一个功能齐全的Vue组件,而无需依赖浏览器输出, 并且我们的测试已经准备就绪

视觉控制 (Visual control)

Now that our component is ready, we can see how it looks and play with it in the browser. This allows us to add the CSS and ensure we didn’t miss out on anything.

现在我们的组件已经准备就绪,我们可以在浏览器中看到它的外观和使用方式。 这使我们能够添加CSS并确保我们不会错过任何东西。

First, mount the component into the main App.vue file.

首先,将组件安装到主App.vue文件中。

<!-- App.vue -->

<template>
  <div id="app">
    <color-picker :swatches="['e3342f', '3490dc', 'f6993f', '38c172', 'fff']"/>
  </div>
</template>

<script>
import ColorPicker from '@/components/ColorPicker'

export default {
  name: 'app',
  components: {
    ColorPicker
  }
}
</script>

Then, run the app by executing the following script, and open it in your browser at http://localhost:8080/.

然后,通过执行以下脚本来运行该应用程序,然后在浏览器中的http://localhost:8080/其打开。

npm run serve

You should see your color picker! It doesn’t look like much for now, but it works. Try clicking colors and change the color mode; you should see the color code change.

您应该看到自己的拾色器! 现在看起来还不多,但是可以用。 尝试点击颜色并更改颜色模式; 您应该会看到颜色代码的变化。

To see the component with proper styling, add the following CSS between the style tags:

要查看具有正确样式的组件,请在style标签之间添加以下CSS:

.color-picker {
  background-color: #fff;
  border: 1px solid #dae4e9;
  border-radius: 0.125rem;
  box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);
  color: #596a73;
  font-family: BlinkMacSystemFont, Helvetica Neue, sans-serif;
  padding: 1rem;
}

.swatches {
  color: #fff;
  display: flex;
  flex-wrap: wrap;
  list-style: none;
  margin: -0.25rem -0.25rem 0.75rem;
  padding: 0;
}

.swatch {
  border-radius: 0.125rem;
  cursor: pointer;
  height: 2rem;
  margin: 0.25rem;
  position: relative;
  width: 2rem;
}

.swatch::after {
  border-radius: 0.125rem;
  bottom: 0;
  box-shadow: inset 0 0 0 1px #dae4e9;
  content: '';
  display: block;
  left: 0;
  mix-blend-mode: multiply;
  position: absolute;
  right: 0;
  top: 0;
}

.swatch svg {
  display: none;
  color: #fff;
  fill: currentColor;
  margin: 0.5rem;
}

.swatch.active svg {
  display: block;
}

.color-modes {
  display: flex;
  font-size: 1rem;
  letter-spacing: 0.05rem;
  margin: 0 -0.25rem 0.75rem;
}

.color-mode {
  background: none;
  border: none;
  color: #9babb4;
  cursor: pointer;
  display: block;
  font-weight: 700;
  margin: 0 0.25rem;
  padding: 0;
  text-transform: uppercase;
}

.color-mode.active {
  color: #364349;
}

.color-code {
  border: 1px solid #dae4e9;
  border-radius: 0.125rem;
  color: #364349;
  text-transform: uppercase;
  padding: 0.75rem;
}

You should see something like this:

您应该会看到以下内容:

And we’re done!

我们完成了!

事后思考 (Afterthoughts)

我们该怎样改进这个? (How can we improve this?)

For now, we have a robust test suite. Even though we don’t have 100% coverage, we can feel confident with our component going out in the wild, and evolving over time. There are still a couple of things we could improve though, depending on the use case.

目前,我们有一个强大的测试套件。 即使我们没有100%的覆盖率,我们也对组件的狂野使用和不断发展感到自信。 但是,根据用例,仍有一些事情可以改进。

First, you may notice that when clicking the white swatch, the checkmark doesn’t show up. That’s not a bug, rather a visual issue: the checkmark is there, but we can’t see it because it’s white on white. You could add a bit of logic to fix this: when a color is lighter than a certain threshold (let’s say 90%), you could add a light class on the swatch. This would then let you apply some specific CSS and make the checkmark dark.

首先,您可能会注意到,单击白色色板时,复选标记没有显示。 那不是错误,而是视觉问题:复选标记已存在,但我们看不到它,因为它是白色的。 您可以添加一些逻辑来解决此问题:当颜色浅于某个阈值(例如90%)时,您可以在色板上添加一个light类别。 然后,这将使您可以应用一些特定CSS并使选中标记变黑。

Fortunately, you already have all you need: the color-converter package can help you determine whether a color is light (with the HSL utilities), and you already have a color utility module to store that logic and test it in isolation. To see what the finished code could look like, check out the project’s repository on GitHub.

幸运的是,您已经拥有了所需的一切: color-converter程序包可以帮助您确定颜色是否是浅色(使用HSL实用程序),并且您已经有了一个color实用程序模块来存储该逻辑并进行单独测试。 要查看完成的代码是什么样子,请查看GitHub上的项目存储库。

We could also reinforce the suite by adding a few tests to make sure some expected classes are there. This doesn’t test actual logic, but would still be particularly useful if someone was relying on those class names to style the component from the outside. Again, everything depends on your use case: test what shouldn’t change without you knowing, don’t only add tests for the sake of it.

我们还可以通过添加一些测试以确保其中存在一些预期的类来增强套件。 这并不能测试实际的逻辑,但是如果有人依靠这些类名从外部对组件进行样式设置,它仍然特别有用。 同样,一切都取决于您的用例:测试在不知情的情况下不应更改的内容,不要仅仅为了它而添加测试。

我们学到了什么? (What did we learn?)

There are several lessons to learn from this TDD experiment. It brings a lot to the table but also highlights a few challenges that we should be aware of.

从该TDD实验中可以学到一些教训。 它带来了很多好处,但也突出了我们应该注意的一些挑战。

First, TDD is a fantastic way to write robust tests, not too many and not too few. Have you ever finished a component, moved on to tests and thought “where do I even start?”? Looking at finished code and figuring out what to test is hard. It’s tempting to get it done quickly, overlook some critical parts and end up with an incomplete test suite. Or you can adopt a defensive approach and test everything, risking to focus on implementation details and writing brittle tests.

首先,TDD是一种编写健壮测试绝妙方法 ,数量不宜过多。 您是否曾经完成过一个组件,继续进行测试并想到“我什至从哪里开始?” ? 查看完成的代码并弄清楚要测试什么很难。 试图快速完成工作,忽略一些关键部分并最终获得不完整的测试套件是很诱人的。 或者,您可以采取防御性方法来测试所有内容,冒着将精力集中在实现细节上并编写易碎测试的风险。

Adopting TDD for developing UI components helps us focus on exactly what to test by defining, before writing any line of code, if this is part of the contract or not.

采用TDD开发UI组件可以帮助我们专注于要测试的内容,方法是在编写任何代码行之前定义是否属于合同内容,然后再进行定义

Secondly, TDD encourages refactors, leading to better software design. When you’re writing tests after coding, you’re usually no longer in a refactoring dynamic. You can fix your code if you find issues while testing, but at this stage, you’re most likely done with the implementation. This separation between writing code and writing test is where lies the issue.

其次, TDD鼓励重构,从而改善软件设计 。 当您在编码后编写测试时,通常不再需要进行重构。 如果在测试时发现问题,则可以修复代码,但是在此阶段,很可能已经完成了实现。 编写代码和编写测试之间的这种分离是问题所在。

With TDD, you’re creating a deeper connection between code and tests, with a strong focus on making the public API reliable. Implementation comes right after you’ve guaranteed the outcome. This is why the green step is critical: you first need your test to pass, then ensure it never breaks. Instead of implementing your way to a working solution, you’re reversing the relationship, focusing on the contract first, and allowing the implementation to remain disposable. Because refactoring comes last, and you’ve established the contract, you now have mental space to make things right, clean some code, adopt a better design, or focus on performance.

借助TDD, 您可以在代码和测试之间建立更深的联系,并将重点放在使公共API可靠上 。 在保证结果之后,即可立即实施。 这就是绿色步骤至关重要的原因:您首先需要通过测试,然后确保它永不中断。 您不是在逆转工作关系,而是颠倒了这种关系,首先关注合同,并允许实施保持一次性状态。 因为重构是最后的事情,并且您已经建立了合同,所以您现在有足够的空间来做正确的事情,清理一些代码,采用更好的设计或关注性能。

It’s worth noting that TDD is much easier to follow with specs. When you already have a clear overview of everything the component should do, you can translate those specifications into tests. Some teams use frameworks like ATDD (acceptance test–driven development), where the involved parties develop specifications from a business perspective. The final specs, or acceptance tests, are a perfect base to write tests following TDD.

值得注意的是, TDD遵循规范要容易得多 。 当您已经清楚地了解了组件应做的所有事情时,可以将这些规范转换为测试。 一些团队使用诸如ATDD (验收测试驱动的开发)之类的框架,由参与方从业务角度制定规范。 最终规格或验收测试是在TDD之后编写测试的理想基础。

On the other hand, going with TDD to test UI components can be difficult at first, and require some prior knowledge before diving into it. For starters, you need to have good knowledge of your testing libraries so that you can write reliable assertions. Look at the test we wrote with a regular expression: the syntax is not the most straightforward. If you don’t know the library well, it’s easy to write a test that fails for the wrong reasons, which would end up hindering the whole TDD process.

另一方面,使用TDD来测试UI组件一开始可能很困难,并且在深入研究之前需要先验知识。 首先, 您需要对测试库有充分的了解,以便可以编写可靠的断言。 看一下我们用正则表达式编写的测试:语法不是最简单的。 如果您不太了解该库,则很容易编写一个由于错误原因而失败的测试,而这最终会阻碍整个TDD流程。

Similarly, you need to be aware of some details regarding the values you expect; otherwise, you could end up battling with your tests and doing some annoying back-and-forths. On that matter, UI components are more challenging than renderless libraries, because of the various ways the DOM specifications can be implemented.

同样,您需要了解一些有关期望值的细节; 否则,您最终可能会与测试作斗争,并进行一些令人讨厌的反复试验。 在这一点上,由于可以实现DOM规范的各种方式,UI组件比无渲染库更具挑战性。

Take the first test of our suite for example: we’re testing background colors. However, even though we’re passing hexadecimal colors, we’re expecting RGB return values. That’s because Jest uses jsdom, a Node.js implementation of the DOM and HTML standards. If we were running our tests in a specific browser, we might have a different return value. This can be tricky when you’re testing different engines. You may have to seek some more advanced conversion utilities or use environment variables to handle the various implementations.

以我们套件的第一次测试为例:我们正在测试背景色。 但是,即使我们传递十六进制颜色,我们也期望RGB返回值。 这是因为Jest使用jsdom ,它是DOM和HTML标准的Node.js实现。 如果我们在特定的浏览器中运行测试,则返回值可能会有所不同。 当您测试不同的引擎时,这可能会很棘手。 您可能必须寻求一些更高级的转换实用程序或使用环境变量来处理各种实现。

这值得么? (Is it worth it?)

If you made it this far, you’ve probably realized that TDD demands time. This article itself is over 6,000 words! This can be a bit scary if you’re used to faster development cycles, and probably looks impossible if you’re often working under pressure. However, it’s important to bust the myth that TDD would somehow double development time for little return on investment, because this is entirely false.

如果到此为止,您可能已经意识到TDD需要时间 。 这篇文章本身超过6,000字! 如果您习惯了更快的开发周期,这可能会有点吓人,如果您经常在压力下工作,这看起来似乎是不可能的。 但是,重要的是要打破这样一个神话:TDD会以某种方式使开发时间增加一倍,而投资回报却很少,因为这完全是错误的。

TDD requires some practice, and you’ll get faster over time. What feels clumsy today can become a second nature tomorrow, if you do it regularly. I encourage you not to discard something because it’s new and feels awkward: give it some time to assess it fairly, then take a decision.

TDD需要一些练习,随着时间的流逝,您会越来越快。 如果您经常这样做,今天觉得笨拙的东西明天可能会成为第二天性。 我鼓励您不要丢弃某些东西,因为它很新并且很尴尬:给它一些时间来公平地评估它,然后做出决定。

Secondly, time spent on writing test-driven code is time you won’t spend fixing bugs.

其次, 花费在编写测试驱动的代码上的时间就是您不会花费在修复错误上的时间

Fixing bugs is far more costly than preventing them. If you’ve ever had to fix critical production bugs, you know this feels close to holding an open wound on a surgical patient with one hand, while trying to operate with the other one. In the desert. At night. With a Swiss Army knife. It’s messy, stressful, suboptimal, and bears high chances of screwing up something else in the process. If you want to preserve your sanity and the trust your end users have in your software, you want to avoid those situations at all costs.

修复错误要比阻止错误花费更多。 如果您曾经不得不修复关键的生产错误,那么您就知道这就像用一只手在手术患者身上握着一个开放的伤口,而试图用另一只手进行手术一样。 在沙漠里。 在晚上。 用瑞士军刀。 它是混乱,压力大,次优的,并且很可能在此过程中搞砸其他事情。 如果您想保持理智和最终用户对软件的信任,则想不惜一切代价避免发生这种情况

Tests help you catch bugs before they make it to production, and TDD helps you write better tests. If you think you should test your software, then you should care about making these tests useful in the first place. Otherwise, the whole thing is only a waste of time.

测试可以帮助您在bug投入生产之前就将其捕获,而TDD可以帮助您编写更好的测试。 如果您认为应该测试软件,则应该首先考虑使这些测试有用。 否则,整个事情只会浪费时间。

As with anything, I encourage you to try TDD before discarding the idea. If you’re consistently encountering production issues, or you think you could improve your development process, then it’s worth giving it a shot. Try it for a limited amount of time, measure the impact, and compare the results. You may discover a method that helps you ship better software, and feel more confident about hitting the “Deploy” button.

与其他任何方法一样,我鼓励您在放弃此主意之前先尝试一下TDD。 如果您一直遇到生产问题,或者您认为可以改善开发流程,那么值得一试。 在有限的时间内尝试一下,测量影响并比较结果。 您可能会发现一种方法,可以帮助您发布更好的软件,并对点击“部署”按钮更有信心。

翻译自: https://www.freecodecamp.org/news/an-introduction-to-tdd-with-vue-js-66544710b50c/

vue.js数据驱动

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值