继续浏览精彩内容
慕课网APP
程序员的梦工厂
打开
继续
感谢您的支持,我会继续努力的
赞赏金额会直接到老师账户
将二维码发送给自己后长按识别
微信支付
支付宝支付

如何做到100%代码覆盖率?详解指南

陪伴而非守候
关注TA
已关注
手记 395
粉丝 62
获赞 285

大家好, 在这篇文章中,我会谈谈如何为你的项目达到100%的代码覆盖率。在这里介绍的方法将帮助你尽快达到这个目标。让我们开始吧!

Let's go

准备工作:

要实现100%的覆盖,很明显,你需要做些准备。识别以下组件,如果立即准备好这些组件,可以加快这个过程。

  1. 我们正在测试的是:这里值得确定测试的代码可以是。它可以是一个函数,也可以是一个包含多种函数、循环比如的独立模块。

  2. 我们需要哪些第三方库:现在有很多库可以供用户测试代码,比如Mocha

  3. 我应该用什么格式生成报告?:通常,像 Codecov 这样的服务需要生成 lcov 格式的报告。

一开始就明确了这一点,写测试就会变得简单得多,你就知道你在做什么以及为什么做。

现在,我们可以直接进入实践部分了。在这里,我会告诉你我是怎么做的。你也可以这样做。还有些小技巧可供参考。

来练练

首先,我需要测试带有 typescript 扩展的文件。这个文件可以在这里找到 here

测试文件 (点击可查看图像)

接下来,为了测试这种情况,我在项目根目录下创建了一个文件夹,名为 test。在那里,你可以放置专门用于测试的文件,例如 .test.ts。这与普通的 Typescript 文件相同,但区别在于它仅用于测试。有时他们不加 test,而是加 spec,但是我建议你还是创建带有这种扩展名的文件。

这个测试文件

现在,我们需要弄清楚一般如何测试。我们将使用MochaSinon,并来生成C8报告:

      "devDependencies": {
        "@types/mocha": "^10.0.9",
        "@types/sinon": "^17.0.3",
        "c8": "^10.1.2",
        "mocha": "^10.8.2",
        "sinon": "^19.0.2"
      }

切换到全屏模式,退出全屏

目前,我们需要把这些包连接起来,然后随着文章的推进,我们会逐渐添加更多库。

我们现在需要编写启动测试并生成报告的适当命令。以下是一些命令的列表:

      "scripts": {
        "test": "mocha --require ts-node/esm --experimental-specifier-resolution=node", // 运行测试脚本
        "test:watch": "mocha --watch --require ts-node/esm --experimental-specifier-resolution=node", // 监视并运行测试脚本
        "coverage": "c8 --reporter=lcov npm run test", // 生成代码覆盖率报告
        "coverage:default": "c8 npm run test" // 默认生成代码覆盖率报告
      },

全屏模式 退出全屏

非常有用的命令是 test:watch。在测试代码时,记得用这个命令,如果不使用它,每次测试后都得手动重启,这会让你懒得测试。

reset

很明显,要让typescript编译成普通的javascript,你需要安装一些额外的模块。

      "devDependencies": {
        "ts-node": "^10.9.2",
        "typescript": "^5.6.3"
      }
      // 开发依赖项,包括ts-node和typescript的版本信息

全屏显示 退出全屏

现在,我们直接来看看这个文件。假设一切准备就绪,我们现在来测试一下这个函数:

add.test.ts

    export function add(a: number, b: number): number {
        return a + b;
    }

全屏模式,退出全屏

要做到这一点,我们在测试用的文件中写入以下内容:

添加.ts

    import { strict as assert } from 'assert';
    import { add } from '../add';

    describe('add() 函数测试', () => {
        it('当把 2 和 3 相加时,结果应为 5', () => {
            const result = add(2, 3);
            assert.equal(result, 5);
        });

        it('当把 -1 和 1 相加时,应该得到 0', () => {
            const result = add(-1, 1);
            assert.equal(result, 0);
        });

        it('当把 -2 和 -3 相加时,结果应为 -5', () => {
            const result = add(-2, -3);
            assert.equal(result, -5);
        });

        it('当将 1.5 和 2 相加时,结果应为 3.5', () => {
            const result = add(1.5, 2);
            assert.equal(result, 3.5);
        });
    });

