阐述单元测试、元件测试,并学习在自己的 Vue3 专案中加入 Vitest!


文章出处

网站建置不是件简单的事,我们都知道网站做好之后,有好多细节需要兼顾,所以许多公司花了大量的时间与金钱,耗用人力对维护中的网站进行不断的、重複的人工测试,想达到的目的不外乎是希望网站不要出错,可以给客户/使用者最好的网站使用体验。本篇章说明使用 Vue3 开发的专案,导入 Vitest 进行极速单元测试的体验!


测试的种类

在不同的测试类型中,所需要保护的面向不太相同,以下是不同测试类型的大致介绍:

单元测试 (Unit testing)

以程式码的最小单位进行测试,保护程式逻辑不会在系统维护的过程中遭到破坏,也进一步确保维护中的程式码品质。

这种测试类型通常由开发人员自行撰写,自己写的 Code 自己写测试,有经验的开发人员可以用非常有效率的方式撰写单元测试,因为测试範围小,这种类型的测试通常不需要设立测试环境,因此可以得到较高的撰写效率,也是所有测试类型中最容易撰写的测试类型。不过,对于一个没有经验的开发者来说,撰写单元测试可能会耗用大量时间,写测试程式的时间很有可能会远大于实际撰写程式码的时间,有蛮多人会因为这样而放弃撰写单元测试。

整合测试 (Integration testing)

整合多方资源进行测试,确保模组与模组之间的互动行为正确无误,也让不同模组在各自开发维护的过程中不会因为功能调整而遭到破坏。

这种类型的测试通常介于单元测试与端对端测试之间,有时候会由专职的测试人员进行开发,但大部分还是由开发团队中负责特定模组的人来撰写。有时候单一模组即便完全通过单元测试,独立运作也正常,但是当需要与其他模组互动时,也是有可能发生错误,这时就是整合测试的主要负责领域。

以下是未通过整合测试的案例:(两个元件在整合时发现问题)

端对端测试 (End-to-end testing) (E2E testing)

所谓的「端对端」(E2E) 是指从使用者的角度出发(一端),对真实系统(另一端)进行测试。

这种类型的测试对许多公司来说,就是「人工测试」的主要範围,因为你可以透过人工对已经完整部属的网站进行测试,因此可以验证出系统是否符合客户的实际需求。这部分也可以透过撰写 E2E 测试程式来进行自动化,增加测试效率。

这里有个未通过 E2E 测试的案例相当有趣,虽然每个整套系统每个模组都通过所有单元测试与整合测试,但最后组装起来后,从使用者的角度无法接受!


测试案例中的 3A 模式

describe('猫咪', () => {  it('摸摸,应该会发出「呼噜噜」声', () => {      // ...  })  it('餵食,应该会发出「呼噜噜」声', () => {      // ...  })  it('拿玩具逗,应该会发出「呼噜噜」声', () => {      // ...  })  it('什么都不做,应该推倒眼前看到的所有东西', () => {      // ...  })})

当我们设立好测试情境与测试案例的叙述结构之后,要开始撰写测试案例内部的实作时就可以利用所谓的 3A 模式来安排。

3A 模式主要是为(Arrange-Act-Assert)三个英文字的缩写,而他们分别代表了:

準备(Arrange):準备好受测目标需要的一切,包含依赖的隔离等操作(Act):操作受测物目标断言(Assert):预期受测物的某个状态是否为我们所预期

以上方第一项测试案例来套用 3A 模式的话就会像是:

it('摸摸,应该会发出「呼噜噜」声', () => {  // Arrange: 準备好一只猫  const target = new Cat()  // Act: 摸摸那只猫咪  target.touch()  // Assert: 观察那只猫是否发出呼噜噜叫声  expect(target.speaking).toBe('呼噜噜')})

那如果这时候你可能会想到每个测试案例都要準备一只猫猫,对于测试案例来说就会一直不断地去做「準备(Arrange)」这个行为,因此你可能会很直觉的这么处理:

describe('猫咪', () => {  const target = new Cat()  it('摸摸,应该会发出「呼噜噜」声', () => {    target.touch()    expect(target.speaking).toBe('呼噜噜')  })  it('餵食,应该会发出「呼噜噜」声', () => {    target.feed('乾乾')    expect(target.speaking).toBe('呼噜噜')  })  it('拿玩具逗,应该会发出「呼噜噜」声', () => {    target.play()    expect(target.speaking).toBe('呼噜噜')  })})

但这样的后果就是每个测试案例之间就会有关联了,比方猫猫其实摸太多下他也会觉得厌烦,从而导致测试失败:

describe('猫咪', () => {  const target = new Cat()  it('摸摸下巴,应该会发出「呼噜噜」声', () => {    target.touch()    expect(target.speaking).toBe('呼噜噜')  })  it('再摸一次下巴,应该会发出「呼噜噜」声', () => {    target.touch()    expect(target.speaking).toBe('呼噜噜')  })  it('再摸一次下巴,应该会发出「呼噜噜」声', () => {    target.touch()    expect(target.speaking).toBe('呼噜噜') // 预期呼噜噜,结果猫咪生气了  })})

而要写好测试案例的其中几个概念就是要尽量让每个测试案例之间「不受顺序影响测试结果」与「保持独立」。

因此大多数的「测试环境」的工具都会提供类似相关的 API 来协助处理测试开始前的「Setup」与结束后的「Teardown」环节。

describe('猫咪', () => {  const target = new Cat()  beforeEach(() => {    // 每个测试案例开始前要做的事情    target.init() // 初始化猫猫的各种状态  })  afterEach(() => {    // 每个测试案例结束后要做的事情  })  it('摸摸,应该会发出「呼噜噜」声', () => {    target.touch() // 这时候的 target 已经是经过 init() 的版本了    expect(target.speaking).toBe('呼噜噜')  })  it('餵食,应该会发出「呼噜噜」声', () => {    target.feed('乾乾') // 这时候的 target 已经是经过 init() 的版本了    expect(target.speaking).toBe('呼噜噜')  })  it('拿玩具逗,应该会发出「呼噜噜」声', () => {    target.play() // 这时候的 target 已经是经过 init() 的版本了    expect(target.speaking).toBe('呼噜噜')  })})

综合上述 3A 与处理 Setup & Teardown 的观念,之后再写测试案例时,我们可以先从基础的 3A 模式开始撰写,而到有需要处理重複的事前準备(Setup)与后续清理时(Teardown),就可以藉由工具来替我们统一处理。

看到这边读者应该会发现,测试的基本概念其实不难懂,而在了解测试的概念后,剩下的就是把概念转换为测试工具可读懂测试程式码就好了!


专案加入 Vitest

因文章篇幅限制,转站到我的部落格继续阅读...


作者:Wayne (伟恩)
连结:https://wayne-blog.com/
来源:Wayne's blog | 伟恩的部落格 | 技术博客



关于作者: 网站小编

码农网专注IT技术教程资源分享平台,学习资源下载网站,58码农网包含计算机技术、网站程序源码下载、编程技术论坛、互联网资源下载等产品服务,提供原创、优质、完整内容的专业码农交流分享平台。

热门文章