如何用Jest测试JavaScript

110 阅读6分钟

当你建立一个应用程序时,手动测试是非常容易的事情。从你开始写第一行代码起,你就一直在这样做了!为什么?怎么做?想一想吧。你写一些代码,保存它,然后运行代码,看看结果是什么。它给出了你想要的结果吗?很好!那么它就成功了。那么,如果测试你写的代码是如此简单,为什么还要写自动测试呢?原因是,随着你的应用程序越来越大,手动测试应用程序的每一部分需要越来越多的时间。

manual application testing takes time


测试的好处是什么?

自动测试允许你在少量的时间内频繁地测试你的代码。这意味着你可以在实际推送代码到生产之前发现问题和错误。如果你曾经第一次部署一个应用程序,你知道随之而来的担忧。它能工作吗?是否有bug?自动测试有助于给你更多的信心,让你知道事情在需要的时候会工作。公平地说,一旦软件进入生产阶段,测试并不能使你免于处理问题和bug。无论你写多少测试,都不可能写出完美的软件。然而,与其部署一个有25个bug的应用程序,不如部署一个只有5个bug的应用程序,这要感谢你所有的测试。另一个好处是允许你更有信心地重构你的代码。换句话说,你可以清理你的功能,或者改变代码本身的结构,使其更易读,同时还可以让代码执行相同的确切任务。


软件开发中的测试类型有哪些?

types of software tests
一般来说,在软件开发中,有三种类型的自动化测试。

  • **单元测试。**测试应用程序的一个小单元,没有外部资源,如数据库。
  • 集成测试。在所有外部资源到位的情况下测试应用程序。
  • 功能/端到端测试。通过其用户界面测试应用程序。

测试的金字塔

在建立一个应用程序时,应该进行哪些类型的测试?如果我们有几种类型,哪种是最好的?最有可能的是,你会有三种类型的测试的组合。最大量的测试是单元测试,因为它们很容易编写和快速执行。接下来,你会有一个集成测试的集合。应用程序中的集成测试可能比单元测试少。最后,你会有一些端到端的测试,因为这些是最全面的,但也是最慢的。这种方法看起来像一个金字塔。注意E2E代表 "端到端 "或功能。
software test pyramid
这意味着你会把大部分的测试写成单元测试。对于测试覆盖率中出现的任何差距,使用集成测试。最后,最少使用端到端测试。


编写你的第一个单元测试

那么,我们什么时候才能写一个测试呢!我们现在就开始吧!首先,我们要创建一个javascript-testing目录,然后我们可以用NPM来安装Jest。让我们首先使用npm init来创建一个新的package.json文件。

javascript-testing $npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.
See `npm help json` for definitive documentation on these fields
and exactly what they do.
Use `npm install ` afterwards to install a package and
save it as a dependency in the package.json file.Press ^C at any time to quit.
package name: (javascript-testing)
version: (1.0.0)
description: JavaScript Test Tutorial
entry point: (index.js)
test command: jest
git repository:
keywords:
author:
license: (ISC)
About to write to C:nodejavascript-testingpackage.json:

{
  "name": "javascript-testing",
  "version": "1.0.0",
  "description": "JavaScript Test Tutorial",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "ISC"
}


Is this OK? (yes)

现在我们可以安装Jest了。
npm install jest

现在我们应该看一下package.json文件中突出显示的那一行。这是说,当我们在命令行中运行npm testjest 脚本将运行以执行所有测试。

{
  "name": "javascript-testing",
  "version": "1.0.0",
  "description": "JavaScript Test Tutorial",
  "main": "index.js",
  "scripts": {
    "test": "jest"
  },
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "jest": "^23.4.1"
  }
}

让我们试试,看看会发生什么。

javascript-testing $npm test
> javascript-testing@1.0.0 test C:nodejavascript-testing
> jest
No tests foundIn C:nodejavascript-testing
  2 files checked.  testMatch: **/__tests__/**/*.js?(x),**/?(*.)+(spec|test).js?(x) - 0 matches  testPathIgnorePatterns: \node_modules\ - 2 matches
Pattern:  - 0 matches
npm ERR! Test failed.  See above for more details.
javascript-testing $

