前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一杯茶的时间,上手 Jest 测试框架

一杯茶的时间,上手 Jest 测试框架

作者头像
一只图雀
发布2020-04-13 19:25:43
1.9K0
发布2020-04-13 19:25:43
举报
文章被收录于专栏:图雀社区

我们能学到什么

  1. Jest怎么4行代码完成一个测试用例
  2. Jest怎么让测试用例覆盖率100%
  3. Jest怎么和Typescript完美结合(填坑实录)
  4. Jest最锋利的功能 Mock Functions

项目初始化

创建工程

代码语言:javascript
复制
# 注意powershell中'&&'替换为';'
mkdir jest-just-go && cd jest-just-go

初始化

代码语言:javascript
复制
npm init -y

安装依赖

代码语言:javascript
复制
npm i jest --save-dev

1.Jest怎么4行代码完成一个测试用例

初始化Jest默认配置

代码语言:javascript
复制
npx jest --init

初始化时会出现提示信息,按y或enter即可。

编写功能代码

现在让我们正式开始,茶和图雀社区精心准备的甜品更搭哦。 在项目根目录下新建src目录,存放我们的功能代码。然后创建src/dessert.js

代码语言:javascript
复制
const dessert = function (name) {
    this.name = name;
}

dessert.prototype = {
    enjoy: function () {
        return "Enjoy the " + this.name;
    }
}

module.exports = dessert;

我们已经编写了一个甜品类型,它有一个提供品尝的方法enjoy

编写测试用例

下面开始编码,实现对上面甜品功能的单元测试。 在项目根目录下新建__tests__目录,存放我们的测试用例。然后创建__tests__/dessert.test.js

代码语言:javascript
复制
const dessert = require("../src/dessert");

describe("test dessert feature", () => {
    test("enjoy the cake", () => {
        const cake = new dessert('cake');
        expect(cake.enjoy()).toBe("Enjoy the cake");
    })
})

简单的四行代码,我们的第一个测试用例就已经大功告成。这里我们只需要注意 describetestexpect 这3个 Jest 关键字就行了:

  1. describe:组合同一类的 test 用例,可以添加 beforeEach \ afterEach、beforeAll \ afterAll (这里由于篇幅,这一类进阶特性将放在后续的教程中)为其下所有 test 进行统一描述和处理。
  2. test:描述具体的测试用例,是单元测试的最小单元。
  3. expectJest 最终落在了每一个对测试结果的 期望 上,通过 expect 中的返回值或是函数执行结果来和期望值进行对比。

执行测试

回到控制台,输入:

代码语言:javascript
复制
npm test

无需更多配置,测试结果显示如下:

其中:

  • %Stmts 是语句覆盖率(statement coverage):是不是每个语句都执行了?
  • %Branch 分支覆盖率(branch coverage):是不是每个if代码块都执行了?
  • %Funcs 函数覆盖率(function coverage):是不是每个函数都调用了?
  • %Lines 行覆盖率(line coverage):是不是每一行都执行了?

%Stmts%Lines 的区别是:行覆盖率的颗粒度是大于语句覆盖率的,因为可能允许一行中有多条语句(js开发中尤为常见)。 我们更改__tests__/dessert.test.js

代码语言:javascript
复制
expect(cake.enjoy()).toBe("Enjoy the cake");
改为
expect(cake.enjoy()).toBe("enjoy the cake");

执行测试命令查看测试不通过的情形:

2.Jest怎么让测试用例覆盖率达到100%

当我们的功能场景逐渐变得复杂,我们的测试就必须确保测试用例的覆盖率达到一个标准。最佳当然是100%啦,这样才能保证测试小改改们找不到我们的茬,闲的没事就会主动找我们拉话话啦,美好生活从测试用例覆盖率100%开始。

编写功能代码

甜点不够怎么办?要不我们开家店吧!先让红豆烧和提拉米苏够吃先~

