We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
单元测试是现代软件工程中必备的环节之一,通常一定规模的团队会有专门的测试人员,但是一些精悍的团队也会选择开发人员自行测试。
虽然 TypeScript 的类型检查和 ESLint 的代码检测已经让我们的代码足够强大,但是这仅仅停留在类型和语法层面,我们无法保证逻辑的正确性,这就需要单元测试来保证我们的逻辑的健壮性了。
在 JS/TS 的世界里有太多的单元测试框架可供选择了,我们逐一进行对比,来选出一个适合我们的框架。
Mocha 可能是当前被使用最多的单元测试工具,他最大的优点就是灵活,它只提供给开发者的只有一个基础测试结构。
然后其它功能性的功能如 assertions, spies,mocks,和像它们一样的其它功能需要引用添加其它库/插件来完成。
它的缺点也是他的优点,我们需要额外的引入众多辅助库和插件,这无形中增加了我们的学习成本和配置成本。
除此之外, Mocha 的另一大亮点是对异步的强大支持,Mocha 毕竟是从一开始为 Node.js 而生的单元测试框架,对浏览器的支持并不如其对服务器做的那么好。
如果你需要一个高度定制的测试框架,Mocha 是非常好的选择。
相比于 Mocha 需要进行额外配置,另一个比较老牌的测试框架 Jasmine 主打的则是开箱即用,它内置了一些断言库和 mocks 工具,并提供了全局变量非常方便我们的测试。
Jest 是一个真正意义上开箱即用的测试框架,它集成了几乎单元测试中我们需要的所有功能,比如:断言、测试覆盖率统计、快照等等一系列功能。
比较友好地支持各种环境,目前前端三大框架都采用了 Jest 作为测试工具,它的优点如下:
Jest 正是基于 Jasmine 开发而来,如果你喜欢 Jasmine,那么为什么不用 Jest,他比 Jasmine 更加大而全,更加开箱即用。
其实在我们面前有两条路,一条是灵活配置但是学习成本陡峭的 Mocha,另一条是大而全开箱即用,却没那么灵活的 Jest,多数没有特殊要求的情况下我认为 Jest 会更适合我们,测试框架到底是一个工具,我们选择一个几乎不需要配置、开箱即用的框架是可以大大提高我们生产效率的选择。
全局安装 Jest:
npm i jest -g
在项目中安装 Jest:
npm i -D jest @types/jest
我们在项目的根目录下初始化 jest:
jest --init
我们会被问到三个问题,我的选择如下:
第一个问题需要我们选择测试执行环境,有浏览器和 Node 两个选项,我们这次在简单的 Node 环境下测试,所以选择了 node。
第二个问题问我们是否需要测试覆盖率报告,通常情况下这个报告很重要,使我们整体测试情况的写一个报告,我选择了“是”。
第三个问题问我们是否在测试结束后帮我们自动清除一些模拟的实例等等,我选择了“是”,避免这些东西影响我们的下次测试。
这个时候我们的根目录下已经生成了一个叫 jest.config.js 的配置文件,我们看到里面有非常多的配置项。
jest.config.js
比如 clearMocks: true 就是我们刚才的问题中用于清除模拟残留的配置,coverageDirectory: 'coverage' 就是我们刚才选择的测试覆盖率报告的配置,testEnvironment: 'node' 是我们刚才选择的测试环境。
clearMocks: true
coverageDirectory: 'coverage'
testEnvironment: 'node'
以上只是基础配置,我们如果想要在 TypeScript 中使用,需要进一步的配置。
我们需要在配置中加入以下选项:
{ moduleFileExtensions: [ 'ts', 'tsx', 'js', 'json', 'jsx', 'node', ], transform: { '^.+\\.tsx?$': 'ts-jest', }, testMatch: [ '**/__tests__/**/*.[jt]s?(x)', '**/?(*.)+(spec|test).[tj]s?(x)', ], }
moduleFileExtensions
transform
.ts
.tsx
testMatch
以上就是我们需要配置的配置项,当然还有一些配置项我们并没有涉及,由于篇幅所限,且其重要性也有限,感兴趣的可以移步官方的配置 Jest进一步学习。
Jest 其实有很多强大的功能,我们并不是专门讲解 Jest 的书,因此只会讲解一下重点内容,更多的资料可以移步Jest 官网。
我们在新建一个目录 src/,如何在此目录下新建一个文件 add.ts,开始进行编码如下:
src/
add.ts
export function add(item: number, ...rest: number[]) { return rest.reduce((a: number, b: number) => a + b, item) }
我们实现一个非常简单的累加函数,然后开始进行测试。
我们先创建一个文件 add.test.ts,我们使用一个最简单的匹配器:
add.test.ts
import { add } from './add' test('two plus two is four', () => { expect(add(2,2)).toBe(4) })
import { add } from './add'
test('two plus two is four', () => { expect(add(2,2)).toBe(4) })
在此代码中,expect(add(2,2)) 返回一个"期望"的对象,.toBe(4) 则是匹配器,我们期望这个函数运行的结果是 4,如果匹配失败,Jest运行时,它会跟踪所有失败的匹配器,以便它可以为你打印出很好的错误消息。
expect(add(2,2))
.toBe(4)
4
那么如果我们想测试相反的匹配那么可以:
test('two plus two is not six', () => { expect(add(2,2)).not.toBe(6) })
我们运行一下 npm run test,如下:
npm run test
在编写代码的过程中你会发现 TypeScript 的提示会显示非常多的匹配器,这也是 TypeScript 的优势之一,我们通常根本记不住如此繁多的 API,但是优秀的代码提示会帮助我们快速使用,而不必去一个个查阅官方的文档。
我们匹配完了简单的数字,接下来可以匹配对象了。
我们新建一个文件 src/person.ts,如下;
src/person.ts
export class Person { public name: string public age: number constructor(name: string, age: number) { this.age = age this.name = name } <span class="hljs-keyword">public</span> say() { <span class="hljs-keyword">return</span> <span class="hljs-string">'hello'</span> } }
export class Person { public name: string public age: number constructor(name: string, age: number) { this.age = age this.name = name } <span class="hljs-keyword">public</span> say() { <span class="hljs-keyword">return</span> <span class="hljs-string">'hello'</span> }
<span class="hljs-keyword">public</span> say() { <span class="hljs-keyword">return</span> <span class="hljs-string">'hello'</span> }
}
同样的我们新建一个测试文件 person.test.ts:
person.test.ts
test('test person', () => { const person = new Person('xiaomuzhu', 11) expect(person).toBeInstanceOf(Person) expect(person).not.toEqual({ name: 'cxk', }) })
toBeInstanceOf 用于匹配实例 person 是否是由 Person 构造的,toEqual 是比较两个对象是否相同。
toBeInstanceOf
person
Person
toEqual
新建一个文件 src/getTopics.ts,我们就声明一个函数,用于读取 Cnode 论坛的首页主题帖:
src/getTopics.ts
import axios from 'axios' const url = 'https://cnodejs.org/api/v1/topics' export async function getTopics() { const res = await axios.get(url) return res }
const url = 'https://cnodejs.org/api/v1/topics'
export async function getTopics() { const res = await axios.get(url) return res }
我们新建一个测试文件 src/getTopics.test.ts,若要编写 async 测试,只要在函数前面使用 async 关键字传递到 test :
src/getTopics.test.ts
import { getTopics } from "./getTopics"; test('should ', async () => { const { data } = await getTopics() expect(data).not.toBeUndefined() expect(data.success).toBeTruthy() });
异步测试在 Node 服务端的测试场景中非常常用。
我们再新建一个文件 src/myForEach.ts,在这里我们创建一个遍历函数:
src/myForEach.ts
export function myForEach(items: number[], callback: (a: number) => void) { for (let index = 0; index < items.length; index++) { callback(items[index]); } }
为了测试此函数,我们可以使用一个模拟函数,然后检查模拟函数的状态来确保回调函数如期调用。
在 src/myForEach.test.ts 中,我们可以用 jest.fn() 模拟函数,来测试调用情况:
src/myForEach.test.ts
jest.fn()
import { myForEach } from './myForEach'; test('should call two', () => { const mockCallback = jest.fn(); myForEach([0, 1], mockCallback); <span class="hljs-comment">// 此模拟函数被调用了两次</span> expect(mockCallback.mock.calls.length).toBe(<span class="hljs-number">2</span>); <span class="hljs-comment">// 第一次调用函数时的第一个参数是 0</span> expect(mockCallback.mock.calls[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>]).toBe(<span class="hljs-number">0</span>); <span class="hljs-comment">// 第二次调用函数时的第一个参数是 1</span> expect(mockCallback.mock.calls[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>]).toBe(<span class="hljs-number">1</span>); });
import { myForEach } from './myForEach'; test('should call two', () => { const mockCallback = jest.fn(); myForEach([0, 1], mockCallback); <span class="hljs-comment">// 此模拟函数被调用了两次</span> expect(mockCallback.mock.calls.length).toBe(<span class="hljs-number">2</span>); <span class="hljs-comment">// 第一次调用函数时的第一个参数是 0</span> expect(mockCallback.mock.calls[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>]).toBe(<span class="hljs-number">0</span>); <span class="hljs-comment">// 第二次调用函数时的第一个参数是 1</span> expect(mockCallback.mock.calls[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>]).toBe(<span class="hljs-number">1</span>);
test('should call two', () => { const mockCallback = jest.fn(); myForEach([0, 1], mockCallback);
<span class="hljs-comment">// 此模拟函数被调用了两次</span> expect(mockCallback.mock.calls.length).toBe(<span class="hljs-number">2</span>); <span class="hljs-comment">// 第一次调用函数时的第一个参数是 0</span> expect(mockCallback.mock.calls[<span class="hljs-number">0</span>][<span class="hljs-number">0</span>]).toBe(<span class="hljs-number">0</span>); <span class="hljs-comment">// 第二次调用函数时的第一个参数是 1</span> expect(mockCallback.mock.calls[<span class="hljs-number">1</span>][<span class="hljs-number">0</span>]).toBe(<span class="hljs-number">1</span>);
});
除此之外我们上面的测试中的模拟函数 mockCallback 还有一个 mock 属性,它它保存了此函数被调用的一系列信息,我们把它打印出来: console.log(mockCallback.mock)。
mockCallback
mock
console.log(mockCallback.mock)
结果如下:
{ calls: [ [ 0 ], [ 1 ] ], instances: [ undefined, undefined ], invocationCallOrder: [ 1, 2 ], results: [ { type: 'return', value: undefined }, { type: 'return', value: undefined } ] }
我们通过 Jest 的安装、配置、使用 学习了在 TypeScript 下进行简单单元测试的内容,严格的代码检测、静态类型检查配合上充分的单元测试,这是保证项目健壮性、可维护性的根本。
参考:
The text was updated successfully, but these errors were encountered:
No branches or pull requests
TypeScript 工程化:单元测试
单元测试是现代软件工程中必备的环节之一,通常一定规模的团队会有专门的测试人员,但是一些精悍的团队也会选择开发人员自行测试。
虽然 TypeScript 的类型检查和 ESLint 的代码检测已经让我们的代码足够强大,但是这仅仅停留在类型和语法层面,我们无法保证逻辑的正确性,这就需要单元测试来保证我们的逻辑的健壮性了。
单元测试工具的选择
在 JS/TS 的世界里有太多的单元测试框架可供选择了,我们逐一进行对比,来选出一个适合我们的框架。
Mocha
Mocha 可能是当前被使用最多的单元测试工具,他最大的优点就是灵活,它只提供给开发者的只有一个基础测试结构。
然后其它功能性的功能如 assertions, spies,mocks,和像它们一样的其它功能需要引用添加其它库/插件来完成。
它的缺点也是他的优点,我们需要额外的引入众多辅助库和插件,这无形中增加了我们的学习成本和配置成本。
除此之外, Mocha 的另一大亮点是对异步的强大支持,Mocha 毕竟是从一开始为 Node.js 而生的单元测试框架,对浏览器的支持并不如其对服务器做的那么好。
如果你需要一个高度定制的测试框架,Mocha 是非常好的选择。
Jasmine
相比于 Mocha 需要进行额外配置,另一个比较老牌的测试框架 Jasmine 主打的则是开箱即用,它内置了一些断言库和 mocks 工具,并提供了全局变量非常方便我们的测试。
Jest
Jest 是一个真正意义上开箱即用的测试框架,它集成了几乎单元测试中我们需要的所有功能,比如:断言、测试覆盖率统计、快照等等一系列功能。
比较友好地支持各种环境,目前前端三大框架都采用了 Jest 作为测试工具,它的优点如下:
Jest 正是基于 Jasmine 开发而来,如果你喜欢 Jasmine,那么为什么不用 Jest,他比 Jasmine 更加大而全,更加开箱即用。
我们的选择
其实在我们面前有两条路,一条是灵活配置但是学习成本陡峭的 Mocha,另一条是大而全开箱即用,却没那么灵活的 Jest,多数没有特殊要求的情况下我认为 Jest 会更适合我们,测试框架到底是一个工具,我们选择一个几乎不需要配置、开箱即用的框架是可以大大提高我们生产效率的选择。
Jest 配置
安装 Jest
全局安装 Jest:
在项目中安装 Jest:
初始化 Jest
我们在项目的根目录下初始化 jest:
我们会被问到三个问题,我的选择如下:
第一个问题需要我们选择测试执行环境,有浏览器和 Node 两个选项,我们这次在简单的 Node 环境下测试,所以选择了 node。
第二个问题问我们是否需要测试覆盖率报告,通常情况下这个报告很重要,使我们整体测试情况的写一个报告,我选择了“是”。
第三个问题问我们是否在测试结束后帮我们自动清除一些模拟的实例等等,我选择了“是”,避免这些东西影响我们的下次测试。
配置项
这个时候我们的根目录下已经生成了一个叫
jest.config.js
的配置文件,我们看到里面有非常多的配置项。比如
clearMocks: true
就是我们刚才的问题中用于清除模拟残留的配置,coverageDirectory: 'coverage'
就是我们刚才选择的测试覆盖率报告的配置,testEnvironment: 'node'
是我们刚才选择的测试环境。以上只是基础配置,我们如果想要在 TypeScript 中使用,需要进一步的配置。
我们需要在配置中加入以下选项:
moduleFileExtensions
: 模块文件扩展名,当你去引入一个模块并没有指定拓展名的时候,它会依次尝试去添加这些扩展名去拟引入模块文件transform
: 一种转换器配置, 由于 Jest 默认的转换器不支持 TypeScript,因此需要 ts-jest 工具把.ts
和.tsx
文件内容转换成 js,因为我们现在基本上也都是用 ts 去编写测试代码,所以要配置转换器testMatch
: 设置识别哪些文件是测试文件(glob形式)以上就是我们需要配置的配置项,当然还有一些配置项我们并没有涉及,由于篇幅所限,且其重要性也有限,感兴趣的可以移步官方的配置 Jest进一步学习。
Jest 的使用
Jest 其实有很多强大的功能,我们并不是专门讲解 Jest 的书,因此只会讲解一下重点内容,更多的资料可以移步Jest 官网。
匹配器的使用
我们在新建一个目录
src/
,如何在此目录下新建一个文件add.ts
,开始进行编码如下:我们实现一个非常简单的累加函数,然后开始进行测试。
我们先创建一个文件
add.test.ts
,我们使用一个最简单的匹配器:在此代码中,
expect(add(2,2))
返回一个"期望"的对象,.toBe(4)
则是匹配器,我们期望这个函数运行的结果是4
,如果匹配失败,Jest运行时,它会跟踪所有失败的匹配器,以便它可以为你打印出很好的错误消息。那么如果我们想测试相反的匹配那么可以:
我们运行一下
npm run test
,如下:在编写代码的过程中你会发现 TypeScript 的提示会显示非常多的匹配器,这也是 TypeScript 的优势之一,我们通常根本记不住如此繁多的 API,但是优秀的代码提示会帮助我们快速使用,而不必去一个个查阅官方的文档。
我们匹配完了简单的数字,接下来可以匹配对象了。
我们新建一个文件
src/person.ts
,如下;同样的我们新建一个测试文件
person.test.ts
:toBeInstanceOf
用于匹配实例person
是否是由Person
构造的,toEqual
是比较两个对象是否相同。异步测试
新建一个文件
src/getTopics.ts
,我们就声明一个函数,用于读取 Cnode 论坛的首页主题帖:我们新建一个测试文件
src/getTopics.test.ts
,若要编写 async 测试,只要在函数前面使用 async 关键字传递到 test :异步测试在 Node 服务端的测试场景中非常常用。
模拟函数
我们再新建一个文件
src/myForEach.ts
,在这里我们创建一个遍历函数:为了测试此函数,我们可以使用一个模拟函数,然后检查模拟函数的状态来确保回调函数如期调用。
在
src/myForEach.test.ts
中,我们可以用jest.fn()
模拟函数,来测试调用情况:除此之外我们上面的测试中的模拟函数
mockCallback
还有一个mock
属性,它它保存了此函数被调用的一系列信息,我们把它打印出来:console.log(mockCallback.mock)
。结果如下:
小结
我们通过 Jest 的安装、配置、使用 学习了在 TypeScript 下进行简单单元测试的内容,严格的代码检测、静态类型检查配合上充分的单元测试,这是保证项目健壮性、可维护性的根本。
参考:
The text was updated successfully, but these errors were encountered: