如何获得 100% 的代码覆盖率?✅

640 阅读6分钟

大家好,在本文中,我将讨论如何为您的项目实现 100% 的代码覆盖率。这里描述的技术将使您能够尽快做到这一点。好吧,让我们开始吧!

动态图片我们走吧

准备

为了确保 100% 覆盖,显然您需要做好准备。首先,有必要确定以下组件,如果立即做好准备,将加快此过程:

  1. 测试内容:这里需要确定要测试的代码片段。它可以是单个函数,也可以是包含一系列函数、循环等的独立模块。
  2. 我们需要哪些第三方库:今天,有许多库(例如Mocha等)允许用户测试代码。
  3. 我应该使用什么格式来生成报告:通常,对于像Codecov这样的服务,我们需要以lcov格式生成报告。

如果一开始就决定了这一点,你编写测试就会变得更容易,因为你会明白你在做什么以及出于什么目的。

现在,值得进入实践部分。在其中,我将举一个例子来说明我如何进行测试,如何实现这一点,以及通常有哪些技巧可以实现这一点。

实践

因此,首先,我需要使用 typescript 扩展测试我拥有的文件。该文件可以在此处查看:

测试文件

接下来,为了测试这种情况,我在项目的根结构中创建了一个文件夹。它被称为test。在那里,你可以给出特殊的分割,例如.test.ts。这是相同的 TypeScript 文件,但不同之处在于它仅用于测试。有时他们不添加测试,或者他们添加 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。测试代码时,请确保使用此命令,因为如果不使用它,则每次都必须手动重新启动测试,这会打消您测试任何内容的积极性。

动态图片重置

之后,很明显,如果没有附加模块,typescript 将无法编译为常规 javascript。为此,您需要安装更多以下模块:

  "devDependencies": {
    "ts-node": "^10.9.2",
    "typescript": "^5.6.3"
  }

现在,让我们直接进入文件本身。假设我们已经设置好了一切,并想对这个函数进行测试:

添加.测试.ts

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

为此,我们在测试文件中写入以下内容:

添加.ts

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

describe('Function add()', () => {
    it('should return 5 when adding 2 and 3', () => {
        const result = add(2, 3);
        assert.equal(result, 5);
    });

    it('should return 0 when adding -1 and 1', () => {
        const result = add(-1, 1);
        assert.equal(result, 0);
    });

    it('should return -5 when adding -2 and -3', () => {
        const result = add(-2, -3);
        assert.equal(result, -5);
    });

    it('should return 3.5 when adding 1.5 and 2', () => {
        const result = add(1.5, 2);
        assert.equal(result, 3.5);
    });
});

我们会比较预期结果和实际结果。如果结果不同,则测试未通过,然后一切都会失败。这是一个笑话,如果你创建了一个新功能,然后扩展它,那么你需要确保旧测试通过,这样你才能确保代码编写正确。

如果文件中有这个函数,那么实际上我们已经对它进行了全面测试,也就是说,我们已经用测试覆盖了整个文件。但是,当然,这是一个简单的例子,但如果我们需要使用 DOM 元素怎么办?例如,click在元素上模仿,或者检查是否存在class。为此,您还需要安装下面描述的软件包:

"devDependencies": {
    "@types/node": "^22.9.0",
    "jsdom": "^25.0.1",
    "jsdom-global": "^3.0.2",
}

这两个包将允许我们在 Node.js 中工作,就像我们在网站上看到的真实 DOM 一样(当然有限制)。让我们尝试测试元素上的单击,并通常配置这两个模块:

require("jsdom-global")();

global.DOMParser = window.DOMParser;

在这里,我们将进行替换DOMParser,以便模块中的函数拾取它而不是 Node.js 中的未定义。

现在,让我们尝试用一个具体的例子来测试整个事情:

setupClickHandler.ts

export function setupClickHandler(buttonId: string, callback: () => void): void {
    const button = document.getElementById(buttonId);
    if (!button) {
        throw new Error(`Button with id "${buttonId}" not found`);
    }

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

设置ClickHandler.测试.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">Click Me</button>
        `;
        button = document.getElementById('testButton')!;
    });

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

    it('should attach a click handler to the button', () => {
        const callback = sinon.spy();
        setupClickHandler('testButton', callback);
        button.click();
        assert.equal(callback.calledOnce, true);
    });

    it('should throw an error if the button is not found', () => {
        assert.throws(() => {
            setupClickHandler('nonExistentButton', () => {});
        }, /Button with id "nonExistentButton" not found/);
    });

    it('should handle multiple clicks correctly', () => {
        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,以便为我们提供配置的响应。node-fetch 包将简单地替换fetch为浏览器中可用的包。

让我们配置这些包:

import fetch from "node-fetch";

global.fetch = fetch as any;

我们继续看例子:

fetchData.ts

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 error! status: ${response.status}`);
    }
    return response.json();
}

fetchData.测试.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('should return data when the response is successful', async () => {
        const mockData = { message: 'Success' };

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

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

    it('should throw an error when the response is not successful', async () => {
        nock(baseUrl)
            .get('/endpoint')
            .reply(404);

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

    it('should handle network errors', async () => {
        nock(baseUrl)
            .get('/endpoint')
            .replyWithError('Network error');

        await assert.rejects(
            fetchData(`${baseUrl}/endpoint`),
            /Network error/
        );
    });
});

在这里,我们检查我们的函数如何工作,从 API 请求数据。如果我们得到 HTTP 代码 200,那么我们检查一件事,如果有错误,那么再检查另一件事。一般来说,在测试时,最好不要向真实服务器发送请求,因为这不稳定且不可预测,所以最好自己设置以避免出现一堆错误。这样会更快。

另外,我注意到测试本身是重复的,因此您可以将大部分测试移到单独的函数中并在代码中调用它:

函数.ts

const e = (text: string, block: () => unknown, message: string) => {
  it(text, () => {
    assert.throws(block, {
      message
    });
  });
};

编译.测试.ts

describe("compile function", () => {
  e(
    "throws an error if the TEMPLATE is not a stringthrows an error if the TEMPLATE is not a string",
    () => compile(123 as any),
    `${COMPILE_ERROR}: Template was not found or the type of the passed value is not string`
  );

这样我们就可以编写更少的代码。

现在我们已经准备好测试,我们需要设置它们的卸载。我们将通过 Codecov 进行此操作

与 Codecov 集成

首先,我们需要有一个存储库。您可以使用不同的服务,但我将在GitHub上向您展示。首先,您需要访问该网站并以方便的方式注册。之后,您将看到一个这样的个人帐户:

个人内阁

在这里,单击配置按钮并按照指南中描述的步骤进行操作。我通过 Github Actions 进行了配置,因此它会自动将报告上传到那里。Github 操作如下所示:

name: CI

on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]
jobs:
  test:
    name: Coverage
    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% 的测试覆盖率。

如果本文对你有帮助,你可以给项目一星☆来支持作者。谢谢!

感谢大家阅读这篇文章!

动态图片感谢您

原文

【智答专家】您身边免费的GPT4.0人工智能Ai助手,免翻!!!无套路!国内直连,支持文本生成、问答、多语言支持、个性化建议、图片生成、代码纠正等等。