全屏 全屏退出

我们比较预期的结果和实际得到的结果。如果它们不同,那么测试就未通过,然后一切都会出错。这就是个玩笑,如果你创建了一个新功能并对其进行扩展,那么你需要确保旧的测试也能通过,这样才能确保代码没有问题。

如果我们在这个文件中有一个功能,那么实际上我们已经完全测试了它,也就是说,我们已经对整个文件进行了全面的测试。但是,当然,这是一个简单的例子,或者当需要操作DOM元素时呢?例如,模拟一个元素的click行为,或者检查一个元素是否有class。为此,你还需要安装下面描述的包:

    "开发依赖": {
        "@types/node": "^22.9.0",
        "jsdom": "^25.0.1",
        "jsdom-global": "^3.0.2",
    }

进入全屏,退出全屏

这两个包将使我们在Node.js中工作时,感觉就像在操作我们在网站上看到的真实DOM一样(当然,也有一些限制)。我们先来尝试测试一下元素的点击事件,并简单配置这两个包。首先,这两个包。

require("jsdom-global")();

// 引入jsdom-global模块,并将全局的DOMParser设置为window.DOMParser
global.DOMParser = window.DOMParser;

全屏模式 退出全屏

我们将替换 DOMParser,这样我们模块中的函数会使用这个替换版本,而不是在 Node.js 中的“未定义”。

现在,我们用一个具体的例子来试试整个事情。

setupClickHandler.ts

    export function 设置点击事件处理器(buttonId: string, callback: () => void): void {
        const button = document.getElementById(buttonId);
        if (!button) {
            throw new Error(`id为\"${buttonId}\"的按钮找不到`);
        }

        button.addEventListener('click', callback);
    }

切换到全屏 按钮 退出全屏

setupClickHandler.test.ts

    import { strict as assert } from 'assert';
    import sinon from 'sinon';
    import { setupClickHandler } from '../domManipulator';
    import 'jsdom-global/register';

    describe('setupClickHandler()', () => {
        let button: HTMLElement;

        beforeEach(() => {
            document.body.innerHTML = `
                <button id="testButton">点击我</button>
            `;
            button = document.getElementById('testButton')!;
        });

        afterEach(() => {
            document.body.innerHTML = '';
        });

        it('应将点击处理程序附加到按钮上', () => {
            const callback = sinon.spy();
            setupClickHandler('testButton', callback);
            button.click();
            assert.equal(callback.calledOnce, true);
        });

        it('如果找不到按钮,应抛出错误', () => {
            assert.throws(() => {
                setupClickHandler('nonExistentButton', () => {});
            }, /找不到具有 id "nonExistentButton" 的按钮/);
        });

        it('应正确处理多次点击事件', () => {
            const callback = sinon.spy();
            setupClickHandler('testButton', callback);
            button.click();
            button.click();
            assert.equal(callback.callCount, 2);
        });
    });

进入全屏模式, 退出全屏

现在,我们可以轻松地测试DOM的行为。但在测试过程中,还有一个处理异步功能的需要。是的,这个话题很复杂,因为即使测试API也需要花费很多时间,但在这里你可以稍微作弊,模拟服务器的行为。为此,我们需要安装以下包:

    "devDependencies": {
        "nock": "^13.5.6",
        "node-fetch": "^2.7.0",
    }

全屏 退出全屏

Nock将允许你复制一个API,这个复制的API会返回我们设定的响应。node-fetch包将简单地用能在浏览器中工作的fetch替换原有的,就像它在浏览器里那样工作。

让我们来设置这些软件包,

// 引入 node-fetch 模块
import fetch from "node-fetch";

// 将全局的 fetch 函数替换成 node-fetch
global.fetch = fetch as any;

点击全屏,然后退出全屏

那么我们来看看这个例子:

fetchData.ts
(获取数据的文件,通常用于TypeScript)

    import fetch from 'node-fetch';

    export async function fetchData(url: string): Promise<any> {
        const response = await fetch(url);
        if (!response.ok) {
            throw new Error(`HTTP 错误!状态码为:${response.status}`);
        }
        return response.json();
    }

