搜索
首页web前端js教程JS单元测试讲座-实用指南

这是 Maximilian Schwarzmüller 讲授的《JavaScript 单元测试 - 实用指南》课程的总结。


介绍

深入研究自动化测试可能是一个好主意,因为与手动测试相比,它更具可预测性和一致性。

让我们考虑一个场景,您添加新功能或更改代码的某些部分。

您不一定知道或意识到代码中真正受到影响的每个部分或所有部分。

通过手动测试,我们必须测试(或尝试)整个应用程序,或者至少测试可能受更改影响的部分。为什么?因为我们需要看看一切是否仍然正常工作,并且那个微小的变化或新功能没有破坏某处的代码。

所以,正如我们想象的那样,有很多工作

此外,不能保证需要测试的所有内容都会被测试,并且每次发生变化时,都不能保证以相同的方式进行测试。

通过自动化测试,我们有初步的努力,但后来会带来很多好处。

在当今我们拥有的各种自动化测试中,这里我们将讨论单元测试和集成测试,但主要是单元测试。

让我们将单元视为代码的最小部分,例如函数或类。因此,在单元测试中,顾名思义,我们将测试应用程序的每个单元。因此,如果我们有 50 个函数,我们将为所有或大多数函数创建测试。这是因为我们希望保证每个部分、每个单元都按照最初的预期正常工作。

另一方面,集成测试将关注一起测试这些单元,或者更好地说,它们如何一起工作以及它们是否可以很好地一起工作。这是因为即使我们单独测试这些单元,也不能保证它们能够协同工作或按应有的方式工作。

测试驱动开发 (TDD)

我们还应该了解 TDD,即测试驱动开发的简称。

TDD 是一个框架/哲学,它引导我们考虑首先编写失败的测试,然后实现使测试成功的代码。然后重构,作为一个循环的事情。

一个提醒

让你的测试保持简单!

当其他人需要阅读你的代码,甚至在未来的场景中需要阅读你的代码时,重要的是不要花太长时间来理解。这必须是一项简单的任务。

维泰斯特

对于本指南,我们将使用 Vitest,这是一个基于著名的 Jest 应用程序的工具,用于测试等。在这里,它并不是要深入 Vitest 语法或所有功能,而是作为理解测试核心的工具。

如果您想了解或查看 Vitest 可以帮助我们的所有内容,请访问以下链接的文档:Vitest 文档

暗示

Vitest 的工作方式与 Webpack 捆绑工具类似,因此在使用 ES 模块表示法时,我们实际上不需要显式告知要导入的文件的扩展名。例如:

从“./math/Math”导入数学

相反的是:

从 './math/Math.js' 导入数学


良好做法

这里有一个小指南,可引导您在编写测试例程中进行良好实践。

编写好的测试

单元和集成测试可能非常有用,但前提是编写得好。为此,我们可以遵循以下将探讨的一系列“良好实践”:

只测试你的代码

当我们谈论测试我们自己的代码时,这意味着第三方代码不是我们测试的责任。嗯,它是供第三方代码编写者使用的,以确保其正常工作,因此测试它是他们的责任。

而且因为你无法更改它,所以测试它没有用。

了解如何区分测试

您不会通过客户端代码隐式测试服务器端代码。

您将测试客户端对不同响应和错误的反应。

因此,将您的测试、前端开发测试和后端开发测试分开。

AAA

  • 安排:定义测试环境和值
  • Act:运行应该测试的实际代码/函数
  • 断言:评估产生的值/结果并将其与预期值/结果进行比较。

这里有一个 Vitest 代码示例:

import { expect, it } from 'vitest';
import { toSum } from './math';

it('should sum two numbers', () => {
    //Arrange
    const num1 = 1;
    const num2 = 2;

    //Act
    const result = toSum(num1, num2);

    //Assert
    const expectedResult = num1 + num2;

    expect(result).toBe(expectedResult);
});

只是为了澄清代码的作用,以防您不知道。

