preface

When you start a new project, you are sure to consider and revise every variable naming, parameter design, and directory structure. You want your design philosophy to be understood and maintained by subsequent developers. However, as the number of requirements increases, iterations lengthen, and developers change, the resulting code style and code quality deteriorates. So the r&d students began to design various tools in the hope of limiting the quality of the code and avoiding technical debt in advance.

Common Governance tools

Governance object governance Execution time
Code specification ESLint / StyleLint / Prettier Git Hook/Merge Request
Commit Message CommitLint Git Hook
Cyclomatic complexity/repetitive code Code inspection tool Local development/Merge Request
Sensitive information/security vulnerabilities Timed code scan Timing task
Bugs/boundary issues Unit testing Local development/Merge Request
Smoke/Inspection E2E test Local development/Merge Request/Scheduled task

earnings

Standardize code quality to reduce BUG risk before code goes live;

Good unit test cases are also the best code usage documentation;

Can rest assured of the reconstruction of the code, can rest assured that the code module to the new students to develop;

Technology selection

Why unit tests?

In the process of doing automated test research, many students have raised this question. Some students think that the coverage of unit test is too small, and the time cost to improve the coverage of single test is much higher than that of writing E2E tests. Some students think that unit test is more suitable for testing tool functions, and they are not good at testing Pages or components. I have also recommended a number of E2E testing frameworks and tools to me, such as NightWatch, Selenium, Puppeteer, etc. Next, let’s compare them.

E2E test

The definition and usage of E2E tests are already well documented in the community and will not be repeated here

Advantages:

  • The environment in which automated tests are run is closer to the user’s environment;
  • Writing test cases requires relatively few compatible environmental issues and data mocks;
  • Writing relatively few test cases can cover more lines of code;

Disadvantages:

  • Stable test environments can be provided upstream and downstream where the development environment is required

    • If a link in the upstream and downstream environment fails, the whole test process will be blocked.
  • It takes a long time to execute the test, which reduces the execution speed of the pipeline

    • You can test the following scenarios only after completing the front-user operations.
    • Wait for a response from the server interface before continuing.
  • Debug is relatively difficult

    • In the process of testing, the original test case cannot be implemented if the account risk control is encountered or the advertisement pop-up window does not meet the expectation;
  • The cost of triggering special logic branches is high

    • If you need to test a very special and small probability scenario, you need to configure a dedicated test account in advance, or reserve a back door for the code to ensure the stable execution of test cases.
  • The test environment needs to install more dependencies and heavy;

Unit testing

The image above is from Testing Vue.js Applications 1st Edition.

Advantages:

  • The test environment is more stable and less dependent on upstream and downstream environments.
  • The test execution speed is fast, and the test can be executed without starting the browser environment.
  • Strong flexibility, can independently simulate a variety of small probability scenarios;

Disadvantages:

  • More test cases need to be written, and the initial writing is painful;
  • The lack of global objects and methods in the browser environment, such as the test involving window, fetch, storage and other operations need to be mock developers;

What is recommended?

Jest

Comparison of test frameworks:

The test framework assertions Mock asynchronous The snapshot
Jest The default support The default support friendly The default support
Mocha Not supported (Configurable) Not supported (Configurable) friendly Not supported (Configurable)
Jasmine The default support The default support Don’t friendly The default support

Basic usage:

Description (" SRC/pages/components/auth ", () = > {test (" should return 100 ", () = > {/ / / / test code const result =... expect(result).toEqual(100); }); It ("should not return 100", () => {// test code // const result =... expect(result).not.toBe(100); }); });Copy the code

Test operation effect:

Testing Library

The core function

  • Powerful Query capabilities

    • Rich APIS are provided to make it easy for users to query and obtain elements in JS-DOM.
  • Simulate events that trigger the triggering of user actions

    • Provides basic fireEvent capabilities;
    • You can also use the more powerful user-event provided by the authorities;

Front-end frame-friendly

In addition to core functions, Testing Library also supports React, Vue, Angular, Svelte, Cypress and other frameworks to further reduce the cost of access unit Testing.

In addition, the @testing-library/jest-dom tool is provided. This tool can be used in the jEST runtime environment to provide more judgment methods for your assertions, such as:

  • 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

Previously, most React projects would choose to run based on the Enzyme. After React officially recommended Testing Library, more and more new projects began to migrate. The Enzyme provides developers with the ability to access React Component state, and you’ll often see assertions about Component state in test cases.

Source code to be tested:

import React from "react";

class Counter extends React.Component {
  constructor() {
    this.state = {
      count: 0,
    };
  }
  
  increment = () => {
    this.setState(({ count }) => ({ count: count + 1 }));
  }
  
  decrement = () => {
    this.setState(({ count }) => ({ count: count - 1 }));
  }
  
  render() {
    return (
      <div>
        <button onClick={this.decrement}>-</button>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>+</button>
      </div>
    );
  }
}

export default Counter;
Copy the code

Enzyme test case:

import React from "react"; import { shallow } from "enzyme"; import Counter from "./counter"; describe("<Counter />", () => { test("properly increments and decrements the counter", () => { const wrapper = shallow(<Counter />); // Expect (wrapper.state("count")).tobe (0); // Trigger the wrapper.instance().increment() method on the component instance; expect(wrapper.state("count")).toBe(1); wrapper.instance().decrement(); expect(wrapper.state("count")).toBe(0); }); });Copy the code

React Testing Library test cases:

import React from "react"; import { render, screen, fireEvent } from "@testing-library/react"; import Counter from "./counter"; describe("<Counter />", () => { it("properly increments and decrements the counter", () => { render(<Counter />); // Const counter = screen.getByText("0"); const incrementButton = screen.getByText("+"); const decrementButton = screen.getByText("-"); // Trigger the user to operate fireEvent.click(incrementButton); expect(counter.textContent).toEqual("1"); fireEvent.click(decrementButton); expect(counter.textContent).toEqual("0"); }); });Copy the code

From the above two examples, it is obvious that the two frameworks have different test philosophies. Enzyme prefers to control code to complete the test process, while React Testing Library prefers to be event-driven and simulate user operations to complete the test process. Now more and more Testing frameworks are approaching the latter.

It is recommended to use

Too many configuration parameters do not want to see how to do?

Jest provides NPM init-like capabilities. Executing the following command will create the minimum available configuration file for you

jest --init
Copy the code

How do I organize the test case files?

In the past to recommend

In the early days of organizing unit test cases, it was often recommended to create a __tests__ directory under the SRC directory and organize the test code according to the source directory structure. Some people would add an additional directory on top of this directory structure to distinguish unit from Integration.

Now recommend

More and more language native testing frameworks (Go), and newer testing frameworks are beginning to recommend putting test cases alongside your source code (also known as domain-driven management). The main benefits of this approach are:

  • Can quickly find your test cases and source code;
  • Reduce a lot of pointless folder nesting;
  • When a module is deprecated, we can quickly remove all files associated with it;

How do I configure the common environment?

The setupFilesAfterEnv configuration is provided in Jest, which points to our script file, the contents of which will be executed after the test framework environment is run and before the test case is executed:

import "@testing-library/jest-dom"; // Extend your assertion method import routeData from "react-router"; I18n = (key, options, fallback) => fallback; const mockLocation = { pathname: "/", hash: "", search: "? test=initial", state: "", }; const mockHistory = { replace: ({ search }) => { mockLocation.search = search; }}; // mock react router hooks beforeEach(() => { jest.spyOn(routeData, "useLocation").mockReturnValue(mockLocation); jest.spyOn(routeData, "useHistory").mockReturnValue(mockHistory); });Copy the code

How to polyfill browser methods and properties?

The BOM/DOM method is always used in the code, which goes directly to window.func = () => {… } cannot be overridden directly and can be overridden using defineProperty. It is recommended that all polyfills be placed in the polyfill directory and introduced in 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(),
  })),
});
Copy the code

How to mock third-party node_modules?

Create a folder __mocks__ under node_modules and create a JavaScript file with the same name as NPM package name, for example:

- node_modules
- __mocks__
  - react.js
  - lodash.js
- src
  - components
  - App.jsx
  - main.js
Copy the code

Now all references to react and LoDash in your code will point to react.js and lodash.js in the Mocks directory.

If the dependent third-party package has scope, you need to add a directory named scope, for example:

- node_modules
- __mocks__
  - @arco-design
    - web-react.js
- src
  - components
  - App.jsx
  - main.js
Copy the code

What about single-test coverage?

Statement coverage: Whether every statement is executed

Branches coverage: Whether each judgment has been executed

Functions coverage: Whether every function is executed

Lines: Whether each line is executed, in most cases equal to Statements

How to integrate single test into the r&d process?

It is difficult to unit test access to an existing project, so we can combine Gitlab’s ability to limit access to single test coverage for incremental code in the Merge Request segment. As shown in the figure below, we have checked the single-test coverage, Reviewer, title, and Work item binding of MR. Incremental code must meet at least 15% of the coverage red line to meet the Approve standard.