进入全屏 退出全屏

获取数据测试.ts

    import { strict as assert } from 'assert';
    import nock from 'nock';
    import { fetchData } from '../fetchData';

    describe('fetchData()', () => {
        const baseUrl = 'http://testapi.com';

        beforeEach(() => {
            nock.cleanAll();
        });

        it('应在响应成功时返回数据内容', async () => {
            const mockData = { message: 'Success' };

            nock(baseUrl)
                .get('/endpoint')
                .reply(200, mockData);

            const data = await fetchData(`${baseUrl}/endpoint`);
            assert.deepEqual(data, mockData);
        });

        it('应在响应不成功时抛出异常', async () => {
            nock(baseUrl)
                .get('/endpoint')
                .reply(404);

            await assert.rejects(
                fetchData(`${baseUrl}/endpoint`),
                /HTTP error! status: 404/
            );
        });

        it('应能处理网络错误', async () => {
            nock(baseUrl)
                .get('/endpoint')
                .replyWithError('Network error');

            await assert.rejects(
                fetchData(`${baseUrl}/endpoint`),
                /网络错误/
            );
        });
    });

全屏模式 退出全屏模式

在这里,我们检查函数是否按预期工作,从API请求数据。如果返回HTTP状态码200,检查是否有错误;如果不是,检查其他问题。总的来说,建议在测试时,最好不要向真正的服务器发送请求,因为这不仅不稳定,还不可预测,所以最好自己搭建一个以避免各种错误。这将更快且更稳定。

我还注意到测试中有许多重复的部分,你可以把这些测试放到一个单独的函数里,并在代码中调用它,这样会更自然。

函数文件:functions.ts

    const e = (text: string, block: () => unknown, message: string) => {
      // 定义了一个名为 e 的函数,用于对特定的 block 函数进行断言测试,确保其会抛出带有特定 message 的异常。
      it(text, () => {
        assert.throws(block, {
          message: message
        });
      });
    };

进入全屏
退出全屏

文件名:compile.test.ts

describe("编译函数测试", () => {
  e(
    "如果 TEMPLATE 不是字符串,则会抛出错误",
    () => compile(123 as any),
    `${COMPILE_ERROR}: 模板未找到或传递的值类型不是字符串`
  );

全屏显示 退出全屏

这样我们就可以少写很多代码了。

现在我们的测试已经准备好了,我们需要通过Codecov来设置它们的执行。

与 Codecov 的集成:

首先,我们需要一个代码库。你可以使用不同的服务,但我会用GitHub来展示。你需要访问网站并以你方便的方式进行注册。之后,你会看到一个类似的个人账户页面:

个人主页

在这里,点击配置按钮,然后按照那里的步骤操作。我用的是GitHub Actions来配置,这样就会自动上传报告。下面是GitHub Actions的样子:

    name: CI

    on:
      push:
        branches: ["main"]
      pull_request:
        branches: ["main"]
    jobs:
      test:
        name: 覆盖率
        runs-on: ubuntu-latest

        steps:
          - uses: actions/checkout@v3

          - name: npm install
            run: npm install

          - name: npm run coverage
            run: npm run coverage

          - uses: codecov/codecov-action@v4
            with:
              token: ${{ secrets.CODECOV_TOKEN }}

点击全屏,点击退出全屏

每次提交时,它会自动运行并加载。如果一切顺利的话,你就可以为自己感到自豪,并在 README 中添加一个徽标,表明一切都经过测试哦耶!

徽章

结论

因此,你将能够做所有酷炫的事情,而这样的建议不仅适用于JavaScript,也适用于其他编程语言,不仅适用于文件夹结构,也适用于函数的分离。希望你能真的做得非常酷,项目测试覆盖率能到100%。

如果这篇文章对你有帮助的话,可以通过给这个项目点个星 (☆) 来支持作者一下。谢谢你的支持!

谢谢大家来看这篇文章!

Thanks you

打开App,阅读手记
0人推荐
发表评论
随时随地看视频慕课网APP