很好!Jest正在工作,但我们还没有任何测试。让我们创建一个!在我们的项目中,我们可以添加一个tests 文件夹。在这个文件夹中,我们将放置一个utility.test.js 文件。
jest test example
事实上,文件名中出现的test这个词将让Jest知道这是一个测试。这里是我们的第一个测试。它还没有测试任何东西,但这将允许我们在命令行中运行npm test ,看看会发生什么。

test('First Jest Test', () => {

});

现在,我们运行我们的测试,并检查一下!
jest passing test

上面我们看到Jest中通过的测试是什么样子的。现在,让我们看看一个失败的测试是什么样子的。我们可以像这样修改我们的测试。

test('First Jest Test', () => {
    throw new Error('It crashed!');
});

现在,当我们运行它时,我们会得到一大堆有用的信息。
jest test failed


如何测试数字

在我们的utility.js文件中,有一个函数可以创建一个绝对数字。换句话说,它将永远不会返回一个负数。

module.exports.absolute = function (number) {
    if (number > 0) return number;
    if (number < 0) return -number;
    return 0;
}

让我们写一个新的测试来测试这个。这里是第一个迭代。

const utility = require('../utility');

test('absolute - should return positive number for any positive input', () => {
    const result = utility.absolute(1);
    expect(result).toBe(1);
});

我们可以运行这个测试,它通过了。

javascript-testing $npm test