代码语言:javascript
复制
const dessert = require("./dessert");

const dessertFactory = function () {
}

dessertFactory.produce = function (type) {
    switch (type) {
        case 'Red bean burning':
            return new dessert('Red bean burning'); // 红豆烧
        case 'Tiramisu':
            return new dessert('Tiramisu'); // 提拉米苏
        default:
            throw new Error("please choose a dessert type");
    }
}

module.exports = dessertFactory;

现在想吃什么通过dessertFactory.produce(...)order就fine啦~

编写测试用例

不过,要保证我们想吃的时候就必须能吃到(这个很重要),我们先验收先:__tests__/dessertFactory.test.js

代码语言:javascript
复制
const dessertFactoty = require("../src/dessertFactoty");

describe("test dessertFactoty feature", () => {
    test("produce all dessert", () => {
        const dessertType = ['Red bean burning', 'Tiramisu'];
        expect(dessertFactoty.produce(dessertType[0]).enjoy()).toBe("Enjoy the Red bean burning");
    })
})

--“我不要你觉得,我要我觉得“,我要上档次的“验收报告“! --行,网页展示出来怎么样

配置jest.config.js保存测试用例覆盖率执行报告

我们在执初始化Jest默认配置的时候,会生成在项目根目录下生成jest.config.js,里面列出了所有的配置项,未设置的已经被注释掉了。我们要将每次执行测试后生成的覆盖率报告保存下来需要找到下面这项配置属性并更改:

代码语言:javascript
复制
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,

然后我们可以再次执行测试命令并用浏览器打开_/coverage/lcov-report/index.html_查看测试用例覆盖率报告:

修改测试用例使覆盖率达到100%

__tests__/dessertFactory.test.js

代码语言:javascript
复制
const dessertFactoty = require("../src/dessertFactoty");

describe("test dessertFactoty feature", () => {
    test("produce all dessert", () => {
        const dessertType = ['Red bean burning', 'Tiramisu'];
        expect(dessertFactoty.produce(dessertType[0]).enjoy()).toBe("Enjoy the Red bean burning");
        expect(dessertFactoty.produce(dessertType[1]).enjoy()).toBe("Enjoy the Tiramisu");
        expect(() => { dessertFactoty.produce('Luckin Coffee') }).toThrow("please choose a dessert type");
    })
})

再测试并查看覆盖率报告:

这里 Functions列 为什么不是100%,大家可以点击 dessertFactory.js 根据详细说明分析推测。

3.Jest怎么和Typescript完美结合(填坑实录)

搜索引擎上现有的 Jest + Typescript 的样例比较少,并且存在了一定的问题没有解决,这一部分我已经填平了坑,可以作为配置参考。

增加依赖

代码语言:javascript
复制
npm i ts-jest @types/jest typescript @types/node --save-dev

其中 ts-jestJest + Typescript 环境下进行测试提供了类型检查支持和预处理。

ts初始化

在根目录下创建配置文件tsconfig.json

代码语言:javascript
复制
{
    "compilerOptions": {
        "target": "es5",
        "module": "commonjs",
        "lib": [
            "es2015"
        ],
        "strict": true,
        "declaration": true,
        "outDir": "build",
        "sourceMap": true
    },
    "include": [
        "src/**/*"
    ],
    "exclude": [
        "node_modules",
        "__test__/**/*"
    ]
}

修改jest.config.js配置

添加如下配置项:

代码语言:javascript
复制
// An array of file extensions your modules use
moduleFileExtensions: [
    "js",
    "json",
    "jsx",
    "ts",
    "tsx",
    "node"
],
// A preset that is used as a base for Jest's configuration
preset: "ts-jest",

修改功能代码

src/dessert.js => src/dessert.ts

代码语言:javascript
复制
export default class dessert {
    name: string;
    constructor(name: string) {
        this.name = name;
    }
    enjoy() {
        return "Enjoy the " + this.name;
    }
}

