一杯茶的时间,上手 Jest 测试框架 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
huan1043269994
V2EX    前端开发

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

  •  
  •   huan1043269994 2020-04-10 20:57:31 +08:00 2062 次点击
    这是一个创建于 2042 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本文由图社区成员 sth 写作而成,欢迎加入图雀社区,一起创作精彩的免费技术教程,予力编程行业发展。

    如果您觉得我们写得还不错,记得 点赞 + 关注 + 评论 三连,鼓励我们写出更好的教程

    当我第一次听说 TDD(Test-Driven Develop, 测试驱动开发)的时候,我首先想到的是测试小姐姐们想搞事情?后面发现这个玩意儿是想干掉测试,那好,下面开始让我们干掉这群整天给我们找 bug...下面开始从简单的单元测试上手 Jest 测试框架。

    我们能学到什么

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

    本文所涉及的源代码都放在了 Github 上,如果您觉得我们写得还不错,希望您能给这篇文章点赞+Github 仓库加星

    项目初始化

    创建工程

    # 注意 powershell 中'&&'替换为';' mkdir jest-just-go && cd jest-just-go 

    初始化

    npminit-y 

    安装依赖

    npm i jest --save-dev 

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

    初始化 Jest 默认配置

    npx jest --init 


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

    编写功能代码


    现在让我们正式开始,茶和图雀社区精心准备的甜品更搭哦。

    在项目根目录下新建src目录,存放我们的功能代码。然后创建src/dessert.js

    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

    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 中的返回值或是函数执行结果来和期望值进行对比。

    执行测试


    回到控制台,输入:

    npm test 


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



    其中:

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

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

    我们更改__tests__/dessert.test.js

    expect(cake.enjoy()).toBe("Enjoy the cake"); 改为 expect(cake.enjoy()).toBe("enjoy the cake"); 


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


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


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

    编写功能代码


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

    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

    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,里面列出了所有的配置项,未设置的已经被注释掉了。我们要将每次执行测试后生成的覆盖率报告保存下来需要找到下面这项配置属性并更改:

    // Indicates whether the coverage information should be collected while executing the test collectCoverage: true, 


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


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


    __tests__/dessertFactory.test.js

    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 的样例比较少,并且存在了一定的问题没有解决,这一部分我已经填平了坑,可以作为配置参考。

    增加依赖

    npm i ts-jest @types/jest typescript @types/node --save-dev 


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

    ts 初始化


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

    { "compilerOptions": { "target": "es5", "module": "commonjs", "lib": [ "es2015" ], "strict": true, "declaration": true, "outDir": "build", "sourceMap": true }, "include": [ "src/**/*" ], "exclude": [ "node_modules", "__test__/**/*" ] } 

    修改jest.config.js配置

    p>
    添加如下配置项:

    // 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

    export default class dessert { name: string; constructor(name: string) { this.name = name; } enjoy() { return "Enjoy the " + this.name; } } 


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

    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

    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

    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. 对功能返回值的直接模拟。


    为甜点增加了评论功能:

    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:

    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 每一处的奥妙。如果下面各处期望的结果都如你所料,那么你就是图雀社区最靓的仔:

    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的测试用例执行测试,降低测试时间并关注特定测试点。

    test.only("enjoy the cake with mock function", () => { ... 


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



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

    mock return value

    test("enjoy the cake with mock return value", () => { .... 


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

    mock module

    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

    test.only("comment the dessert with mock implementations", () => { ... 


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



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

    以上。

    参考

    https://jestjs.io/docs/en/getting-started
    https://www.valentinog.com/blog/jest
    https://dev.to/muhajirdev/unit-testing-with-typescript-and-jest-2gln

    想要学习更多精彩的实战技术教程?来图雀社区逛逛吧。

    本文所涉及的源代码都放在了 Github 上,如果您觉得我们写得还不错,希望您能给这篇文章点赞+Github 仓库加星

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5337 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 33ms UTC 07:46 PVG 15:46 LAX 23:46 JFK 02:46
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86