> javascript-testing@1.0.0 test C:nodejavascript-testing
> jest

 PASS  tests/utility.test.js
  √ absolute - should return positive number for any positive input (6ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        6.351s, estimated 7s
Ran all test suites.

这个函数还应该返回一个正数,即使给定一个负数。没问题,我们可以把它添加到测试中。

const utility = require('../utility');

test('absolute - should return positive number for any positive input', () => {
    const result = utility.absolute(1);
    expect(result).toBe(1);
});

test('absolute - should return positive number for any negative input', () => {
    const result = utility.absolute(-1);
    expect(result).toBe(1);
});

现在当我们运行测试时,它又通过了。很好!还请注意我们是如何被显示出运行了多少个测试的。
two jest tests passing

现在让我们做一个失败的测试,以展示如何使用预期值与接收值来排除故障。我们知道,如果这个函数的输入值为0,它应该返回0。我们可以做一个测试,让它在这个条件下失败,像这样。

const utility = require('../utility');

test('absolute - should return positive number for any positive input', () => {
    const result = utility.absolute(1);
    expect(result).toBe(1);
});

test('absolute - should return positive number for any negative input', () => {
    const result = utility.absolute(-1);
    expect(result).toBe(1);
});

test('absolute - should return 0 if input is 0', () => {
    const result = utility.absolute(-1);
    expect(result).toBe(0);
});

看看我们从一个失败的测试中得到的输出。
troubleshooting failed tests in jest

这只是为了显示失败的测试是什么样子的。我们可以修复这个测试,现在这个1个函数的3个测试都成功了,看起来是这样的。
three good jest tests


如何测试字符串

现在我们可以测试一个处理字符串的函数。考虑一下这个简单的函数,当某人提供他们的名字时,向他们问好。

module.exports.hello = function (name) {
    return 'Hello ' + name + '!';
}

让我们为它做一个测试。这个测试将使用一个新的语法。

const utility = require('../utility');

describe('hello', () => {
    it('should return the hello message', () => {
        const result = utility.hello('Stranger');
        expect(result).toBe('Hello Stranger!');
    })
});

当我们运行它时,测试看起来不错。

javascript-testing $npm test

> javascript-testing@1.0.0 test C:nodejavascript-testing
> jest

 PASS  tests/utility.test.js
  hello
    √ should return the hello message (7ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        6.977s
Ran all test suites.

在测试字符串时,你可以使用的另一个选项是toContain()匹配器而不是toBe()。原因是测试可能过于具体,可能容易失败。下面是同一个测试的一个稍微灵活的版本。

const utility = require('../utility');

describe('hello', () => {
    it('should return the hello message', () => {
        const result = utility.hello('Stranger');
        expect(result).toContain('Stranger');
    })
});

如何测试数组

让我们来看看我们可以在Jest中使用的一些简单的数组测试。想象一下,我们有这样一个函数,它返回一个股票代码的数组。

module.exports.getTickers = function () {
    return ['AAPL', 'MSFT', 'NFLX'];
}

下面是我们为此创建的测试。查阅文档以了解arrayContaining()函数是如何工作的。

const utility = require('../utility');

describe('getTickers', () => {
    it('should return three stock tickers', () => {
        const result = utility.getTickers();
        expect(result).toEqual(expect.arrayContaining(['NFLX', 'MSFT', 'AAPL']));
    })
});

如何测试对象

在这个函数中,我们传递了一个游戏的ID,该函数返回一个具有该ID的游戏对象。

module.exports.getGame = function (gameId) {
    return {
        id: gameId,
        price: 10
    };
}

这里有一个测试,可以确保我们得到正确的游戏。

const utility = require('../utility');

describe('getGame', () => {
    it('should return the game with the provided id', () => {
        const result = utility.getGame(1);
        expect(result).toEqual({
            id: 1,
            price: 10
        })
    })
});

当我们运行这个测试时,一切看起来都很好。
jest toequal for testing objects


如何测试异常

对于有能力抛出错误的函数,我们需要采取不同的方法来测试。考虑一下这个函数,它创建了一个新的用户。如果没有提供用户名,它将抛出一个异常。

module.exports.createUser = function (username) {
    if (!username) throw new Error('Username is required.');

    return {
        id: new Date().getTime(),
        username: username
    }
}

下面是我们如何测试这样的一个函数。

const utility = require('../utility');

describe('createUser', () => {
    it('should throw an error if username is falsy', () => {
        const args = [null, undefined, NaN, '', 0, false];
        args.forEach(a => {
            expect(() => {
                utility.createUser(a)
            }).toThrow();
        });
    })

    it('should return a user object when a valid username is provided', () => {
        const result = utility.createUser('Jester');
        expect(result).toMatchObject({
            username: 'Jester'
        });
        expect(result.id).toBeGreaterThan(0);
    });
});

当我们运行它时,测试是通过的。

javascript-testing $npm test

> javascript-testing@1.0.0 test C:nodejavascript-testing
> jest

 PASS  tests/utility.test.js
  createUser
    √ should throw an error if username is falsy (9ms)
    √ should return a user object when a valid username is provided (4ms)

Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        6.693s
Ran all test suites.


用Jest测试JavaScript总结

综上所述,我们已经学到了以下一些关于测试的概念。

  • 编写代码来测试应用程序的代码被称为自动化测试。

  • 测试有助于以更少的错误和更高的质量交付软件。

  • 测试有助于你自信地进行重构。

  • Jest是一个强大的测试框架,拥有测试JavaScript所需的一切。

  • 有三种类型的自动化测试。

  • 单元测试:测试应用程序的一个小单元,没有外部资源,如数据库。

  • 集成测试:在所有外部资源到位的情况下测试应用程序。

  • 功能/端到端测试:通过其用户界面测试应用程序。

  • 如果测试过于笼统,就不能保证你的代码工作。如果他们太具体,他们往往太容易损坏。因此,测试的目标是既不过于笼统,也不过于具体。

  • 你可以使用Mock函数来将应用程序代码与外部资源隔离。

  • 你在测试中可能使用的Jest匹配器函数。

  • // 平等性
    expect(...).toBe();
    expect(...).toEqual()。

  • // 真实性
    expect(...).toBeDefined();
    expect(...).toBeNull();
    expect(...).toBeTruthy();
    expect(...).toBeFalsy()

  • // 数字
    expect(...).toBeGreaterThan();
    expect(...).toBeGreaterThanOrEqual();
    expect(...).toBeLessThan()

  • // 字符串
    expect(...).toMatch(/regularExp/);

  • // 数组
    expect(...).toContain()。

  • // 对象
    expect(...).toBe(); // 用于对象引用
    expect(...).toEqual(); // 用于属性相等
    expect(...).toMatchObject()。

  • // 异常
    expect(() => { someCode }).toThrow()。