src/dessertFactory.js => src/dessertFactory.ts

代码语言:javascript
复制
import dessert from "./dessert";

export default class dessertFactory {
    static produce(type: string) {
        switch (type) {
            case 'Red bean burning':
                return new dessert('Red bean burning'); // 红豆烧
            case 'Tiramisu':
                return new dessert('Tiramisu'); // 提拉米苏
            default:
                throw new Error("please choose a dessert type");
        }
    }
}

修改测试用例

__tests__/dessert.js => __tests__/dessert.ts

代码语言:javascript
复制
import dessert from "../src/dessert";

describe("test dessert feature", () => {
    test("enjoy the cake", () => {
        const cake = new dessert('cake');
        expect(cake.enjoy()).toBe("Enjoy the cake");
    })
})

__tests__/dessertFactory.js => __tests__/dessertFactory.ts

代码语言:javascript
复制
import dessertFactoty from "../src/dessertFactoty";

describe("test dessertFactoty feature", () => {
    test("produce all dessert", () => {
        const dessertType = ['Red bean burning', 'Tiramisu'];
        expect(dessertFactoty.produce(dessertType[0]).enjoy()).toBe("Enjoy the Red bean burning");
        expect(dessertFactoty.produce(dessertType[1]).enjoy()).toBe("Enjoy the Tiramisu");
        expect(() => { dessertFactoty.produce('Luckin Coffee') }).toThrow("please choose a dessert type");
    })
})

如同代码重构后我们通过测试用例可以快速检查是否改动出现差错一样,我们这次变更可以执行 Jest 测试命令,检查是否对功能无影响。

4.Jest最锋利的功能 Mock Functions

关于 Jest 测试框架中的Mock功能,我们主要关注两点:

  1. mock function: 对函数进行mock.
  2. mock return value: 对返回值进行mock.

从以上两点可以衍生出 Jest 对于代码单元测试中两项常用的锋利功能:

  1. 对功能中业务逻辑简化后的重新实现,方便有指向性的进行测试(比如忽略实际场景中的跨服务调用功能等,仅需将原有功能中对应的调用逻辑改为定义的测试数据即可)。
  2. 对功能返回值的直接模拟。

为甜点增加了评论功能:

代码语言:javascript
复制
export default class dessert {
    name: string;
    static comment: string[] = [];
    constructor(name: string) {
        this.name = name;
    }
    enjoy() {
        return "Enjoy the " + this.name;
    }
    static comments(message: string) {
        dessert.comment.push(message);
    }
}

专门建立一个互动区进行甜点的讨论。创建src/dessertCommentModule.ts:

代码语言:javascript
复制
import dessert from "./dessert";

module dessertCommentModule {
    export function comments(message: string) {
        dessert.comments(message);
        return dessert.comment;
    }

}

export default dessertCommentModule;

下面开始test dessert feature with mock~这一部分均已通过测试,建议深入研读代码,感受 Jest mock 每一处的奥妙。如果下面各处期望的结果都如你所料,那么你就是图雀社区最靓的仔:

代码语言:javascript
复制
import dessert from "../src/dessert";
import dessertCommentModule from "../src/dessertCommentModule";
jest.mock("../src/dessertCommentModule");

describe("test dessert feature", () => {
    test("enjoy the cake", () => {
        const cake = new dessert('cake');
        expect(cake.enjoy()).toBe("Enjoy the cake");
    })
})

