Jest 单元测试快速上手指南

脑洞前端

共 6883字,需浏览 14分钟

 ·

2020-09-08 08:03

原文链接: https://github.com/yinxin630/blog/issues/38

Jest[1] 是一款简单, 容易上手且功能十分强大的测试框架

安装

yarn add -D jest

使用

创建 test 目录, 添加 plus.spec.js 文件

describe('example', () => {
    it('should equal 2', () => {
        expect(1 + 1).toBe(2);
    });
});

执行 yarn jest 或者 yarn jest test/plus.spec.js 运行测试用例

成功结果

失败结果

输出测试覆盖率

在根目录创建 jest.config.js 配置文件

module.exports = {
    collectCoveragetrue,
};

创建 plus.js 模块

module.exports = function plus(a, b{
    return a + b;
}

修改测试用例使用模块

const plus = require('../plus');

describe('example', () => {
    it('should equal 2', () => {
        expect(plus(1, 1)).toBe(2);
    });
});

再次执行测试, 输出覆盖率如下


在浏览器中打开 coverage/lcov-report/index.html 可以浏览覆盖率结果页面


忽略部分文件或者代码行的覆盖率

修改 plus.ts 模块, 添加更多分支

export default function plus(a: number, b: number{
    if (a + b > 100) {
        return 0;
    } else if (a + b < 0) {
        return 0;
    } else {
        return a + b;
    }
}

重新执行测试, 覆盖率输出结果


你可以完善测试用例, 或者可能有些文件(譬如 config)和代码分支并不需要测试, 可以将其在测试覆盖率结果中排除, 参考如下配置

  1. 忽略目录下所有文件

jest.config.js 中添加

collectCoverageFrom: [
    '**/*.{ts,tsx}',
    '!**/node_modules/**',
    '!**/[directory path]/**',
],

! 开头的表示忽略与其匹配的文件

  1. 忽略单个文件

在该文件顶部添加 /* istanbul ignore file */

  1. 忽略一个函数, 一块分支逻辑或者一行代码

在该函数, 分支逻辑或者代码行的上一行添加 /* istanbul ignore next */

支持 Typescript

执行 yarn add -D typescript ts-jest @types/jest 安装 typescript 和声明 并在 jest.config.js 中添加 preset: 'ts-jest'

plus.js 重命名为 plus.ts

export default function plus(a: number, b: number{
    return a + b;
}

同样的, 将 plus.spec.js 重命名为 plus.spec.ts

import plus from '../plus'

describe('example'() => {
    it('should equal 2'() => {
        expect(plus(11)).toBe(2);
    });
});

执行测试, 结果和之前一致

执行单测时不校验 ts 类型

有时你可能会希望不校验 ts 类型, 仅执行代码测试, 比如需要在 CI 中将类型校验和单元测试分为两个任务 在 jest.config.js 中添加如下内容

globals: {
    'ts-jest': {
        isolatedModulestrue,
    },
}

测试 React 组件

安装 react 依赖 yarn add react react-dom 和声明 yarn add -D @types/react安装 react 测试库 yarn add -D @testing-library/react @testing-library/jest-dom

添加 typescript 配置文件 tsconfig.json

{
    "compilerOptions": {
        "target""es2018",
        "strict"true,
        "moduleResolution""node",
        "jsx""react",
        "allowSyntheticDefaultImports"true,
        "esModuleInterop"true,
        "lib": ["es2015""es2016""es2017""dom"]
    },
    "exclude": ["node_modules"]
}

新增测试组件 Title.tsx

import React from 'react';

function Title() {
return (

Title


);
}

export default Title;

新增测试用例 test/Title.spec.tsx

/**
* @jest-environment jsdom
*/

import React from 'react';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';
import Title from '../Title';

describe('Title', () => {
it('should render without error', () => {
const { getByText } = render();<br> const $title = getByText('Title');<br> expect($title).toBeInTheDocument();<br> });<br>});<br></code></pre><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">执行 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">yarn jest test/Title.spec.ts</code> 查看结果</p><h3 data-tool="mdnice编辑器" style="margin-top: 30px;margin-bottom: 15px;font-weight: bold;font-size: 20px;"><span style="display: none;"></span>处理静态资源引用<span style="display: none;"></span></h3><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">react 组件有时引用一些静态资源, 譬如图片或者 css 样式表, webpack 会正确的处理这些资源, 但是对 Jest 来讲, 这些资源是无法识别的</p><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">创建 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Title.less</code> 样式表</p><pre data-tool="mdnice编辑器" style="margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">h1 {<br> color: red;<br>}<br></code></pre><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">修改 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Ttitle.tsx</code>, 添加样式引用 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">import './Title.less';</code></p><p data-tool="mdnice编辑器" style="padding-top: 8px;padding-bottom: 8px;line-height: 26px;">执行测试会报错</p></section><p style="text-align: center;"><img class="rich_pages js_insertlocalimg" data-ratio="0.3968503937007874" data-s="300,640" src="https://filescdn.proginn.com/b0f530f1ab543f24f9b73b36935b2aea/c3010f2832d7fad3b2f00a623771ec06.webp" data-type="png" data-w="1270" style></p><section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;line-height: 1.6;letter-spacing: 0px;word-break: break-word;overflow-wrap: break-word;text-align: left;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;"><p style="color: black;font-size: 16px;"><br></p><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">我们需要配置 transform 对其处理</p><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在根目录创建 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">jest.transformer.js</code></p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">const</span> path = <span style="color: #a6e22e;line-height: 26px;">require</span>(<span style="color: #a6e22e;line-height: 26px;">'path'</span>);<br><br><span style="color: #a6e22e;line-height: 26px;">module</span>.exports = {<br>    process(src, filename) {<br>        <span style="color: #f92672;font-weight: bold;line-height: 26px;">return</span> <span style="color: #a6e22e;line-height: 26px;">`module.exports = <span style="line-height: 26px;">${<span style="line-height: 26px;">JSON</span>.stringify(path.basename(filename))}</span>;`</span>;<br>    },<br>};<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这里是将资源文件名作为模块导出内容</p><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">修改 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">jest.config.js</code> 添加如下配置</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">transform: {<br>    <span style="color: #a6e22e;line-height: 26px;">'\\.(less)$'</span>: <span style="color: #a6e22e;line-height: 26px;">'<rootDir>/jest.transformer.js'</span>, <span style="color: #75715e;line-height: 26px;">// 正则匹配, 处理 less 样式</span><br>},<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">然后重新执行测试就可以了</p><h3 data-tool="mdnice编辑器" style="color: black;font-size: 20px;margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>处理 css in js<span style="display: none;"></span></h3><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">如果你使用了类似 <span style="color: #1e6bb8;font-weight: bold;">linaria</span><sup style="line-height: 0;color: #1e6bb8;font-weight: bold;">[2]</sup> 这种 css in js 方案, 其中的 css 样式模板字符串是不支持运行时编译的</p><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">修改 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Title.tsx</code></p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">import React from 'react';<br>import { css } from 'linaria';<br><br>const title = css`<br> color: red;<br>`;<br><br>function Title() {<br> return <h1 className={title}>Title</h1>;<br>}<br><br>export default Title;<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">运行测试会报错</p><p style="text-align: center;"><img class="rich_pages js_insertlocalimg" data-ratio="0.253125" data-s="300,640" src="https://filescdn.proginn.com/a3817f52cf7ebe1926d002f62f4ef210/beeb1606c2bedccc925b7808756f5f8e.webp" data-type="png" data-w="1280" style></p><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">linaria 是通过 babel 插件将其预编译为 class 名的, 这里可以 mock 一下 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">css</code> 函数, 返回一个随机值作为 class 名</p><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">在根目录创建 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">jest.setup.js</code></p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">jest.mock(<span style="color: #a6e22e;line-height: 26px;">'linaria'</span>, () => ({<br>    <span style="line-height: 26px;">css</span>: jest.fn(<span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> <span style="color: #a6e22e;line-height: 26px;">Math</span>.floor(<span style="color: #a6e22e;line-height: 26px;">Math</span>.random() * (<span style="line-height: 26px;">10</span> ** <span style="line-height: 26px;">9</span>)).toString(<span style="line-height: 26px;">36</span>)),<br>}));<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">修改 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">jest.config.js</code> 添加如下配置</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">setupFilesAfterEnv: [<span style="color: #a6e22e;line-height: 26px;">'./jest.setup.js'</span>],<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">重新执行测试就可以了</p><h3 data-tool="mdnice编辑器" style="color: black;font-size: 20px;margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>测试交互事件<span style="display: none;"></span></h3><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">新增 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Count.tsx</code> 组件</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">import React, { useState } from 'react';<br><br>function Count() {<br> const [count, updateCount] = useState(0);<br> return (<br> <div><br> <span data-testid="count">{count}</span><br> <button data-testid="button" onClick={() => updateCount(count + 1)}><br> +1<br> </button><br> </div><br> );<br>}<br><br>export default Count;<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">新增 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">test/Count.spec.tsx</code> 组件</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">/**<br> * @jest-environment jsdom<br> */<br><br>import React from 'react';<br>import { render, fireEvent } from '@testing-library/react';<br>import '@testing-library/jest-dom/extend-expect';<br>import Count from '../Count';<br><br>describe('Count', () => {<br> it('should render without error', () => {<br> const { getByTestId } = render(<Count />);<br> const $count = getByTestId('count');<br> const $button = getByTestId('button');<br> expect($count).toHaveTextContent('0');<br> fireEvent.click($button);<br> expect($count).toHaveTextContent('1');<br> });<br>});<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这里通过 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">testId</code> 来查找元素, 使用 <span style="color: #1e6bb8;font-weight: bold;">fireEvent</span><sup style="line-height: 0;color: #1e6bb8;font-weight: bold;">[3]</sup> 触发 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">click</code> 事件</p><h3 data-tool="mdnice编辑器" style="color: black;font-size: 20px;margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>测试函数调用<span style="display: none;"></span></h3><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">新增 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">Button.tsx</code> 组件</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">import React from 'react';<br><br>type Props = {<br> onClick: () => void;<br>};<br><br>function Button({ onClick }: Props) {<br> return <button onClick={onClick}>button</button>;<br>}<br><br>export default Button;<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">添加 <code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">test/Button.spec.tsx</code> 测试用例</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;">/**<br> * @jest-environment jsdom<br> */<br><br>import React from 'react';<br>import { render, fireEvent } from '@testing-library/react';<br>import '@testing-library/jest-dom/extend-expect';<br>import Button from '../Button';<br><br>describe('Button', () => {<br> it('should render without error', () => {<br> const handleClick = jest.fn(); // mock 函数<br> const { getByText } = render(<Button onClick={handleClick} />); // 传递 props<br> const $button = getByText('button');<br> fireEvent.click($button);<br> expect(handleClick).toHaveBeenCalled(); // 期望其被调用<br> });<br>});<br></code></pre><h2 data-tool="mdnice编辑器" style="color: black;font-size: 22px;margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>测试包含定时器的逻辑</h2><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #75715e;line-height: 26px;">// timer.ts</span><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">let</span> cache = <span style="color: #a6e22e;line-height: 26px;">'cache'</span>;<br><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">export</span> <span style="color: #f92672;font-weight: bold;line-height: 26px;">default</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">function</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">timer</span>(<span style="line-height: 26px;"></span>) </span>{<br>    setTimeout(<span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>        cache = <span style="color: #a6e22e;line-height: 26px;">''</span>;<br>    }, <span style="line-height: 26px;">1000</span>);<br>    <span style="color: #f92672;font-weight: bold;line-height: 26px;">return</span> cache;<br>}<br></code></pre><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #75715e;line-height: 26px;">// test/timer.spec.ts</span><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">import</span> timer <span style="color: #f92672;font-weight: bold;line-height: 26px;">from</span> <span style="color: #a6e22e;line-height: 26px;">'../timer'</span><br><br>jest.useFakeTimers(); <span style="color: #75715e;line-height: 26px;">// 替代原生计时器</span><br><br>describe(<span style="color: #a6e22e;line-height: 26px;">'timer'</span>, <span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>    it(<span style="color: #a6e22e;line-height: 26px;">'should clear cache after timer out'</span>, <span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>        expect(timer()).toBe(<span style="color: #a6e22e;line-height: 26px;">'cache'</span>);<br>        jest.advanceTimersByTime(<span style="line-height: 26px;">1000</span>); <span style="color: #75715e;line-height: 26px;">// 让计时器前进 1000ms</span><br>        expect(timer()).toBe(<span style="color: #a6e22e;line-height: 26px;">''</span>);<br>    })<br>})<br></code></pre><h2 data-tool="mdnice编辑器" style="color: black;font-size: 22px;margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>mock 依赖模块</h2><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">要测试的模块可能依赖于其他模块或者第三方 npm 包的结果, 我们可以使用 <span style="color: #1e6bb8;font-weight: bold;">Mock Functions</span><sup style="line-height: 0;color: #1e6bb8;font-weight: bold;">[4]</sup> 对其进行 mock</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #75715e;line-height: 26px;">// test/mock.spec.ts</span><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">import</span> { mocked } <span style="color: #f92672;font-weight: bold;line-height: 26px;">from</span> <span style="color: #a6e22e;line-height: 26px;">'ts-jest/utils'</span>;<br><span style="color: #f92672;font-weight: bold;line-height: 26px;">import</span> plus <span style="color: #f92672;font-weight: bold;line-height: 26px;">from</span> <span style="color: #a6e22e;line-height: 26px;">'../plus'</span>;<br><br>jest.mock(<span style="color: #a6e22e;line-height: 26px;">'../plus'</span>);<br><br>describe(<span style="color: #a6e22e;line-height: 26px;">'mock'</span>, <span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>    it(<span style="color: #a6e22e;line-height: 26px;">'should return mock value'</span>, <span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>        mocked(plus).   (<span style="line-height: 26px;">50</span>);<br>        expect(plus(<span style="line-height: 26px;">1</span>, <span style="line-height: 26px;">1</span>)).toBe(<span style="line-height: 26px;">50</span>);<br>    });<br>});<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">还有官网 mock axios npm 模块的例子 https://jestjs.io/docs/en/mock-functions#mocking-modules</p><h2 data-tool="mdnice编辑器" style="color: black;font-size: 22px;margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: none;"></span>mock 环境变量和命令行参数</h2><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">有的模块会从环境变量和命令行参数取值, 并且可能是在模块初始化时获取的</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #75715e;line-height: 26px;">// process.ts</span><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">const</span> { env, argv } = process;<br><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">export</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">function</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">getEnvironmentValue</span>(<span style="line-height: 26px;"></span>) </span>{<br>    <span style="color: #f92672;font-weight: bold;line-height: 26px;">return</span> env.Value;<br>}<br><br><span style="color: #f92672;font-weight: bold;line-height: 26px;">export</span> <span style="line-height: 26px;"><span style="color: #f92672;font-weight: bold;line-height: 26px;">function</span> <span style="color: #a6e22e;font-weight: bold;line-height: 26px;">getProcessArgsValues</span>(<span style="line-height: 26px;"></span>) </span>{<br>    <span style="color: #f92672;font-weight: bold;line-height: 26px;">return</span> argv[<span style="line-height: 26px;">2</span>];<br>}<br></code></pre><p data-tool="mdnice编辑器" style="color: black;font-size: 16px;padding-top: 8px;padding-bottom: 8px;line-height: 26px;">这种情况我们需要在每个测试用例中, 使用动态 require 来运行时引入改模块, 并且设置其每次引入时删除 cache</p><pre data-tool="mdnice编辑器" style="color: black;font-size: 16px;margin-top: 10px;margin-bottom: 10px;"><code style="overflow-x: auto;padding: 16px;background: #272822;color: #ddd;display: -webkit-box;font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;border-radius: 0px;font-size: 12px;-webkit-overflow-scrolling: touch;"><span style="color: #75715e;line-height: 26px;">// test/process.spec.ts</span><br>describe(<span style="color: #a6e22e;line-height: 26px;">'mock process'</span>, <span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>    beforeEach(<span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>        jest.resetModules();<br>    });<br><br>    it(<span style="color: #a6e22e;line-height: 26px;">'should return environment value'</span>, <span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>        process.env = {<br>            Value: <span style="color: #a6e22e;line-height: 26px;">'value'</span>,<br>        };<br>        <span style="color: #f92672;font-weight: bold;line-height: 26px;">const</span> { getEnvironmentValue } = <span style="color: #a6e22e;line-height: 26px;">require</span>(<span style="color: #a6e22e;line-height: 26px;">'../process'</span>);<br>        expect(getEnvironmentValue()).toBe(<span style="color: #a6e22e;line-height: 26px;">'value'</span>);<br>    });<br><br>    it(<span style="color: #a6e22e;line-height: 26px;">'should return process args value'</span>, <span style="line-height: 26px;"><span style="line-height: 26px;">()</span> =></span> {<br>        process.argv = [<span style="color: #a6e22e;line-height: 26px;">'value'</span>];<br>        <span style="color: #f92672;font-weight: bold;line-height: 26px;">const</span> { getProcessArgsValues } = <span style="color: #a6e22e;line-height: 26px;">require</span>(<span style="color: #a6e22e;line-height: 26px;">'../process'</span>);<br>        expect(getProcessArgsValues()).toBe(<span style="color: #a6e22e;line-height: 26px;">'value'</span>);<br>    });<br>});<br></code></pre><h3 data-tool="mdnice编辑器" style="color: black;font-size: 20px;margin-top: 30px;margin-bottom: 15px;font-weight: bold;"><span style="display: block;">参考资料</span></h3><section data-tool="mdnice编辑器" style="color: black;font-size: 16px;"><span style="display: flex;"><span style="display: inline;width: 10%;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;font-size: 80%;opacity: 0.6;line-height: 26px;font-family: ptima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[1]</span><p style="display: inline;font-size: 14px;width: 90%;line-height: 26px;word-break: break-all;">Jest: <em>https://jestjs.io/</em></p></span><span style="display: flex;"><span style="display: inline;width: 10%;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;font-size: 80%;opacity: 0.6;line-height: 26px;font-family: ptima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[2]</span><p style="display: inline;font-size: 14px;width: 90%;line-height: 26px;word-break: break-all;">linaria: <em>https://github.com/yinxin630/blog/issues/36</em></p></span><span style="display: flex;"><span style="display: inline;width: 10%;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;font-size: 80%;opacity: 0.6;line-height: 26px;font-family: ptima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[3]</span><p style="display: inline;font-size: 14px;width: 90%;line-height: 26px;word-break: break-all;">fireEvent: <em>https://testing-library.com/docs/dom-testing-library/api-events</em></p></span><span style="display: flex;"><span style="display: inline;width: 10%;background-image: none;background-position: initial;background-size: initial;background-repeat: initial;background-attachment: initial;background-origin: initial;background-clip: initial;font-size: 80%;opacity: 0.6;line-height: 26px;font-family: ptima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;">[4]</span><p style="display: inline;font-size: 14px;width: 90%;line-height: 26px;word-break: break-all;">Mock Functions: <em>https://jestjs.io/docs/en/mock-function-api</em></p></span></section></section><section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-top: -10px;padding-right: 10px;padding-left: 10px;max-width: 100%;color: rgb(43, 43, 43);font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);word-break: break-word;line-height: 1.25;font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><h3 data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;font-weight: bold;font-size: 17px;max-width: 100%;color: black;text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="padding-bottom: 2px;max-width: 100%;border-bottom: 2px solid rgba(79, 177, 249, 0.65);color: rgb(43, 43, 43);box-sizing: border-box !important;overflow-wrap: break-word !important;">推荐阅读</span></h3><p style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p></section><p style="max-width: 100%;min-height: 1em;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);orphans: 4;word-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(123, 12, 0);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;">1、<a target="_blank" data-itemshowtype="11" tab="innerlink" data-linktype="2" hasload="1" style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">力扣刷题插件</a></span></em></span><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p><p style="max-width: 100%;min-height: 1em;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);orphans: 4;word-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(123, 12, 0);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;">2、<a target="_blank" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">你不知道的 TypeScript 泛型(万字长文,建议收藏)</a></span></em></em></span></p><p style="max-width: 100%;min-height: 1em;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);orphans: 4;word-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(123, 12, 0);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;">3、<em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;"><a target="_blank" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">TypeScript 类型系统</a></span></em></em></span></em></em></span></p><p style="max-width: 100%;min-height: 1em;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);orphans: 4;word-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(123, 12, 0);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;">4、<a target="_blank" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">immutablejs 是如何优化我们的代码的?</a></span></em></em></span></p><p style="max-width: 100%;min-height: 1em;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);orphans: 4;word-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(123, 12, 0);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;">5、<a target="_blank" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">typeScript 配置文件该怎么写?</a></span></em></em></span></p><p style="max-width: 100%;min-height: 1em;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);orphans: 4;word-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(123, 12, 0);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;">6、<a target="_blank" data-itemshowtype="11" tab="innerlink" data-linktype="2">漫画:三种 “奇葩” 的排序算法</a></span></em></em></span></p><p style="max-width: 100%;min-height: 1em;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;letter-spacing: 2px;text-align: left;white-space: normal;background-color: rgb(255, 255, 255);orphans: 4;word-spacing: 2px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgb(123, 12, 0);font-size: 15px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><em style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><span style="max-width: 100%;font-weight: bolder;box-sizing: border-box !important;overflow-wrap: break-word !important;">7、<a target="_blank" data-itemshowtype="0" tab="innerlink" data-linktype="2" hasload="1" style="color: var(--weui-LINK);-webkit-tap-highlight-color: rgba(0, 0, 0, 0);cursor: pointer;max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;">【校招面经分享】好未来-北京-视频面试</a></span></em></em></em></em></span></p><p data-tool="mdnice编辑器" style="margin-bottom: 20px;max-width: 100%;min-height: 1em;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;font-size: 16px;white-space: normal;line-height: 1.8em;color: rgb(58, 58, 58);text-align: right;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"></p><section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="padding-right: 10px;padding-left: 10px;max-width: 100%;font-size: 16px;word-break: break-word;text-align: left;line-height: 1.25;color: rgb(43, 43, 43);font-family: Optima-Regular, Optima, PingFangTC-Light, PingFangSC-light, PingFangTC-light;letter-spacing: 2px;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-top: -10px;padding-right: 10px;padding-left: 10px;max-width: 100%;font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;white-space: normal;background-color: rgb(255, 255, 255);word-break: break-word;line-height: 1.25;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><section data-tool="mdnice编辑器" data-website="https://www.mdnice.com" style="margin-top: -10px;padding-right: 10px;padding-left: 10px;max-width: 100%;word-break: break-word;line-height: 1.25;background-image: linear-gradient(90deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%), linear-gradient(360deg, rgba(50, 0, 0, 0.04) 3%, rgba(0, 0, 0, 0) 3%);background-size: 20px 20px;background-position: center center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><blockquote data-tool="mdnice编辑器" style="margin-top: 20px;margin-bottom: 20px;padding: 10px 10px 10px 20px;border-width: 1px;border-top-style: solid;border-right-style: solid;border-bottom-style: solid;border-color: rgba(64, 184, 250, 0.4);color: rgb(89, 89, 89);font-size: 0.9em;max-width: 100%;box-sizing: inherit;line-height: 1.55em;overflow: auto;border-radius: 6px;background: none 0% 0% repeat scroll rgba(64, 184, 250, 0.1);overflow-wrap: break-word !important;"><span style="max-width: 100%;color: rgba(64, 184, 250, 0.5);font-size: 34px;line-height: 1;font-weight: 700;box-sizing: border-box !important;overflow-wrap: break-word !important;">❝</span><p style="padding-top: 8px;padding-bottom: 8px;max-width: 100%;min-height: 1em;font-size: 14px;word-spacing: 2px;line-height: 26px;box-sizing: border-box !important;overflow-wrap: break-word !important;">关注加加,星标加加~</p><span style="max-width: 100%;float: right;color: rgba(64, 184, 250, 0.5);box-sizing: border-box !important;overflow-wrap: break-word !important;">❞</span></blockquote></section></section><section data-mpa-template="t" style="max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;white-space: normal;background-color: rgb(255, 255, 255);letter-spacing: 0.544px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><section style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"><section style="margin: 10px auto;max-width: 100%;text-align: center;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></section></section></section><section data-mpa-template="t" style="max-width: 100%;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;white-space: normal;background-color: rgb(255, 255, 255);letter-spacing: 0.544px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><section data-mid style="max-width: 100%;width: 578px;display: flex;justify-content: center;align-items: flex-end;box-sizing: border-box !important;overflow-wrap: break-word !important;"><section data-mid style="padding-right: 35px;padding-left: 35px;max-width: 100%;height: 35px;background: none 0% 0% repeat scroll rgb(219, 14, 14);border-radius: 5px;border-width: 1px;border-style: solid;border-color: rgb(255, 216, 59);display: flex;justify-content: center;align-items: center;flex-wrap: nowrap;font-weight: 600;color: rgb(255, 216, 59);line-height: 23px;box-sizing: border-box !important;overflow-wrap: break-word !important;"><p data-mid style="max-width: 100%;min-height: 1em;box-sizing: border-box !important;overflow-wrap: break-word !important;">如果觉得文章不错,帮忙点个在看呗</p></section><section data-mid style="margin-bottom: -3px;margin-left: -20px;max-width: 100%;width: 24px;height: 29px;background-image: url("https://mmbiz.qpic.cn/mmbiz_png/3Y5hIUMYLLnyVLlrRSw2mdl1kk9jX3J9twnIqashN5xLibxEMaFB14EktRo4S1ia3ia9t3D7tictYFmfs7vibJIMGRA/640?wx_fmt=png");background-position: center center;background-size: contain;background-repeat: no-repeat;background-attachment: initial;background-origin: initial;background-clip: initial;z-index: 10;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;box-sizing: border-box !important;overflow-wrap: break-word !important;"></section></section></section><p style="max-width: 100%;min-height: 1em;color: rgb(0, 0, 0);font-family: Optima-Regular, Optima, PingFangSC-light, PingFangTC-light, "PingFang SC", Cambria, Cochin, Georgia, Times, "Times New Roman", serif;white-space: normal;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;box-sizing: border-box !important;overflow-wrap: break-word !important;"></p></section><p style="margin-top: 10px;margin-bottom: 5px;max-width: 100%;min-height: 1em;line-height: 2em;text-align: right;box-sizing: border-box !important;overflow-wrap: break-word !important;"><br style="max-width: 100%;font-family: -apple-system-font, system-ui, "Helvetica Neue", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", Arial, sans-serif;letter-spacing: 0.544px;white-space: normal;color: rgb(63, 63, 63);font-size: 16px;background-color: rgb(255, 255, 255);box-sizing: border-box !important;overflow-wrap: break-word !important;"></p><p><br></p> </div></div></div><div class="tag-list-box"><div class="tag-list"><div class="tag-list-container"></div></div></div><span class="view_num">浏览 1</span><div class="float-bar float-bar-relative" id="float-bar-relative"><div class="float-bar-body"><div class="item qinglite-zan"><i class="iconfont icon-dianzan"></i>点赞 </div><div class="gap"></div><a href="#comments" class="item"><i class="iconfont iconfont icon-pinglun1"></i><span class="com_num"></span>评论 </a><div class="item qinglite-collect"><i class="iconfont icon-shoucang"></i>收藏 </div><div class="item qinglite_share"><i class="iconfont icon-fenxiang1"></i>分享 <div class="qrcode-modal"><img src="/api/pub/ewm" alt=""><p>手机扫一扫分享</p></div></div><div class="expand"></div><a onclick="miniProgram_navigateTo_func()" class="item qinglite_share_miniapp miniapp_show"><i class="iconfont icon-fenxiang1"></i>分享 </a><div class="item jubao qinglite-jubao miniapp_hide"><i class="iconfont icon-jubao"></i> 举报 </div></div></div></div><div class="comments_wrapper comments"><div class="title">评论</div><div id="comments" class="comments"><div class="error"></div><div class="textarea-wrapper"><textarea class="comment-content" cols="30" rows="5" placeholder="输入评论"></textarea></div><div class="button"><div class="error"><div class="comment-emojis"><div class="comment-choose-img qinglite_upload"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-tupianyangshi2"></use></svg><span>图片</span></div><div class="comment-choose-img comment-emoji-btn"><svg class="icon show-emoji-list" aria-hidden="true"><use xlink:href="#icon-biaoqing"></use></svg><span class="show-emoji-list">表情</span><div class="comment-emoji-list"></div></div><div style="display: none" class="comment-choose-img"><svg class="icon" aria-hidden="true"><use xlink:href="#icon-shipinwenjian1"></use></svg><span>视频</span></div></div></div><button class="qinglite-comment">评价</button></div><div class="medias qinglite_upload_content"></div></div></div><div style="display: none" class="comments"><div class="title">全部评论</div><div class="comments comment-item-content"></div></div><div id="recommend" class="comments hide_app"><div class="title">推荐 <a href="#qs_detail" class="iconfont icon-huidaodingbu"></a></div></div><div class="qs_post_list flow_post_list hide_app"><div class="item img qinglite_item"><a href="/doc/80576475125b7aafa" title="Github Action 快速上手指南" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/73b5df07c5ae866471088bc043baf24d/bec6141235a72840b4faf6f206ac431d.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/80576475125b7aafa" title="Github Action 快速上手指南" class="title_middle">Github Action 快速上手指南</a><a href="https://jishu.proginn.com/u/84606474f6191a48e" title="后端技术漫谈" class="up_info"><div style="background-image:url(https://inn.proginn.com/useralbum/548400/cps_wx_0177d7ea46cd.jpg!mediumicon?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">后端技术漫谈</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><div class="item qinglite_item"><a href="/doc/4307643f93db7fd82" title="快速上手" class="content"><div class="qinglite_item_top_wrapper"><div class="title">快速上手</div><div class="right-top-icon-tag"></div></div><div class="desc">准备工作为满足相关部门监管要求,在正式使用 UniSMS 短信服务前须进入控制台依次完成如下操作。实名认证应运营商网关要求,发送短信必须通过实名认证,未认证个人、企业、组织机构均不支持提交自定义模板,仅允许发送普通验证码短信。添加签名前往控制台「短信报备-签名管理」添加签名,注意事项如下:签名支持以</div></a></div><div class="item img qinglite_item"><a href="/doc/87456477ef9cb6256" title="前端掌握单元测试-jest" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/4b325fb38696d3cbc8c38cbb8ea7d1ed/427f3bb688a7df4e2d85a0fda7d81f7a.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/87456477ef9cb6256" title="前端掌握单元测试-jest" class="title_middle">前端掌握单元测试-jest</a><a href="https://jishu.proginn.com/u/5339647506f588a67" title="前端大神之路" class="up_info"><div style="background-image:url(https://inn.proginn.com/useralbum/551055/cps_wx_0177d7ea600c.jpg!mediumicon?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">前端大神之路</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><div class="item qinglite_item"><a href="/doc/6178669a2a620279f" title="爆款短视频指南,快速上手!" class="content"><div class="qinglite_item_top_wrapper"><div class="title">爆款短视频指南,快速上手!</div><div class="right-top-icon-tag"></div></div><div class="desc">短视频及直播电商现在已经非常成熟了,大家想要做好带货,就要先做好拍摄及账号搭建等相关内容。当然拍摄也是有技巧的,有人发了几百上千条视频,粉丝量不过万; 有人一条视频就增粉几万,千差万别,一条爆款视频顶过一千条普通视频。那么如何做好爆款短视频呢?接着往下看~目录:1.直播引流短视频2.短视频选题类型3</div></a></div><div class="item img qinglite_item"><a href="/doc/33596475022f015fd" title="ES开发指南|如何快速上手ElasticSearch" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/37842fb0cc20ba27696329fb22c89688/4c9341f6117fea3815847cc84d7195e3.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/33596475022f015fd" title="ES开发指南|如何快速上手ElasticSearch" class="title_middle">ES开发指南|如何快速上手ElasticSearch</a><a href="https://jishu.proginn.com/u/4870643e68ba75b64" title="源码共读" class="up_info"><div style="background-image:url(https://inn.proginn.com/useralbum/350307/cps_wx_0173a3657cfe.jpg!mediumicon?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">源码共读</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><div class="item img qinglite_item"><a href="/doc/2328647622d61b642" title="JSDoc 快速上手" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/3fa27c362da68365a42620419dd85e36/0f624655c794b63221b4c58cce489247.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/2328647622d61b642" title="JSDoc 快速上手" class="title_middle">JSDoc 快速上手</a><a href="https://jishu.proginn.com/u/232564444a962850a" title="SegmentFault" class="up_info"><div style="background-image:url(https://inn.proginn.com/useralbum/468975/cps_wx_0173a36648ea.jpg!mediumicon?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">SegmentFault</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><div class="item img qinglite_item"><a href="/doc/85126477810ebf6e0" title="Pandas快速上手!" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/f44a318413c0117e5f4e106540e6dd61/baf55720436444c6b4e7668a2051801d.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/85126477810ebf6e0" title="Pandas快速上手!" class="title_middle">Pandas快速上手!</a><a href="https://jishu.proginn.com/u/5773647576fd41dfb" title="算法进阶" class="up_info"><div style="background-image:url(https://stacdn.proginn.com/image/usericon/1.png?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">算法进阶</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><div class="item img qinglite_item"><a href="/doc/738564777b167173d" title="K8S 上手指南!" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/bc63e1036eb818b0737ea4890287f47d/a3ea1b7cdbbe424a20028daa4529e720.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/738564777b167173d" title="K8S 上手指南!" class="title_middle">K8S 上手指南!</a><a href="https://jishu.proginn.com/u/2234647569622102a" title="程序员面试吧" class="up_info"><div style="background-image:url(https://inn.proginn.com/useralbum/572060/cps_wx_017857c8cef0.jpg!mediumicon?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">程序员面试吧</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><div class="item img qinglite_item"><a href="/doc/525264777baa80dd9" title="K8S 上手指南!" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/a6ce3bbd6763cd71f6fd0c7e0cdd6f71/be46a97e8bd17c0e39ae6bda177a0a46.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/525264777baa80dd9" title="K8S 上手指南!" class="title_middle">K8S 上手指南!</a><a href="https://jishu.proginn.com/u/6008643ec67f75371" title="杰哥的IT之旅" class="up_info"><div style="background-image:url(https://inn.proginn.com/useralbum/376135/cps_wx_0173a36581fc.jpg!mediumicon?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">杰哥的IT之旅</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><div class="item img qinglite_item"><a href="/doc/45316476ceae6d400" title="Jest 写前端单元测试入门教程" class="content"><div class="bg" style="background-image:url(https://filescdn.proginn.com/fe909f98b7f4aba716d5b3ccb319275e/f0d0afc64eb659fe655fddadfc866bcd.webp?x-oss-process=image/resize,w_300)"></div></a><a href="/doc/45316476ceae6d400" title="Jest 写前端单元测试入门教程" class="title_middle">Jest 写前端单元测试入门教程</a><a href="https://jishu.proginn.com/u/32716443d30e58660" title="前端迷" class="up_info"><div style="background-image:url(https://inn.proginn.com/useralbum/465976/cps_wx_0173a36601b6.jpg!mediumicon?imageMogr2/format/webp/thumbnail/!200x200r)" class="avatar"></div><div class="username">前端迷</div><div class="expand"></div><div class="likes"><i class="iconfont icon-dianzan"></i></div><span class="num">0</span></a></div><i></i><i></i><i></i><i></i><i></i></div><div class="float-bar" id="float-bar"><div class="float-bar-body"><div class="item qinglite-zan"><i class="iconfont icon-dianzan"></i>点赞 </div><div class="gap"></div><a href="#comments" class="item"><i class="iconfont iconfont icon-pinglun1"></i><span class="com_num"></span>评论 </a><div class="item qinglite-collect"><i class="iconfont icon-shoucang"></i>收藏 </div><div class="item qinglite_share"><i class="iconfont icon-fenxiang1"></i>分享 <div class="qrcode-modal"><img src="/api/pub/ewm" alt=""><p>手机扫一扫分享</p></div></div><div class="expand"></div><a onclick="miniProgram_navigateTo_func()" class="item qinglite_share_miniapp miniapp_show"><i class="iconfont icon-fenxiang1"></i>分享 </a><div class="item jubao qinglite-jubao miniapp_hide"><i class="iconfont icon-jubao"></i> 举报 </div><a href="#recommend" class="item iconfont icon-huidaodingbu"></a></div></div></article></div></main><script> let act_type = 1; let act_pro_id="45225"; let act_point = 0; let act_kind = 0; let act_time =90000; let act_page_id=""; </script><footer id="footer"><div class="container"><div class="links"><i class="copyright">2023©技术圈</i><a href="https://jishu.proginn.com">隐私协议</a><a href="https://jishu.proginn.com">用户协议</a><a href="https://jishu.proginn.com/about">关于我们</a></div></div></footer><link href="https://qinglite-1253448069.cos.ap-shanghai.myqcloud.com/css/layui/css/layui.css" rel="stylesheet"><script src="https://qinglite-1253448069.cos.ap-shanghai.myqcloud.com/css/layui/layui.js?v=v202311290103"></script><script> var $ = layui.jquery; </script><script src="https://cdn.qinglite.cn/js/pub.js?v=v202311290103"></script><script src="https://cdn.qinglite.cn/js/news_info.js?v=v202311290103"></script><link rel="stylesheet" href="https://qinglite-1253448069.cos.ap-shanghai.myqcloud.com/css/icon/iconfont.css?v=v202311290103"><script src="https://qinglite-1253448069.cos.ap-shanghai.myqcloud.com/css/icon/iconfont.js?v=v202311290103"></script></body></html>