51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 1219|回复: 0
打印 上一主题 下一主题

[转贴] 前端单元测试入门与最佳实践(2)

[复制链接]
  • TA的每日心情
    无聊
    3 天前
  • 签到天数: 1050 天

    连续签到: 1 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-1-17 14:02:36 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
     Testing Library
      核心功能
      ·强大的 Query 能力
        提供丰富的 API 使得用户可以轻松的在 JS-DOM 中查询、获取元素;
      · 模拟触发用户操作触发的事件
        提供了基础的 fireEvent 能力;
        也可以使用官方提供的更强大的 user-event。
      前端框架友好
      在核心功能之上,Testing Library 还支持 React、Vue、Angular、Svelte、Cypress 等框架,进一步的降低接入单元测试的成本。
      除此之外,还提供了 @testing-library/jest-dom 工具,在 jest 的运行环境下引入该工具,可以为你的断言提供更多的判断方法,比如:
      toBeDisabled
      toBeEnabled
      toBeEmptyDOMElement
      toBeInTheDocument
      toBeInvalid
      toBeRequired
      toBeValid
      toBeVisible
      toContainElement
      toContainHTML
      toHaveAccessibleDescription
      toHaveAccessibleName
      toHaveAttribute
      toHaveClass
      toHaveFocus
      toHaveFormValues
      toHaveStyle
      toHaveTextContent
      toHaveValue
      toHaveDisplayValue
      toBeChecked
      toBePartiallyChecked
      toHaveErrorMessage
      React Testing Library vs Enzyme
      在此之前大部分的 React 项目会选择基于 Enzyme 来作为运行时,在 React 官方推荐 Testing Library 后越来越多的新项目开始迁移过来。两者的测试用例哲学有着明显的差异,Enzyme 提供给开发者访问 React Component state 的能力,你常常能够在测试用例中看到对组件状态的断言。
      待测试源码:
    1. import React from "react";
    2.   class Counter extends React.Component {
    3.     constructor() {
    4.       this.state = {
    5.         count: 0,
    6.       };
    7.     }
    8.     
    9.     increment = () => {
    10.       this.setState(({ count }) => ({ count: count + 1 }));
    11.     }
    12.     
    13.     decrement = () => {
    14.       this.setState(({ count }) => ({ count: count - 1 }));
    15.     }
    16.     
    17.     render() {
    18.       return (
    19.         <div>
    20.           <button onClick={this.decrement}>-</button>
    21.           <p>{this.state.count}</p>
    22.           <button onClick={this.increment}>+</button>
    23.         </div>
    24.       );
    25.     }
    26.   }
    27.   export default Counter;
    复制代码
    Enzyme 测试用例:
    1. import React from "react";
    2.   import { shallow } from "enzyme";
    3.   import Counter from "./counter";
    4.   describe("<Counter />", () => {
    5.     test("properly increments and decrements the counter", () => {
    6.       const wrapper = shallow(<Counter />);
    7.       // 对组装内部状态进行断言
    8.       expect(wrapper.state("count")).toBe(0);
    9.       // 触发组件实例上的方法
    10.       wrapper.instance().increment();
    11.       expect(wrapper.state("count")).toBe(1);
    12.       wrapper.instance().decrement();
    13.       expect(wrapper.state("count")).toBe(0);
    14.     });
    15.   });
    复制代码
    React Testing Library 测试用例:
    1. import React from "react";
    2.   import { render, screen, fireEvent } from "@testing-library/react";
    3.   import Counter from "./counter";
    4.   describe("<Counter />", () => {
    5.     it("properly increments and decrements the counter", () => {
    6.       render(<Counter />);
    7.       // 通过 getByText 或者节点,也可以通过无障碍属性获取
    8.       const counter = screen.getByText("0");
    9.       const incrementButton = screen.getByText("+");
    10.       const decrementButton = screen.getByText("-");
    11.       // 触发用户操作
    12.       fireEvent.click(incrementButton);
    13.       expect(counter.textContent).toEqual("1");
    14.       fireEvent.click(decrementButton);
    15.       expect(counter.textContent).toEqual("0");
    16.     });
    17.   });
    复制代码
    从上面两个示例中可以明显看出两种框架的测试哲学不同之处,Enzyme 更偏向于对代码进行控制来完成测试流程,React Testing Library 更倾向于事件驱动,通过模拟用户操作来完成测试流程,现在越来越多的测试框架开始向后者靠近。
      推荐使用
      配置参数太多不想看怎么办?
      jest 提供了类似 npm init 的能力,执行下方命令会为你创建最小可用的配置文件:
    1.  jest --init
    复制代码
    如何组织测试用例文件?
      过去推荐

     在早期组织单元测试用例时,通常建议在 src 目录下创建 __tests__ 目录,按照源码的目录结构组织测试代码,部分人会在这个目录结构之上再增加一层目录用以区分 unit 和 integration。
      现在推荐
      越来越多的语言自带的测试框架(Go),还有新测试框架开始推荐将测试用例放在你的源码边(也有人称之为领域驱动管理),这个方式主要带来的好处有:
      ·能够快速的找到你的测试用例和源码;
      ·减少许多无意义的文件夹嵌套;
      ·当一个模块废弃的时候,我们可以快速的移除所有与他相关的文件。

    如何配置通用环境?
      在 Jest 中提供了 setupFilesAfterEnv 配置,可以指向我们的脚本文件,该脚本的内容会在测试框架环境运行之后,执行测试用例之前执行:
    1. import "@testing-library/jest-dom"; // 扩展你的断言方法
    2.   import routeData from "react-router";
    3.   // mock 全局 i8n 方法,放弃网络请求,直接使用降级处理
    4.   window.i18n = (key, options, fallback) => fallback;
    5.   const mockLocation = {
    6.     pathname: "/",
    7.     hash: "",
    8.     search: "?test=initial",
    9.     state: "",
    10.   };
    11.   const mockHistory = {
    12.     replace: ({ search }) => {
    13.       mockLocation.search = search;
    14.     },
    15.   };
    16.   // mock react router hooks
    17.   beforeEach(() => {
    18.     jest.spyOn(routeData, "useLocation").mockReturnValue(mockLocation);
    19.     jest.spyOn(routeData, "useHistory").mockReturnValue(mockHistory);
    20.   });
    复制代码
     如何 polyfill 浏览器的方法和属性?
    代码中肯定会使用到 BOM / DOM 方法,这类方法通过直接 window.func = () => { ... } 是无法直接覆写的,此时可以使用 defineProperty 重写。这类 polyfill 建议统一放到 polyfill 目录下,在 setupFilesAfterEnv 时全部引入即可。
    // test-utils/polyfill/matchMedia.js
      Object.defineProperty(window, "matchMedia", {
        writable: true,
        value: jest.fn().mockImplementation(query => ({
          matches: false,
          media: query,
          onchange: null,
          addListener: jest.fn(), // Deprecated
          removeListener: jest.fn(), // Deprecated
          addEventListener: jest.fn(),
          removeEventListener: jest.fn(),
          dispatchEvent: jest.fn(),
        })),
      });

    如何 mock 第三方 node_modules?
      在 node_modules 同级目录下创建文件夹 __mocks__,然后创建和 npm package name 同名的 JavaScript 文件即可,例如:
    1.   - node_modules
    2.   - __mocks__
    3.     - react.js
    4.     - lodash.js
    5.   - src
    6.     - components
    7.     - App.jsx
    8.     - main.js
    复制代码
    现在你的代码中对于 react 和 lodash 的引用全都会指向 mocks 目录下的 react.js 和 lodash.js。
      如果依赖的第三方包具有 scope,则需要增加一个目录,命名为 scope 即可,例如:
    1.  - node_modules
    2.   - __mocks__
    3.     - @arco-design
    4.       - web-react.js
    5.   - src
    6.     - components
    7.     - App.jsx
    8.     - main.js
    复制代码
     单测覆盖率怎么看?
      Statements(语句覆盖率):是否每个语句都执行了。
      Branches(分支覆盖率):是否每个判断都执行了。
      Functions(函数覆盖率): 是否每个函数都执行了。
      Lines(行覆盖率):是否每行都执行了,大部分情况下等于 Statements。

    如何将单测接入到研发流程中?
      对一个现存项目进行单元测试接入是比较困难的,我们可以结合 Gitlab 的能力,在 Merge Request 的环节对增量代码进行单测覆盖率的准入红线限制。如下图,我们对 MR 的单测覆盖率、reviewer、title、work item 绑定均进行了检查,增量代码必须满足至少 15% 的覆盖率红线才能够达到 approve 标准。










    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

    x
    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-11-24 10:46 , Processed in 0.065216 second(s), 24 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表