javascript 代码
We all know we should write unit tests. But, it's hard to know where to start and how much time to devote to tests compared to actual implementation. So, where to start? And is it just about testing code or do unit tests have other benefits?
我们都知道我们应该编写单元测试。 但是,与实际实施相比,很难知道从哪里开始以及花多少时间进行测试。 那么,从哪里开始呢? 仅仅是测试代码还是单元测试还有其他好处?
In this article, I will explain the different types of tests, and which benefits unit testing brings to development teams. I'll showcase Jest - a JavaScript testing framework.
在本文中,我将解释不同类型的测试,以及单元测试给开发团队带来的好处。 我将展示Jest-一个JavaScript测试框架。
不同类型的测试 (Different types of testing)
Before we dive into unit testing specifics, I want to do a quick run through of the different types of tests. There is often some confusion around them and I'm not surprised. Sometimes the line between them is quite thin.
在深入探讨单元测试细节之前,我想快速介绍一下不同类型的测试。 他们周围经常会有一些困惑,我并不感到惊讶。 有时它们之间的界限很细。
单元测试 (Unit tests)
Unit tests only test a single part of your implementation. A unit. No dependencies or integrations, no framework specifics. They're like a method that returns a link in a specific language:
单元测试仅测试实现的一部分。 一个单位。 没有依赖关系或集成,没有框架细节。 它们就像一个以特定语言返回链接的方法:
export function getAboutUsLink(language){
switch (language.toLowerCase()){
case englishCode.toLowerCase():
return '/about-us';
case spanishCode.toLowerCase():
return '/acerca-de';
}
return '';
}
整合测试 (Integration tests)
At some point, your code communicates with a database, file system or another third party. It could even be another module in your app.
在某些时候,您的代码与数据库,文件系统或其他第三方进行通信。 它甚至可能是您应用程序中的另一个模块。
That piece of implementation should be tested by integration tests. They typically have a more complicated setup that involves preparing testing environments, initializing dependencies, and so on.
该实现部分应通过集成测试进行测试。 它们通常具有更复杂的设置,其中涉及准备测试环境,初始化依赖关系等。
功能测试 (Functional tests)
Unit tests and integration tests give you confidence that your app works. Functional tests look at the app from the user's point of view and test that the system works as expected.
单元测试和集成测试使您对应用程序的运行充满信心。 功能测试从用户的角度来看应用程序,并测试系统是否按预期工作。
In the diagram above, you see that unit tests form the large base of your application's testing suite. Typically, they are small, there are a lot of them, and they are executed automatically.
在上图中,您可以看到单元测试构成了应用程序测试套件的基础。 通常,它们很小,有很多,它们是自动执行的。
So now let's get into unit tests in a bit more detail.
现在,让我们更详细地进行单元测试。
为什么要打扰单元测试? (Why Should I Bother Writing Unit Tests?)
Whenever I ask developers whether they wrote tests for their application, they always tell me: "I did not have time for them" or "I don't need them, I know it works."
每当我问开发人员是否为他们的应用程序编写了测试时,他们总是告诉我:“我没有时间为他们服务”或“我不需要它们,我知道这是可行的”。
So I smile politely and tell them what I want to tell you. Unit tests are not only about testing. They help you in other ways, too, so you can:
所以我礼貌地微笑着,告诉他们我想告诉你什么。 单元测试不仅涉及测试。 他们也以其他方式帮助您,因此您可以:
Be confident that your code works. When was the last time you committed a code change, your build failed, and half of your app stopped working? Mine was last week.
确信您的代码有效。 上一次提交代码更改的时间是什么时候,构建失败,并且一半的应用程序停止工作? 我的是上周。
But that's still OK. The real problem is when the build succeeds, the change is deployed, and your app starts being unstable.
但这还是可以的。 真正的问题是,当构建成功,部署更改并且您的应用开始不稳定时。
When that happens, you start losing confidence in your code and eventually just pray for the app to work. Unit tests will help you discover issues much sooner and gain confidence.
发生这种情况时,您将开始对代码失去信心,最终只能祈祷该应用程序正常运行。 单元测试将帮助您更快地发现问题并获得信心。
Make better architectural decisions. Code changes, but some decisions about platform, modules, structure, and others need to be made during the early stages of a project.
做出更好的架构决策。 代码已更改,但是在项目的早期阶段需要做出一些有关平台,模块,结构等的决定。
When you start thinking about unit testing right at the start, it will help you structure your code better and achieve proper separation of concerns. You won't be tempted to assign multiple responsibilities to single code blocks as those would be a nightmare to unit-test.
当您从一开始就开始考虑单元测试时,它将帮助您更好地构建代码并实现关注点的适当分离。 您不会试图将多个职责分配给单个代码块,因为这将是单元测试的噩梦。
Pinpoint functionality before coding. You write the method's signature and start implementing it right away. Oh, but what should happen in case a parameter is null? What if its value is outside of the expected range or contains too many characters? Do you throw an exception or return null?
编码前查明功能。 您编写了方法的签名,并立即开始实现它。 哦,但是如果参数为null怎么办? 如果其值超出预期范围或包含太多字符该怎么办? 您抛出异常还是返回null?
Unit tests will help you discover all these cases. Look at the questions again and you'll find it's exactly what defines your unit test cases.
单元测试将帮助您发现所有这些情况。 再次查看问题,您会发现这正是定义单元测试用例的要素。
I'm sure there are many more benefits to writing unit tests. These are just the ones that I recall from my experience. Those that I learned the hard way.
我相信编写单元测试还有更多好处。 这些只是我从我的经历中回想起的。 那些我很难学的东西。
如何编写您的第一个JavaScript单元测试 (How to Write Your First JavaScript Unit Test)
But let's get back to JavaScript. We will start with Jest, which is a JavaScript testing framework. It's a tool that enables automatic unit testing, provides code coverage, and lets us easily mock objects. Jest also has an extension for Visual Studio Code available here.
但是,让我们回到JavaScript。 我们将从Jest开始,这是一个JavaScript测试框架。 它是一种工具,可以进行自动单元测试,提供代码覆盖范围并让我们轻松地模拟对象。 玩笑也有Visual Studio代码的扩展可以在这里找到 。
There are also other frameworks, if you're interested, you can check them in this article.
还有其他框架,如果您有兴趣,可以在本文中进行检查。
npm i jest --save-dev
Let's use the previously mentioned method getAboutUsLink
as an implementation we want to test:
让我们使用前面提到的方法getAboutUsLink
作为我们要测试的实现:
const englishCode = "en-US";
const spanishCode = "es-ES";
function getAboutUsLink(language){
switch (language.toLowerCase()){
case englishCode.toLowerCase():
return '/about-us';
case spanishCode.toLowerCase():
return '/acerca-de';
}
return '';
}
module.exports = getAboutUsLink;
I put this into the index.js
file. We can write tests in the same file, but a good practice is to separate unit tests into a dedicated file.
我将其放入index.js
文件。 我们可以在同一文件中编写测试,但是一个好的实践是将单元测试分成一个专用文件。
The common naming patterns include {filename}.test.js
and {filename}.spec.js
. I used the first, index.test.js
:
常见的命名模式包括{filename}.test.js
和{filename}.spec.js
。 我使用了第一个index.test.js
:
const getAboutUsLink = require("./index");
test("Returns about-us for english language", () => {
expect(getAboutUsLink("en-US")).toBe("/about-us");
});
First, we need to import the function we want to test. Every test is defined as an invocation of the test
function. The first parameter is the name of the test for your reference. The other is an arrow function where we call the function we want to test and specify which result we expect. I
首先,我们需要导入要测试的功能。 每个测试都定义为对test
功能的调用。 第一个参数是测试的名称,以供您参考。 另一个是箭头函数,我们在其中调用要测试的函数并指定期望的结果。 一世
n this case, we call getAboutUsLink
function with en-US
as the language parameter. We expect the result to be /about-us
.
在这种情况下,我们使用en-US
作为语言参数调用getAboutUsLink
函数。 我们期望结果是/about-us
。
Now we can install the Jest CLI globally and run the test:
现在,我们可以全局安装Jest CLI并运行测试:
npm i jest-cli -g
jest
If you see a configuration-related error, make sure you have your package.json
file present. In case you don't, generate one using npm init
.
如果看到与配置有关的错误,请确保存在package.json
文件。 如果您不这样做,请使用npm init
生成一个。
You should see something like this:
您应该会看到以下内容:
PASS ./index.test.js
√ Returns about-us for english language (4ms)
console.log index.js:15
/about-us
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 2.389s
Great job! This was the first simple JavaScript unit test from start to end. If you installed the Visual Studio Code extension, it will run tests automatically once you save a file. Let's try it by extending the test with this line:
很好! 这是从头到尾的第一个简单JavaScript单元测试。 如果安装了Visual Studio Code扩展,则在保存文件后它将自动运行测试。 让我们通过以下代码扩展测试:
expect(getAboutUsLink("cs-CZ")).toBe("/o-nas");
Once you save the file, Jest will inform you that the test failed. That helps you discover potential issues even before committing your changes.
保存文件后,Jest将通知您测试失败。 这甚至可以在您提交更改之前帮助您发现潜在的问题。
测试高级功能和模拟服务 (Testing Advanced Functionality and Mocking Services)
In real life, the language codes for the getAboutUsLink method would not be constants in the same file. Their value is typically used throughout the project so they would be defined in their own module and imported into all functions that use them.
在现实生活中,getAboutUsLink方法的语言代码在同一文件中不是常量。 它们的值通常在整个项目中使用,因此它们将在自己的模块中定义,并导入到使用它们的所有函数中。
import { englishCode, spanishCode } from './LanguageCodes'
You can import these constants into the test the same way. But the situation will get more complicated if you're working with objects instead of simple constants. Take a look at this method:
您可以以相同的方式将这些常量导入测试。 但是,如果使用对象而不是简单的常量,情况将变得更加复杂。 看一下这个方法:
import { UserStore } from './UserStore'
function getUserDisplayName(){
const user = UserStore.getUser(userId);
return `${user.LastName}, ${user.FirstName}`;
}
This method uses imported UserStore
:
此方法使用导入的UserStore
:
class User {
getUser(userId){
// logic to get data from a database
}
setUser(user){
// logic to store data in a database
}
}
let UserStore = new User();
export { UserStore }
In order to properly unit test this method, we need to mock UserStore
. A mock is a substitute for the original object. It allows us to separate dependencies and real data from the tested method's implementation just like dummies help with crash tests of cars instead of real people.
为了正确地对该方法进行单元测试,我们需要模拟UserStore
。 模拟可以替代原始对象。 它使我们能够从测试方法的实现中分离依赖项和真实数据,就像假人可以帮助汽车(而不是真实的人)进行碰撞测试一样。
If we didn't use the mock, we'd be testing both this function and the store. That would be an integration test and we would likely need to mock the used database.
如果我们不使用该模拟,那么我们将同时测试此功能和商店。 那将是一个集成测试,我们可能需要模拟使用的数据库。
模拟服务 (Mocking a Service)
To mock objects, you can either provide a mocking function or a manual mock. I will focus on the latter as I have a plain and simple use-case. But feel free to check out other mocking possibilities Jest provides.
要模拟对象,可以提供模拟功能或手动模拟。 由于我有一个简单的用例,因此我将重点讨论后者。 但是,请随时查看Jest提供的其他模拟可能性 。
jest.mock('./UserStore', () => ({
UserStore: ({
getUser: jest.fn().mockImplementation(arg => ({
FirstName: 'Ondrej',
LastName: 'Polesny'
})),
setUser: jest.fn()
})
}));
First, we need to specify what are we mocking - the ./UserStore
module. Next, we need to return the mock that contains all exported objects from that module.
首先,我们需要指定要./UserStore
模块。 接下来,我们需要返回包含该模块中所有导出对象的模拟对象。
In this sample, it's only the User
object named UserStore
with the function getUser
. But with real implementations, the mock may be much longer. Any functions you don't really care about in the scope of unit testing can be easily mocked with jest.fn()
.
在此示例中,只有名为UserStore
的User
对象具有功能getUser
。 但是对于实际的实现,模拟可能会更长。 使用jest.fn()
可以轻松jest.fn()
在单元测试范围内您真正不在意的任何功能。
The unit test for the getUserDisplayName
function is similar to the one we created before:
getUserDisplayName
函数的单元测试类似于我们之前创建的单元测试:
test("Returns display name", () => {
expect(getUserDisplayName(1)).toBe("Polesny, Ondrej");
})
As soon as I save the file, Jest tells me I have 2 passing tests. If you're executing tests manually, do so now and make sure you see the same result.
保存文件后,Jest告诉我我有2个通过测试。 如果您手动执行测试,请立即执行,并确保看到相同的结果。
代码覆盖率报告 (Code Coverage Report)
Now that we know how to test JavaScript code, it's good to cover as much code as possible with tests. And that is hard to do. In the end, we're just people. We want to get our tasks done and unit tests usually yield an unwanted workload that we tend to overlook. Code coverage is a tool that helps us fight that.
既然我们知道如何测试JavaScript代码,那么最好在测试中覆盖尽可能多的代码。 这很难做到。 最后,我们就是人。 我们希望完成任务,单元测试通常会产生不必要的工作负载,而我们往往会忽略这些工作负载。 代码覆盖率是可以帮助我们解决这一问题的工具。
Code coverage will tell you how big a portion of your code is covered by unit tests. Take for example my first unit test checking the getAboutUsLink
function:
代码覆盖率将告诉您单元测试覆盖了代码的很大一部分。 以我检查getAboutUsLink
函数的第一个单元测试getAboutUsLink
:
test("Returns about-us for english language", () => {
expect(getAboutUsLink("en-US")).toBe("/about-us");
});
It checks the English link, but the Spanish version stays untested. The code coverage is 50%. The other unit test is checking the getDisplayName
function thoroughly and its code coverage is 100%. Together, the total code coverage is 67%. We had 3 use cases to test, but our tests only cover 2 of them.
它检查英语链接,但西班牙语版本未经测试。 代码覆盖率为50%。 另一个单元测试是彻底检查getDisplayName
函数,其代码覆盖率为100%。 在一起,总代码覆盖率为67%。 我们有3个用例进行测试,但我们的测试仅涵盖其中2个。
To see the code coverage report, type the following command into the terminal:
要查看代码覆盖率报告,请在终端中键入以下命令:
jest --coverage
Or, if you're using Visual Studio Code with the Jest extension, you can run the command (CTRL+SHIFT+P) Jest: Toggle Coverage Overlay. It will show you right in the implementation which lines of code are not covered with tests.
或者,如果您将Visual Studio Code与Jest扩展名一起使用,则可以运行(CTRL + SHIFT + P) Jest:Toggle Coverage Overlay命令 。 它会在实现中正确显示出哪些代码行未包含在测试中。
By running the coverage check, Jest will also create an HTML report. Find it in your project folder under coverage/lcov-report/index.html
.
通过运行覆盖率检查,Jest还将创建一个HTML报告。 在coverage/lcov-report/index.html
下的项目文件夹中找到它。
Now, I don't have to mention that you should strive for 100% code coverage, right? :-)
现在,我不必提起您应该争取100%的代码覆盖率,对吧? :-)
摘要 (Summary)
In this article, I showed you how to start with unit testing in JavaScript. While it's nice to have your code coverage shine at 100% in the report, in reality, it's not always possible to (meaningfully) get there. The goal is to let unit tests help you maintain your code and ensure it always works as intended. They enable you to:
在本文中,我向您展示了如何开始使用JavaScript进行单元测试。 虽然使代码覆盖率在报告中达到100%很高兴,但实际上,并非总是可以(有意义地)达到目标。 目的是让单元测试帮助您维护代码并确保其始终按预期工作。 它们使您能够:
- clearly define implementation requirements, 明确定义实施要求,
- better design your code and separate concerns, 更好地设计代码并分离关注点,
- discover issues you may introduce with your newer commits, 发现您可能在较新的提交中引入的问题,
- and give you confidence that your code works. 并给您信心,您的代码可以正常工作。
The best place to start is the Getting started page in the Jest documentation so you can try out these practices for yourself.
最好的起点是Jest文档中的“ 入门”页面,因此您可以自己尝试这些实践。
Do you have your own experience with testing code? I'd love to hear it, let me know on Twitter or join one of my Twitch streams.
您有测试代码方面的经验吗? 我希望听到它,在Twitter上告诉我,或加入我的Twitch视频流之一 。
翻译自: https://www.freecodecamp.org/news/how-to-start-unit-testing-javascript/
javascript 代码