首先,我们从 vitest 导入“expect”和“it”,这是我们需要的功能,以及“toSum”函数,这是为示例构建的函数,但位于另一个文件中。

The "it" works as the scope for our test; it receives a string that behaves as the identifier and a function that will run the test code. Here is very simple; we are saying that it should sum two numbers, that's our expectation for the function and for the test.

In the arrange part we create the variables which will be passed to the "toSum" function. Then, in the act part, we create a constant that will receive the result of the function. Finally, in the assert, we will store the expected result, which would be the sum of the two numbers and use "expect" from Vitest to make the assertion. Basically, we are saying that we expect the result to be the same as the expected result.

There are many assertion possibilities, so please do check the documentation for further study.

Note: "it" is an alias for "test"

Also, it's very important the following line:

const expectedResult = num1 + num2;

Imagine if we've done it like this:

const expectedResult = 3;

It's okay for the current test because we are indeed expecting 3 as the result.

But, imagine in the future, someone changes "num1" or "num2", and forgets to change the "expectedResult"; it would not work if the result of the sum was not 3.

Essence of what is being tested

If you, for example, created a function that is going to receive an array of numbers as an argument and you need to test if it actually received an array of numbers in your test file, you just need to reference an array, for example:
const items = [1, 2];

You don't need to create a bigger array, for example:
const items = [1, 2, 3, 4, 5, 6, 7, 8];

It's unnecessary and redundant. Keep it short, simple and concise, so you will make sure that you are only testing what needs to be tested or what is important for the function.

Test one thing

