TypeScript:JavaScript 库使用与自动化测试指南
1. JavaScript 库类型定义
在使用 JavaScript 库时,大多数流行的库已经在 Definitely Typed 上有类型定义,并通过 NPM 上的 Types 组织发布。但如果遇到未列出的新库或特殊库,就需要自己创建类型定义。
1.1 创建类型定义的方法
采用迭代/增量的方法编写类型定义,可以让投入的时间和精力获得最大回报。可以通过库的文档查找类型信息,或通过阅读示例来推断。
1.2 关键要点
- 文件扩展名 :类型定义通常放在扩展名为
.d.ts的文件中。 - 增量创建 :可以逐步创建新的类型定义,无需一次性为整个库生成类型信息。
- 包含 JavaScript 文件 :可以在 TypeScript 编译中包含 JavaScript 文件。
- 升级文件 :通常将文件从 JavaScript 升级到 TypeScript 比创建类型定义文件更容易。
- 发现并修复错误 :由于 JavaScript 是完全动态的,升级到 TypeScript 时可能会发现并修复之前未知的错误。
2. 自动化测试的重要性
自动化测试对于编写大型应用程序至关重要。通过自动化测试,开发者可以将更多时间花在新功能开发上,减少修复缺陷的时间。同时,自动化测试也是代码重构的关键。
2.1 不同测试类型的缺陷检测率
不同类型的测试在缺陷检测率上有所不同:
| 测试类型 | 缺陷检测率 |
| ---- | ---- |
| 单元测试 | 高达 50% |
| 集成测试 | 高达 40% |
| 回归测试 | 高达 30% |
从这些数据可以看出,在软件开发生命周期后期进行测试,更多缺陷可能会漏检,而且后期检测到的缺陷修复成本更高。因此,测试优先编程可能是减少错误的有效方法之一。
2.2 测试驱动设计(TDD)
TDD 的支持者认为,测试是一种额外的收获,而不是 TDD 的主要目的。TDD 是一种有助于设计内聚代码单元的工具。
3. 测试框架选择
有许多用 JavaScript 编写的高质量测试框架可用于测试程序,以下是三个最流行的框架:
- Jasmine
- Mocha
- Jest
本文示例使用 Jest 进行测试,它是一个易于设置的测试框架,与 React 框架紧密相关,因此受到了广泛关注。
4. 使用 Jest 进行测试
4.1 安装 Jest
将 Jest 添加到项目中的最简单方法是从 NPM 获取包和类型定义。可以通过将这些包添加到开发依赖项中来实现,如下所示:
{
"name": "fizzbuzz",
"version": "1.0.0",
"devDependencies": {
"@types/jest": "^21.1.0",
"jest": "^21.1.0"
},
"scripts": {
"test": "jest"
}
}
下载这些包后,就可以开始编写代码了。
4.2 第一个规范
以 FizzBuzz 游戏为例,首先实现一个简单的 FizzBuzz 类:
// FizzBuzz.ts
export class FizzBuzz {
generate(input: number) {
return 1;
}
}
对应的 Jest 测试如下:
// FizzBuzz.test.ts
import { FizzBuzz } from './FizzBuzz';
describe('A FizzBuzz generator', () => {
it('should return the number 1 when 1 is played', () => {
const fizzBuzz = new FizzBuzz();
const result = fizzBuzz.generate(1);
expect(result).toBe(1);
});
});
Jest 会在扩展名为 .test.js 的文件中查找测试(排除 node_modules 文件夹中的文件),因此文件名很重要。运行 Jest 只需在项目文件夹中执行以下命令:
npm test
4.3 驱动实现过程
- 添加第二个规范 :当输入 2 时,期望返回 2。
import { FizzBuzz } from './FizzBuzz';
describe('A FizzBuzz generator', () => {
it('should return the number 1 when 1 is played', () => {
const fizzBuzz = new FizzBuzz();
const result = fizzBuzz.generate(1);
expect(result).toBe(1);
});
it('should return the number 2 when 2 is played', () => {
const fizzBuzz = new FizzBuzz();
const result = fizzBuzz.generate(2);
expect(result).toBe(2);
});
});
由于当前 FizzBuzz 类硬编码返回 1,第二个测试会失败。为了通过测试,需要更新 FizzBuzz 类:
export class FizzBuzz {
generate(input: number) {
return input;
}
}
- 添加 Fizz 规范 :当输入 3 时,期望返回 “Fizz”。
it('should return "Fizz" when 3 is played', () => {
const fizzBuzz = new FizzBuzz();
const result = fizzBuzz.generate(3);
expect(result).toBe('Fizz');
});
更新 FizzBuzz 类以通过此测试:
class FizzBuzz {
generate(input: number) : string | number {
if (input === 3) {
return 'Fizz';
}
return input;
}
}
4.4 重构代码
当编写了多个规范并实现了相应代码后,就可以对程序进行重构。重构代码是指在不改变程序行为的前提下,改变程序的结构和设计。
为了确保重构没有改变程序的实际功能,需要运行自动化测试。以下是重构后的规范:
import { FizzBuzz } from './FizzBuzz';
describe('A FizzBuzz generator', () => {
let fizzBuzz: FizzBuzz;
beforeEach(() => {
fizzBuzz = new FizzBuzz();
});
it('should return the number 1 when 1 is played', () => {
const result = fizzBuzz.generate(1);
expect(result).toBe(1);
});
it('should return the number 2 when 2 is played', () => {
const result = fizzBuzz.generate(2);
expect(result).toBe(2);
});
it('should return "Fizz" when 3 is played', () => {
const result = fizzBuzz.generate(3);
expect(result).toBe('Fizz');
});
});
4.5 完整的 FizzBuzz 实现
以下是一个完整的 FizzBuzz 类实现,涵盖了默认规则以及 Fizz、Buzz 和 FizzBuzz 三种变体:
export class FizzBuzz {
generate(input: number): string | number {
let output = '';
if (input % 3 === 0) {
output += 'Fizz';
}
if (input % 5 === 0) {
output += 'Buzz';
}
return output === '' ? input : output;
}
}
对应的规范如下:
import { FizzBuzz } from './FizzBuzz';
describe('A FizzBuzz generator', () => {
let fizzBuzz: FizzBuzz;
const FIZZ = 'Fizz';
const BUZZ = 'Buzz';
const FIZZ_BUZZ = 'FizzBuzz';
beforeEach(() => {
fizzBuzz = new FizzBuzz();
});
it('should return the number 1 when 1 is played', () => {
const result = fizzBuzz.generate(1);
expect(result).toBe(1);
});
it('should return the number 2 when 2 is played', () => {
const result = fizzBuzz.generate(2);
expect(result).toBe(2);
});
it('should return "Fizz" when 3 is played', () => {
const result = fizzBuzz.generate(3);
expect(result).toBe(FIZZ);
});
it('should return "Fizz" when 6 is played', () => {
const result = fizzBuzz.generate(6);
expect(result).toBe(FIZZ);
});
it('should return "Buzz" when 5 is played', () => {
const result = fizzBuzz.generate(5);
expect(result).toBe(BUZZ);
});
it('should return "Buzz" when 10 is played', () => {
const result = fizzBuzz.generate(10);
expect(result).toBe(BUZZ);
});
it('should return "FizzBuzz" when 15 is played', () => {
const result = fizzBuzz.generate(15);
expect(result).toBe(FIZZ_BUZZ);
});
it('should return "FizzBuzz" when 30 is played', () => {
const result = fizzBuzz.generate(30);
expect(result).toBe(FIZZ_BUZZ);
});
});
4.6 依赖隔离
在测试代码时,可能会遇到依赖外部资源的情况,这会使测试变得脆弱。例如,代码可能依赖第三方 API 或特定状态的数据库。为了在不依赖这些依赖项的情况下进行测试,可以使用以下技术进行隔离。
以下是一个依赖 localStorage 的 FizzBuzz 类:
class FizzBuzz {
constructor(private storage: Storage) {
}
generate(input: number): string | number {
if (input === 3) {
return this.storage.getItem('FizzText');
}
return input;
}
}
可以使用一个简单的对象来满足这个依赖:
describe('A FizzBuzz generator', () => {
it('should return "FakeFizz" when 3 is played', () => {
// Create a test double for storage
var storage: any = {
getItem: () => 'FakeFizz'
};
const fizzBuzz = new FizzBuzz(storage);
const result = fizzBuzz.generate(3);
expect(result).toBe('FakeFizz');
});
});
总体而言,应使用简单对象作为测试替身,测试应检查结果,而不是具体的实现细节。
5. 总结
自动化测试的价值不言而喻。如果仍然对其效果存疑,可以尝试在有测试和无测试的情况下运行编码练习,以帮助做出决策。
虽然本文示例使用了 Jest,但使用 Mocha 或 Jasmine 同样简单,它们都提供了简洁的语法。无论选择哪种测试框架,都应使测试输出具有良好的可读性,以便在需要时作为文档提供。
此外,还有一个基于 Gherkin 语言的行为驱动框架 TypeSpec,可用于结合业务规范和测试自动化。可以在 GitHub 上了解更多信息: https://github.com/Steve-Fenton/TypeSpec 。
通过掌握 JavaScript 库的类型定义和自动化测试技术,可以提高代码的质量和可维护性,减少错误,使开发过程更加高效。
6. 自动化测试的流程总结
为了更清晰地展示使用 Jest 进行自动化测试的流程,下面给出一个 mermaid 格式的流程图:
graph LR
A[安装 Jest] --> B[编写测试规范]
B --> C{运行测试}
C -->|失败| D[更新代码实现]
D --> B
C -->|通过| E[重构代码]
E --> F[再次运行测试]
F -->|失败| D
F -->|通过| G[完成测试]
这个流程图展示了一个典型的测试驱动开发(TDD)流程,从安装 Jest 开始,编写测试规范,运行测试,如果测试失败则更新代码实现,直到测试通过。然后进行代码重构,再次运行测试确保重构没有改变程序行为,最终完成测试。
7. 不同测试类型的比较
| 测试类型 | 检测缺陷比例 | 特点 | 适用场景 |
|---|---|---|---|
| 单元测试 | 高达 50% | 针对程序中的最小可测试单元进行测试,如函数、方法等。测试粒度小,能快速定位问题。 | 开发过程中,对单个功能模块进行验证。 |
| 集成测试 | 高达 40% | 测试多个模块之间的交互和协作,检查模块之间的接口是否正常工作。 | 当多个模块开发完成后,验证它们之间的集成是否正确。 |
| 回归测试 | 高达 30% | 在代码修改后,重新运行之前的测试用例,确保修改没有引入新的缺陷。 | 每次代码更新后,确保原有功能仍然正常工作。 |
从这个表格可以看出,不同的测试类型在缺陷检测能力和适用场景上有所不同。在实际开发中,通常需要结合多种测试类型来确保软件的质量。
8. 测试代码的最佳实践
8.1 测试代码的可读性
测试代码应该像文档一样易于理解,使用清晰的描述和有意义的变量名。例如,在 FizzBuzz 测试中,使用 FIZZ 、 BUZZ 和 FIZZ_BUZZ 常量来提高代码的可读性:
import { FizzBuzz } from './FizzBuzz';
describe('A FizzBuzz generator', () => {
let fizzBuzz: FizzBuzz;
const FIZZ = 'Fizz';
const BUZZ = 'Buzz';
const FIZZ_BUZZ = 'FizzBuzz';
beforeEach(() => {
fizzBuzz = new FizzBuzz();
});
it('should return the number 1 when 1 is played', () => {
const result = fizzBuzz.generate(1);
expect(result).toBe(1);
});
// 其他测试用例...
});
8.2 测试代码的可维护性
避免测试代码中的重复,使用 beforeEach 等方法来减少重复代码。例如,将 FizzBuzz 类的实例化移到 beforeEach 方法中:
import { FizzBuzz } from './FizzBuzz';
describe('A FizzBuzz generator', () => {
let fizzBuzz: FizzBuzz;
beforeEach(() => {
fizzBuzz = new FizzBuzz();
});
it('should return the number 1 when 1 is played', () => {
const result = fizzBuzz.generate(1);
expect(result).toBe(1);
});
// 其他测试用例...
});
8.3 测试代码的独立性
每个测试用例应该是独立的,不依赖于其他测试用例的执行结果。这样可以确保测试的可靠性,避免一个测试用例的失败影响其他测试用例。
9. 总结与展望
自动化测试是提高代码质量和可维护性的重要手段。通过使用 Jest 等测试框架,结合测试驱动开发的方法,可以有效地减少代码中的缺陷,提高开发效率。
在未来的开发中,随着项目的复杂度增加,可能会遇到更多的依赖和复杂的场景。此时,需要进一步掌握依赖隔离和测试替身的技术,确保测试的稳定性和可靠性。同时,不断优化测试代码的质量,使其更易于理解和维护。
希望通过本文的介绍,你能对 JavaScript 库的类型定义和自动化测试有更深入的理解,并在实际开发中应用这些技术,提升自己的开发能力。
超级会员免费看
418

被折叠的 条评论
为什么被折叠?



