前端测试驱动开发 (TDD)

前端测试驱动开发 (tdd)

测试驱动开发(tdd)因提高代码质量和减少软件开发中的错误而被广泛认可。虽然 tdd 在后端和 api 开发中很常见,但它在前端开发中同样强大。通过在实现功能之前编写测试,前端开发人员可以尽早发现问题,确保一致的用户体验并自信地进行重构。在本文中,我们将在前端开发的背景下探索 tdd,讨论它的好处,并演练使用 react 和 javascript 的示例。

为什么在前端开发中使用 tdd

前端开发面临着独特的挑战,包括用户交互、渲染组件和管理异步数据流。 tdd 使开发人员能够在每个阶段验证其逻辑、组件和 ui 状态,从而提供帮助。 tdd 在前端的好处包括:

更高的代码质量:首先编写测试可以通过强制模块化来鼓励干净、可维护的代码。

提高开发人员信心:测试在代码投入生产之前捕获错误,减少回归错误。

更好的用户体验:tdd 确保组件和交互按预期工作,从而实现更流畅的用户体验。

重构安全:测试提供了一个安全网,允许开发人员进行重构而不必担心破坏功能。

tdd 在前端如何工作:红-绿-重构循环

tdd 流程遵循一个简单的三步循环:红色、绿色、重构。

红色 - 为新特性或功能编写测试。由于尚未实现任何代码,因此该测试最初应该会失败。

绿色 - 编写通过测试所需的最少代码。
重构 - 清理和优化代码而不改变其行为,确保测试继续通过。

让我们通过在 react 中构建简单搜索组件的示例来应用 tdd

示例:在 react 中为搜索组件实现 tdd
第 1 步:设置测试环境

要跟随,您需要:

react 用于创建 ui 组件。
用于编写和运行测试的 jest 和 react 测试库。

# install dependencies
npx create-react-app tdd-search-component
cd tdd-search-component
npm install @testing-library/react

第 2 步:红色阶段 – 编写失败的测试

假设我们想要构建一个搜索组件,根据用户输入过滤项目列表。我们将首先编写一个测试来检查组件是否正确过滤项目。

// search.test.js
import { render, screen, fireevent } from "@testing-library/react";
import search from "./search";

test("filters items based on the search query", () => {
  const items = ["apple", "banana", "cherry"];
  render(<search items={items} />);

  // ensure all items are rendered initially
  items.foreach(item => {
    expect(screen.getbytext(item)).tobeinthedocument();
  });

  // type in the search box
  fireevent.change(screen.getbyrole("textbox"), { target: { value: "a" } });

  // check that only items containing "a" are displayed
  expect(screen.getbytext("apple")).tobeinthedocument();
  expect(screen.getbytext("banana")).tobeinthedocument();
  expect(screen.querybytext("cherry")).not.tobeinthedocument();
});

这就是我们正在做的:

使用项目数组渲染搜索组件。
模拟在搜索框中输入“a”。
断言仅显示过滤后的项目。

现在运行测试将导致失败,因为我们还没有实现搜索组件。这是“红色”阶段。

第 3 步:绿色阶段 – 编写最少代码以通过测试

现在,让我们创建搜索组件并编写测试通过所需的最少代码。

// search.js
import react, { usestate } from "react";