You can think of one thing as one feature or one behavior. For example, if you have a function that will sum two numbers (the same example above) and you need to make sure that is summing two numbers (because indeed that's the main functionality) but also that the output is of type number, then, you can separate it into two assertions, for example:

import { describe, expect, it } from 'vitest';
import { toSum } from './math';

describe('toSum()', () => {
 it('should sum two numbers', () => {
    const num1 = 1;
    const num2 = 2;

    const result = toSum(num1, num2);

    const expectedResult = num1 + num2;

    expect(result).toBe(expectedResult);
 });

 it('should output a result of type number', () => {
    const num1 = 1;
    const num2 = 2;

    const result = toSum(num1, num2);

    const expectedResult = num1 + num2;

    expect(result).toBe(expectedResult);
 });

})

If you're wondering what describe does, it help us to create suites. As many suites as we want, like dividing blocks of tests. It keeps our code organized, clear, and easier to understand the outputting.

Here's an example using the toSum function:

Lectures of JS Unit Testing - The Practical Guide

As you can see in the image above, it will show us the file path, and after that the "describe" name, and then the "it" name. It's a good idea to keep the describer name short and referencing the function to what the tests are about.

And you could have describers inside describers to organize even further, it's up to you.

SO,

when we create our tests following good practices, we are creating tests that will actually help us on what's needed to be tested. And also, testing forces us to write better code. For that, we can write good functions that will hold only the logic of that function so it'll be easier to test what's need to be tested.

Code coverage

It's important to understand also that coverage doesn't mean good testing or testing that is useful and meaningful for the application. Well, you could cover 100% of your code with meaningless tests after all, or, missing important tests that you didn't think of.

Don't see a high amount of code coverage as the ultimate goal!

You will want to try and test cover the majority of the units (functions or classes) in your application, because that's what unit testing is about, but, there is some code that doesn't need to be tested.

Vitest comes with a built-in functionality to help us measure the code coverage; you can access in the following link: Vitest coverage tool


Callbacks and Async Functions

As callbacks and async functions exhibit specific behavior in Vitest, this section is dedicated to exploring them superficially.

When testing for a callback, keep in mind that Vitest does not wait for the response or for the callback to be executed. Therefore, we need to use the "done" argument.

Consider the following test as an example:

import { expect, it } from 'vitest';
import { generateToken } from './async-example';

it('should generate a token value', (done) => {
   const email = 'test@mail.com';

   generateToken(email, (err, token) => {
       expect(token).toBeDefined();
       done()
   })

})

Now, we are working with a callback function. Notice that there's a parameter being passed. The "done".

Vitest now needs to wait until the done function is called.

What would happen if we didn't use the "done" argument? The "expect" wouldn't be executed.

Try and catch

Still in that function, imagine if we changed toBeDefined to toBe, as in the image below:

import { expect, it } from 'vitest';
import { generateToken } from './async-example';

it('should generate a token value', (done) => {
   const email = 'test@mail.com';

   generateToken(email, (err, token) => {
       expect(token).toBe(2);
       done();
   });
})

By default, in Vitest, the "toBe" function throws an error each time something doesn't go as expected, in this case, if the token returned wasn't 2.

However, as we are working with a callback, we will need to add an exception handling syntax, such as "try and catch", because if we don't do so, the test will timeout.

import { expect, it } from 'vitest';
import { generateToken } from './async-example';

it('should generate a token value', (done) => {
   const email = 'test@mail.com';

   try {
     generateToken(email, (err, token) => {
         expect(token).toBe(2);
     });
   } catch (error) {
       done(error);
   }
})

Since we are dealing with an error, we also need to pass this error to the "done" function.

Promises

Now, when working with promises, it's a bit easier, or should we say, simpler.

import { expect, it } from 'vitest';
import { generateTokenPromise } from './async-example';

it('should generate a token value', () => {
  const email = 'test@mail.com';

  return expect(generateTokenPromise(email)).resolves.toBeDefined();

  //or

  return expect(generateTokenPromise(email)).rejects.toBe();
});

Here we have two possibilities: resolves and rejects

The "return" statement guarantees Vitest waits for the promise to be resolved.

Alternatively, we have:

import { expect, it } from 'vitest';
import { generateTokenPromise } from './async-example';

it('should generate a token value', async () => {
  const email = 'test@mail.com';

  const token = await generateTokenPromise(email);

  expect(token).resolves.toBeDefined();

  // or

  expect(token).rejects.toBe();
})

Here we don't need to use "return" because "async/await" is being used (since a function annotated with "async" returns a promise implicitly).


Hooks, spies and mocks

Here, we are going to explore a little bit of these important functionalities that Vitest provides to us.

Hooks

Imagine working with a bunch of tests that use the same variable, and you don't want to initialize it every single time in every single test because it's the same.

Hooks can help us in this case because you can use functions provided by it that allow us to reuse this variable.

Functions available: "beforeAll", "beforeEach", "afterEach", "afterAll".

Here goes a simple example just to show how it works:

import { beforeEach } from 'vitest';

let myVariable;

beforeEach(() => {
  myVariable = "";
});

it('sentence', () => {
  myVariable = "Hello";
});

it('another sentence', () => {
  myVariable += 2;
});

Now, imagine the same but without the hook:

let myVariable;

it('sentence', () => {
  myVariable = "Hello";
});

it('another sentence', () => {
  myVariable += 2;
});

As we can see, when using "beforeEach" and a global variable, before each test starts to execute, the variable will be "cleaned". This allows the tests to use the variable as if it were fresh.

But, without using the hook and using a global variable, in some cases, things would be tricky. In the example of the test "another sentence," if we didn't clean the variable, it would be holding "Hello" because the "sentence" test is run first. And that's not what we want.

Mocks and Spies

Mocks and spies are mainly to handle side effects and external dependencies.

We could say that spies help us deal with side effects in our functions, and mocks help us deal with side effects of external dependencies.

For that, you will have to import "vi" from vitest.

To build a spy, you can use "vi.fn()" and for a mock "vi.mock()". Inside each function, you will pass the name to the other function (your or external).

So, spies and mocks kind of replace the actual functions with other functions or empty functions.

Mocks will be available only for tests of the file you called them and Vitest, behind the scenes, puts them at the start of the file.


Conclusion

In summary, you need to consider what the unit should or should not do. To achieve this, you can utilize the "it" syntax provided by Vitest, which takes a string describing your expectations and a function that will test the given expectations.

The name of the test should be short, simple and easy to understand.

The testing magic lies in thinking about aspects that were not initially considered, leading to code improvement. This process helps prevent errors and promotes a clearer understanding of what the function should do and its expected behaviors.

以上是JS单元测试讲座-实用指南的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
JavaScript的演变:当前的趋势和未来前景JavaScript的演变:当前的趋势和未来前景Apr 10, 2025 am 09:33 AM

JavaScript的最新趋势包括TypeScript的崛起、现代框架和库的流行以及WebAssembly的应用。未来前景涵盖更强大的类型系统、服务器端JavaScript的发展、人工智能和机器学习的扩展以及物联网和边缘计算的潜力。

神秘的JavaScript:它的作用以及为什么重要神秘的JavaScript:它的作用以及为什么重要Apr 09, 2025 am 12:07 AM

JavaScript是现代Web开发的基石,它的主要功能包括事件驱动编程、动态内容生成和异步编程。1)事件驱动编程允许网页根据用户操作动态变化。2)动态内容生成使得页面内容可以根据条件调整。3)异步编程确保用户界面不被阻塞。JavaScript广泛应用于网页交互、单页面应用和服务器端开发,极大地提升了用户体验和跨平台开发的灵活性。

