Ten years of testing experience tells you what unit testing is testing

Today's front-end night snack, let's talk about what should be tested in unit tests in the project ?

With the development pace of the domestic Internet , it is sometimes not feasible to fully cover unit tests in front-end business projects, mainly because of the following stumbling blocks:

· The UI interaction is complex, and the path is difficult to cover comprehensively

·  The construction period is tight, and the development has no confidence in the long-term benefits brought by the practice of TDD and BDD

Product managers change requirements under the banner of "agile development" from time to time, making the test scripts that have just been written so hard completely invalid

In such a situation, it does not make much sense to blindly emphasize the logic coverage of unit tests. It is more meaningful to clarify where the application of unit tests can achieve the greatest marginal benefit.

Based on my actual experience in unit testing, the author lists three points of view on "what should be tested in unit testing" and communicates with you with some examples:

·  Unit testing is not all testing

·  Treat unit testing rationally

Single test is just a partial module test, one of many test schemes. Recognizing this can prevent us from testing for testing or testing for indicators.

At the same time, it should also be recognized that the coverage ability of the unit test itself is limited, and the PASS and 100% coverage of all use cases cannot guarantee that all logical paths of the tested module have correct behavior.

Whether to use unit testing for a module often depends on the logic stability and business type of the module

For example, for a low-level npm package project, unit testing is almost his only means of code quality assurance. At this time, unit testing should be used as much as possible to verify whether its behavior in various application scenarios meets expectations, so as to ensure its every time at the lowest cost. Quality of subcontracts and updates. For such projects, the thorough application of the BDD development model will also gain higher and higher development efficiency benefits.

For a UI component with complex functions, in addition to unit testing, there are also E2E testing, automated regression testing, and QA manual testing to ensure its code quality. At this time, the marginal benefit of using unit testing may not be the highest, and other means can be considered to return to its logic. You can also consider returning to verify the logic of each iteration through snapshot testing (snapshot) after the first version of functional verification is launched.

·  Simulation of boundary environment

·  Let the module travel through time and space

A very important significance of unit testing is to help us simulate boundary scenarios that are not easy to reach in QA manual testing (??) or even in online usage scenarios during the development phase, such as:

Simulate  the JS version of individual browsers

·  Simulate a certain URL state

Simulate  some sort of local cache state

·  Simulate the situation in different time zones

Mock  time goes over an hour (this is almost only possible with unit tests)

·  Wait

The marginal benefits of unit testing modules with such mocks are extremely high, often much faster than QA doing an equivalent mock.

For example, the following script implements the test of the expire function through the timer mock capability of jest:

const expire = (callback) => setTimeout(callback, 60000); // 一分钟以后过期
  test('到点就调用回调', () => {
    const callback = jest.fn();
    expire(callback);
    jest.advanceTimersByTime(59999);
    expect(callback).not.toBeCalled();
    jest.advanceTimersByTime(1);
    expect(callback).toBeCalledOnce();
  })

 

This code accurately simulates the running process of the macro task through jest.advanceTimersByTime, and synchronously completes the test of the asynchronous process that originally took one minute to verify once.

For another example, the following test script is used to test a utility function named catchFromURL, which can get the specified parameter from the current URL as the return value and erase the parameter from the URL at the same time.

This is very common in business scenarios (such as single sign-on) that require token information to be carried through the URL.

test('通过URL获取指定的参数值并抹去之', () => {
    const CURRENT_ORIGIN = document.location.origin;
    const testHref = `${CURRENT_ORIGIN}/list/2/detail?a=123b&b=true#section2`;
    history.replaceState(null, '', testHref);
    expect(catchFromURL('a')).toBe('123b');
    expect(document.location.href).toBe(`${CURRENT_ORIGIN}/list/2/detail?b=true#section2`);
  })

This test code uses jsdom to simulate the environment to be tested. The construction and simulation of the environment is actually a difficult point in unit testing. Due to some defects of jsdom itself (such as not implementing Navigator), simulating the correct browser environment in the node environment where the test script runs often requires a lot of Hack  technology . This point will be discussed emphatically in future night snacks.

So far:

 less is more

The test code does not need to care about the specific implementation of the tested module, just test several necessary process scenarios. On the one hand, this can reduce the time for writing test logic, and on the other hand, it can make business logic have greater freedom of implementation.

For a business module, the test script only needs to care about all the externalities associated with the module:

•  For a function module, control the modules it references, its inputs, and its side effects, and verify its outputs and effects on side effects.

·  For component modules, control the services it depends on, the subcomponents it depends on, its props and its events, and verify its rendering results and props.

Callback in the callback, and should not care about its state.

The script below tests a React component called ValidatableInput with the enzyme component testing tool. This component will trigger the onValidate callback when it is out of focus (blur), and pass in the inputValue parameter.

test('失焦时触发 onValidate', () => {
      const onValidate = jest.mock();
      const inputValue = '输入的内容';
      const wrapper = shallow(
        <ValidatableInput
          placeholder={''}
          value={inputValue}
          alert={''}
          onChange={onChange}
          onValidate={onValidate}
        />
      );
      wrapper.find('.validatable-input').first().simulate('blur');
      expect(onValidate).toBeCalledWith(inputValue);
    });

In the above test case , our test logic is completely based on behavior. We only care about the out-of-focus "action" and the "feedback" of executing the callback, without asserting anything about the state of the component.

In this way, the component can freely implement its internal logic according to its needs, such as adding the ability to provide value and onChange through an external Provider to become a controlled component. These implementation changes will not affect the validity of the current test case.

The above are some opinions on what should be tested with unit tests. Only by using unit tests where they are best at can we achieve twice the result with half the effort in a tight development rhythm.

Finally, I would like to thank everyone who has read my article carefully. Reciprocity is always necessary. Although it is not a very valuable thing, you can take it away if you need it:

These materials should be the most comprehensive and complete preparation warehouse for [software testing] friends. This warehouse has also accompanied tens of thousands of test engineers through the most difficult journey, and I hope it can help you! Partners can click the small card below to receive   

Guess you like

Origin blog.csdn.net/hlsxjh/article/details/132028750