function search({ items }) {
  const [query, setquery] = usestate("");

  const filtereditems = items.filter(item =>
    item.tolowercase().includes(query.tolowercase())
  );

  return (
    <div>
      <input
        type="text"
        placeholder="search..."
        value={query}
        onchange={(e) => setquery(e.target.value)}
      />
      <ul>
        {filtereditems.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default search;

在此代码中:

我们使用 usestate 来存储搜索查询。
我们根据查询过滤项目数组。
我们仅渲染与查询匹配的项目。

现在,运行测试应该会导致测试通过的“绿色”阶段。

第四步:重构——提高代码结构和可读性

测试通过后,我们就可以专注于提高代码质量了。一个小的重构可能涉及将过滤逻辑提取到一个单独的函数中,以使组件更加模块化。

// refactored search.js
import react, { usestate } from "react";

function filteritems(items, query) {
  return items.filter(item =>
    item.tolowercase().includes(query.tolowercase())
  );
}

function search({ items }) {
  const [query, setquery] = usestate("");
  const filtereditems = filteritems(items, query);

  return (
    <div>
      <input
        type="text"
        placeholder="search..."
        value={query}
        onchange={(e) => setquery(e.target.value)}
      />
      <ul>
        {filtereditems.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default search;

通过重构,代码更加清晰,过滤逻辑更加可复用。运行测试可确保组件仍按预期运行。

用于处理边缘情况的 tdd

tdd 中,考虑边缘情况至关重要。在这里,我们可以添加测试来处理诸如空项目数组或与任何项目都不匹配的搜索词之类的情况。
示例:测试边缘情况

test("displays no items if the search query doesn't match any items", () => {
  const items = ["apple", "banana", "cherry"];
  render(<search items={items} />);

  // type a query that doesn't match any items
  fireevent.change(screen.getbyrole("textbox"), { target: { value: "z" } });

  // verify no items are displayed
  items.foreach(item => {
    expect(screen.querybytext(item)).not.tobeinthedocument();
  });
});

test("renders correctly with an empty items array", () => {
  render(<search items={[]} />);

  // expect no list items to be displayed
  expect(screen.querybyrole("listitem")).not.tobeinthedocument();
});

这些测试进一步确保我们的组件能够处理异常情况而不会中断。

异步前端代码中的 tdd

前端应用程序通常依赖于异步操作,例如从 api 获取数据。 tdd 也可以应用在这里,尽管它需要在测试中处理异步行为。
示例:测试异步搜索组件

假设我们的搜索组件从 api 获取数据,而不是作为 prop 接收数据。

// asyncsearch.js
import react, { usestate, useeffect } from "react";

function asyncsearch() {
  const [query, setquery] = usestate("");
  const [items, setitems] = usestate([]);

  useeffect(() => {
    async function fetchdata() {
      const response = await fetch(`https://api.example.com/search?q=${query}`);
      const data = await response.json();
      setitems(data);
    }
    if (query) fetchdata();
  }, [query]);

  return (
    <div>
      <input
        type="text"
        placeholder="search..."
        value={query}
        onchange={(e) => setquery(e.target.value)}
      />
      <ul>
        {items.map((item) => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default asyncsearch;

在测试中,我们可以使用 jest.fn() 来模拟 api 响应。

import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import AsyncSearch from "./AsyncSearch";

global.fetch = jest.fn(() =>
  Promise.resolve({
    json: () => Promise.resolve(["apple", "banana"]),
  })
);

test("fetches and displays items based on the search query", async () => {
  render(<AsyncSearch />);

  fireEvent.change(screen.getByRole("textbox"), { target: { value: "a" } });

  // Wait for the items to be fetched and rendered
  await waitFor(() => {
    expect(screen.getByText("apple")).toBeInTheDocument();
    expect(screen.getByText("banana")).toBeInTheDocument();
  });
});

前端 tdd 最佳实践

从小处开始:专注于一小部分功能并逐渐增加复杂性。
编写清晰的测试:测试应该易于理解并且与功能直接相关。
测试用户交互:验证用户输入、点击和其他交互。
覆盖边缘情况:确保应用程序优雅地处理异常输入或状态。
用于异步测试的模拟 api:模拟 api 调用以避免测试期间对外部服务的依赖。

结论

测试驱动开发为前端开发带来了众多优势,包括更高的代码质量、减少错误和提高信心。虽然 tdd 需要思维方式和纪律的转变,但它成为一项宝贵的技能,尤其是在处理复杂的用户交互和异步数据流时。遵循 tdd 流程(红色、绿色、重构)并逐渐将其集成到您的工作流程中将帮助您创建更可靠、可维护和用户友好的前端应用程序。

以上就是前端测试驱动开发 (TDD)的详细内容,更多请关注其它相关文章!