describe("test dessert feature with mock", () => {
    test("enjoy the cake with mock function", () => {
        const dessertFactoryMock = jest.fn(name => new dessert(name));
        const cake = dessertFactoryMock('cake');
        expect(cake.enjoy()).toBe("Enjoy the cake");
        expect(dessertFactoryMock.mock.results[0].value.enjoy()).toBe("Enjoy the cake");
        console.log(dessertFactoryMock.mock);
    })
    test("enjoy the cake with mock return value", () => {
        const dessertFactoryMock = jest.fn(name => new dessert(name));
        const cake = new dessert('cake');
        dessertFactoryMock.mockReturnValue(cake);
        const tiramisu = dessertFactoryMock('tiramisu');
        expect(tiramisu.enjoy()).toBe("Enjoy the cake");
        expect(tiramisu).toEqual(cake);
        expect(dessertFactoryMock.mock.results[0].value).toEqual(cake);
    })
    test("comment the dessert with mock module", () => {
        const mockedDessert = dessertCommentModule as jest.Mocked<typeof dessertCommentModule>;
        mockedDessert.comments.mockReturnValue(['not bad']);
        expect(mockedDessert.comments("cake is so good")).toEqual(['not bad']);
        expect(dessert.comment).toEqual([]);
    })
    test("comment the dessert with mock implementations", () => {
        const mockedDessert = dessertCommentModule as jest.Mocked<typeof dessertCommentModule>;
        mockedDessert.comments.mockImplementation((message: string) => {
            dessert.comments(message);
            return ['not bad'];
        });
        expect(mockedDessert.comments("cake is so good")).toEqual(['not bad']);
        expect(dessert.comment).toEqual(['cake is so good']);
    })
})

mock function

注意:我们可以更改testtest.only仅对test.only的测试用例执行测试,降低测试时间并关注特定测试点。

代码语言:javascript
复制
test.only("enjoy the cake with mock function", () => {  ...

这里我们通过 jest.fn 进行 了 mock function 功能的展示,通过执行 npm test 看到 .mock 的结构信息:

.mock的中将会记录mock function调用后的相关信息

mock return value

代码语言:javascript
复制
test("enjoy the cake with mock return value", () => {
  ....

这里我们通过 .mockReturnValu 可以将 function mock 的操作略过,直接会返回 .mockReturnValue 中填充的返回值。通过执行 npm test 验证。

mock module

代码语言:javascript
复制
import dessertCommentModule from "../src/dessertCommentModule";
jest.mock("../src/dessertCommentModule");
test.only("comment the dessert with mock module", () => {
  ...

通过 jest.mock ,我们 mock 了甜点评论区,这项操作可以使我们对dessertCommentModule中的所有功能进行我们的测试定制。这里我们通过对评论功能进行 mock return value ,通过执行 npm test 看到:

并没有进入dessertCommentModule中的comments方法,直接返回了我们预置的返回值。

mock implementations

代码语言:javascript
复制
test.only("comment the dessert with mock implementations", () => {
  ...

我们通过对评论功能进行 mock implementations ,通过执行 npm test 看到:

进入了 mockImplementation 中的测试定制功能,并且调用了dessert中的comments方法。 以上。

参考

_https://jestjs.io/docs/en/getting-started_[4] _https://www.valentinog.com/blog/jest_[5] _https://dev.to/muhajirdev/unit-testing-with-typescript-and-jest-2gln_[6]

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 图雀社区 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 项目初始化
    • 创建工程
      • 初始化
        • 安装依赖
        • 1.Jest怎么4行代码完成一个测试用例
          • 初始化Jest默认配置
            • 编写功能代码
              • 编写测试用例
                • 执行测试
                • 2.Jest怎么让测试用例覆盖率达到100%
                  • 编写功能代码
                    • 编写测试用例
                      • 配置jest.config.js保存测试用例覆盖率执行报告
                        • 修改测试用例使覆盖率达到100%
                        • 3.Jest怎么和Typescript完美结合(填坑实录)
                          • 增加依赖
                            • ts初始化
                              • 修改jest.config.js配置
                                • 修改测试用例
                                • 4.Jest最锋利的功能 Mock Functions
                                  • mock function
                                    • mock return value
                                      • mock module
                                        • mock implementations
                                        • 参考
                                        领券
                                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档