Python还是JavaScript更好?Python还是JavaScript更好?Apr 06, 2025 am 12:14 AM

Python更适合数据科学和机器学习,JavaScript更适合前端和全栈开发。 1.Python以简洁语法和丰富库生态着称,适用于数据分析和Web开发。 2.JavaScript是前端开发核心,Node.js支持服务器端编程,适用于全栈开发。

如何安装JavaScript?如何安装JavaScript?Apr 05, 2025 am 12:16 AM

JavaScript不需要安装,因为它已内置于现代浏览器中。你只需文本编辑器和浏览器即可开始使用。1)在浏览器环境中,通过标签嵌入HTML文件中运行。2)在Node.js环境中,下载并安装Node.js后,通过命令行运行JavaScript文件。

在Quartz中如何在任务开始前发送通知?在Quartz中如何在任务开始前发送通知?Apr 04, 2025 pm 09:24 PM

如何在Quartz中提前发送任务通知在使用Quartz定时器进行任务调度时,任务的执行时间是由cron表达式设定的。现�...

在JavaScript中,如何在构造函数中获取原型链上函数的参数?在JavaScript中,如何在构造函数中获取原型链上函数的参数?Apr 04, 2025 pm 09:21 PM

在JavaScript中如何获取原型链上函数的参数在JavaScript编程中,理解和操作原型链上的函数参数是常见且重要的任�...

微信小程序webview中Vue.js动态style位移失效是什么原因?微信小程序webview中Vue.js动态style位移失效是什么原因?Apr 04, 2025 pm 09:18 PM

在微信小程序web-view中使用Vue.js动态style位移失效的原因分析在使用Vue.js...

在Tampermonkey中如何实现对多个链接的并发GET请求并依次判断返回结果?在Tampermonkey中如何实现对多个链接的并发GET请求并依次判断返回结果?Apr 04, 2025 pm 09:15 PM

在Tampermonkey中如何对多个链接进行并发GET请求并依次判断返回结果?在Tampermonkey脚本中,我们经常需要对多个链...

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前By尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
3 周前By尊渡假赌尊渡假赌尊渡假赌

热工具

SecLists

SecLists

SecLists是最终安全测试人员的伙伴。它是一个包含各种类型列表的集合,这些列表在安全评估过程中经常使用,都在一个地方。SecLists通过方便地提供安全测试人员可能需要的所有列表,帮助提高安全测试的效率和生产力。列表类型包括用户名、密码、URL、模糊测试有效载荷、敏感数据模式、Web shell等等。测试人员只需将此存储库拉到新的测试机上,他就可以访问到所需的每种类型的列表。

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用