current position:Home>Front end test collection - how to write front-end test to ensure code quality?

Front end test collection - how to write front-end test to ensure code quality?

2021-08-27 08:58:57 EvalStudio

“ automated testing ” There are thousands of articles related to this topic , But if you look closely, you will find that a large part of them are back-end or BFF Test of , Their test coverage can almost reach 100%( According to different requirements of different teams ).

But when it comes to front-end testing , Few people say they can do 100% Coverage of , Of course, there's a reason , Front-end UI Change too fast , Adjust often , Pure right UI Whether the test is worth taking the time to write ? Most front ends will Say NO.

Today's article is a collection of front-end tests , This paper analyzes several test types included in front-end test from the principle of test pyramid , The cost performance of each test type is introduced 、 Usage scenarios and test frameworks or tools commonly used for each test . Because unit testing is often the largest proportion of automated testing , So from 2 This paper introduces how to write a unit test , How to reasonably use test avatars to isolate test dependencies , Improve test independence . Expect front-end test strategy development & Practice can help improve software quality , Reduce “ 8、 ... and (B) o (U) Brother (G)”.

Test pyramids

image.png

The test pyramid is Mike Cohn stay 《Succeeding with Agile》 The concept of , It divides the test into three layers :UI Test layer 、Service Test layer and unit test . The higher the cost from the bottom up , The less efficient , So it's recommended to go from top to bottom , The more you write down, the more test cases . But from today's technical point of view, this is not entirely the case , Because now the front end UI The cost of layer testing is much less than before , for example UI Snapshot test 、 The key to relying on the front-end framework DOM Element testing, etc .

Benchmarking now , The most significant reference of the whole test pyramid is :

  • (1) Different levels of test granularity
  • (2) The higher the level is. , Write fewer tests , Because of its low cost performance .

So now the more appropriate front-end test pyramid should be similar to :

  • (1)E2E test

    Some team front ends also include E2E test , In order to ensure stable functional availability , however E2E The cost of testing is relative to other 2 The floor is higher , Therefore, the functions of the main process should be as E2E Test of .

  • (2) Integration testing

    When you encounter complex front-end business , State management is often introduced , from DOM Operation trigger status change , cause re-render, Integration testing can be used to ensure that the whole process is correct .

  • (3) unit testing

    Speaking of unit testing , Back end or BFF Unit test , It's easy for everyone to understand , So what does the front-end unit test generally include ? Unit tests apply to Util Method , commonly util Methods are pure functions , The unit test of pure function is very easy to write , Because every input and output is clear . Of course, in addition to the above , It also includes unit tests for data processing in state management 、UI Unit testing at the level of . Specific test preparation, follow-up to see specific cases .

Common front-end test methods

The test pyramid was introduced earlier , So what are the test methods often used in the front-end field ? The following are the test methods commonly used in practice :

unit testing

In the front end , A unit can be a UI Components 、 One Util Method 、 A state management processing function 、 A business logic function, etc , These functions can be guaranteed through unit testing .

Here are Jest A test case in the document , This test is for sum Methods were tested , In real projects , Often write a lot of custom methods , The responsibilities of each method should be very clear , You can specify the input and output parameters , Test each scenario .

function sum(a, b) {
  return a + b;
}
module.exports = sum;
 Copy code 
const sum = require('./sum');

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});
 Copy code 

Snapshot test

Snapshot test , seeing the name of a thing one thinks of its function , It's a test for snapshots . In the front-end test framework , Snapshot tests are often performed for the first time , Make a snapshot , With Jest Case study ,React The snapshot test of the component is as follows :

import React from 'react';
import renderer from 'react-test-renderer';
import Link from '../Link.react';

it('renders correctly', () => {
  const tree = renderer
    .create(<Link page="http://www.facebook.com">Facebook</Link>)
    .toJSON();
  expect(tree).toMatchSnapshot();
});
 Copy code 

For details, please refer to jest In document Snapshot test (www.jestjs.cn/docs/snapsh…)

The above tests run for the first time , A snapshot file was created as follows , Saved the snapshot results of the first run . When the component changes , When you run the snapshot test again , New snapshot results will be regenerated , This new snapshot result will be compared with the previously saved snapshot file , If it's not consistent , The snapshot test will fail . Of course, test frameworks generally provide commands to update snapshots ,Jest In the framework U command .

exports[`renders correctly 1`] = ` <a className="normal" href="http://www.facebook.com" onMouseEnter={[Function]} onMouseLeave={[Function]} > Facebook </a> `;
 Copy code 

According to the analysis of the above snapshot test , It can be found that for UI Components are not suitable for snapshot testing , Because the snapshot is updated almost every time to pass the test , Then you lose the meaning of snapshot testing . Therefore, snapshot testing is generally applicable to stable front-end pages or components , You can avoid erroneous changes through snapshot testing .

Contract test

Now many teams are front-end and back-end separated teams , It often takes a lot of time on joint commissioning ( For example, interface availability ? Whether the interface complies with the document ? Whether the interface is stable ?). Contract testing is a contract between provider and consumer based on interface , This contract is generally an interface specification document , Often contains requests URL、Method、 Request parameters 、response Data structure, etc . In a scene where the front and back ends are separated , The contract test is more biased towards Service Between API test , Mainly to decouple the dependencies between services , To speed up the API The speed of verification .

The front end is the consumer of the contract , What contract testing often does is create corresponding unit tests , In this unit test, a test that conforms to the contract specification is initiated request To Mock Server Get the corresponding response To verify the results , When there is no mock server when , The front end can also add contract compliant mock response File to decouple API. Common tools are based on YAML Of Swagger Specification, And based on JSON Format Pact Specification, Refer to their documentation for details .

E2E test

E2E test , Also called end-to-end testing , More functional automated testing . This kind of test often simulates a real user operation to verify whether the results meet the expectations .E2E The test is usually a black box test , Focus on whether the whole system meets user expectations .

There are many tools that can be used for the above test methods , See the next chapter for details .

Front end test tools

Testing tools generally include testing frameworks Test Framework、 Assertions library Assertion Library、mock library 、 Test report, tool library, etc .

Let's introduce some libraries commonly used in front-end testing , Attach the official document .

Testing Library

Test Library Is a collection of test libraries , It simulates user interaction , Test by verifying the external state change of the component , In this way, you can not go deep into the implementation details of the component , Improve the efficiency of testing . The following is an overview of React Testing Library, Of course Testing Library There is also vue testing library and angular testing library Such as the library .

at present React The officially recommended test plan is React Testing Library + Jest The combination of .

We recommend using React Testing Library, It makes it as easy to write test cases for components as it is for end users . When used React edition <= 16 when , have access to Enzyme Test tools , It makes it easy to React The output of the component is asserted 、 Control and traverse .

React Testing Library yes Testing Library A test library in , When you want to write maintainable React components test , No need to pay attention React Detailed internal implementation of the component , You can use this library for component testing .React Testing Library Provides many functions to locate elements , The positioned element can be used as an assertion or user interaction . Please refer to file .

Reference link :

Jest

Jest yes Facebook Open source testing framework , Built in JSDOM Running environment 、 Assertions library , Provide coverage (coverage)、 Snapshot comparison (snapshots)、 Analog functions (Mock Funtion) And so on , It is one of the most widely used front-end testing frameworks at present . If you are using Create React App The initialization of the React frame , It's built in Jest As its test library . If you are from 0 Start building the framework , According to official documents, it is also easy to introduce Jest, I won't go into details here ,Jest It's very easy to get started , If necessary, you can check 《JEST file 》.

Reference link :

Mocha

Mocha It's based on JS Flexible testing framework , Contains asynchronous processing (beforeEach、afterEach Wait for the hook function ,Promise To deal with 、timeout Processing and so on )、 Concise test report 、 And you can customize the assertion tool :

These assertions can be added and used by users .

Jasmine

Angular The default testing framework is Karma( from Google The front-end test run framework developed by the team ) + Jasmine.

Jasmine It is a fully functional testing framework , Has a complete assertion method 、Setup and Teardown Method 、 Asynchronous processing 、Mock Functions, etc . Jasmine Document links :github.com/jasmine/jas…

Cypress

Cypress Is in Mocha API Developed on the basis of E2E The test framework , Does not rely on the front-end framework , There is no need for other test tool Libraries , Simple configuration , And provides a powerful GUI Graphical tools , Can automatically capture the screen , It can also be used in the test process Debug . It is a popular end-to-end testing tool . Specific reference documentation , Write use cases according to the document and get familiar with them . Cypress Document links :www.cypress.io/

There are many other tool Libraries , Be similar to Jasmine、Selenium、Puppeteer、phantomjs I'm not going to list , If necessary, you can see the document and use it by yourself , More libraries can be seen Another little partner sorted out the test related Library github.com/huaize2020/…).

Front end test strategy

The front end has a lot of UI Interaction , The cost performance of this part of the test is relatively low , because DOM The structure often changes , If you really need testing, consider snapshot testing .

In the front-end project, what needs to be tested is the logical part of the front-end code , Now the front end is not just UI, It also includes state management and business logic , For this part, unit testing can be used to ensure .

In the test strategy, you can also consider writing integration tests , For example, simulate a dom operation , Trigger state changes and re rendering , Finally, verify whether the rendering results meet the expectations . This type of test is also a kind of black box test , Because state changes and... Were not exposed in the test re-render The logic of , Instead, the function is guaranteed by triggering the operation to verify the final result .

Of course, different projects still need to consider the particularity of the project , According to the focus and cost of different tests, consider what kind of test strategy to adopt .

How to write unit tests well

How to write unit tests can be learned from 2 Write from a different angle , The business perspective and the technical perspective do not conflict with each other , Just think differently :

Write unit tests from a business perspective

From a business perspective, you can use Given\When\Then To describe a unit test ,given What kind of premise is provided ( It is generally used to prepare test data ),when As something ( Generally, it is to call specific methods ),then What happened ( Usually test assertions ).

Look at a specific case : Define a unit test , The test has its own description and test content . The test content is divided into preparation products data (given)、 call getTotalAmount The function under test (when)、expect Assertion calculation result result (then).

//  Realization 
const getTotalAmount = (products) => {
  return products.reduce((total, product) => total + product.price, 0); 
}
 Copy code 
//  test 
it('should return total amount 600 when there are three products priced 100, 200, 300', () => {
  // given -  Prepare the data 
  const products = [
    { name: 'nike', price: 100 },
    { name: 'adidas', price: 200 },
    { name: 'lining', price: 300 },
  ]

  // when -  Call the function under test 
  const result = getTotalAmount(products)

  // then -  assertion results 
  expect(result).toBe(600)
})
 Copy code 

Write unit tests from a technical point of view

From a technical point of view, it can be divided into four test stages : Preparation stage (Setup) 、 Execution phase (Exercise) 、 Validation phase (Verify)、 Disassembly stage (Teardown).

Most test frameworks have many corresponding methods for the four phases , The following is a case : adopt beforeEach Get ready for the test , In a single test , Execute the specific method under test , adopt expect Assert validation test results , Pass after the test afterEach Empty data , Avoid affecting other tests , Need to be in Teardown The data cleared in the phase is generally shared by different tests .

//  test 
describe("getTotalAmount test", function () {   
    let products=[]
    // Setup
    beforeEach(function () {        
        products = [
          { name: 'nike', price: 100 },
          { name: 'adidas', price: 200 },
          { name: 'lining', price: 300 },
        ]   
    });    

    // Teardown
    afterEach(function () {        
      products=[] 
    });       

    it("should return total amount 600 when there are three products priced 100, 200, 300", function () {
        // Exercise
        const result = getTotalAmount(products)
        // Verify
        expect(result).toBe(600)    
    });
});
 Copy code 

Test stand in

Test stand in (Test Doubles) In order to isolate some dependencies that affect the test , For example, third-party UI Components 、 Third party tools 、 Interface, etc. . After isolating dependencies , You can focus on the functions of the front end itself , Ensure its own code function . Test avatars are generally divided into the following uses :

Test Stub

Stubs provide canned answers to calls made during the test, usually not responding at all to anything outside what's programmed in for the test.

Test Stub Provide an encapsulated response for each call , Generally, it will not respond to requests outside the test . Stub Often translated into “ pile ”, In the test , Frequently used Stub To completely replace the component under test ( System ) Dependent objects in , We set the output for it ( Return value ), It's like the component under test ( System ) A pile in , Only for testing , We will not verify the logic inside the pile, nor will we verify whether the pile is called . All you need to test is its impact , That is, the preset return value .

image.png

Be similar to jest.fn() It's just one. Stub, Sure mock Its return value , You can also return a by default undefined Methods .

const myObj = {
  doSomething() {
    console.log('does something');
  }
};

test('stub .toHaveBeenCalled()', () => {
  const stub = jest.fn();
  stub();
  expect(stub).toHaveBeenCalled();
});

test('Stub jest.fn() return value', () => {
  let stub = jest.fn().mockReturnValue('default');
  expect(stub()).toBe('default');
})
 Copy code 

Test Spy

Spies are stubs that also record some information based on how they were called. One form of this might be an email service that records how many messages it was sent.

As described in the figure below ,Test Spy It refers to the use of test avatars to capture the calls made by the tested system to other components , So that it can be verified by test in subsequent tests . It is similar to the later mentioned Mock The big difference “ Capture ”,Mock yes setUp When the time is right Object to Mock fall , and Spy It captures the call .

image.png

For example, in the following case , Added incrementSpy Test stand in , Captured counter Of increment Method call , When performing tests , Verify that the test avatar is indeed called 1 Time ( Of course not increment The specific implementation of ).

let count = 0;
const counter = {
  increment() {
    count += 1;
  },
  getCount() {
    return count;
  }
};
const app = counter => {
  counter.increment();
};

test('app() with jest.spyOn(counter) .toHaveBeenCalledTimes(1)', () => {
  const incrementSpy = jest.spyOn(counter, 'increment');
  app(counter);
  expect(incrementSpy).toHaveBeenCalledTimes(1);
});
 Copy code 

Mock Object

Mocks are pre-programmed with expectations which form a specification of the calls they are expected to receive. They can throw an exception if they receive a call they don't expect and are checked during verification to ensure they got all the calls they were expecting.

Use specific test objects (Mock Object) Replace the test dependent object , In the test, just verify whether it is called correctly . Here's the picture ,Mock object stay coding It is preset to receive the call , And it will check whether all the expected calls have been received during verification , If you receive an unexpected call , An exception will be thrown .

image.png

for example Jest One of them Mock Case study ,Mock One. counter object, And the mock Of counter object As an input parameter to isolate counter Impact on testing , Verify... In the test counter Of increment Whether it is called correctly , And don't care counter Of increment The concrete implementation of the method .

let count = 0;
const counter = {
  increment() {
    count += 1;
  },
  getCount() {
    return count;
  }
};
const app = counter => {
  counter.increment();
};

test('app() with mock counter .toHaveBeenCalledTimes(1)', () => {
  const mockCounter = {
    increment: jest.fn()
  };
  app(mockCounter);
  expect(mockCounter.increment).toHaveBeenCalledTimes(1);
});
 Copy code 

Fake Object

Fake objects actually have working implementations, but usually take some shortcut which makes them not suitable for production (an InMemoryTestDatabase is a good example).

Fake Object It's simpler 、 A lighter implementation replaces the components that are dependent on in the test . Typical in backend testing Fake Object Is a memory database , Use it to simulate real database operations , But this Fake Object Not suitable for use in a production environment , Generally only used for testing . in summary ,Fake Object Sounds and Test Stub Their positioning is very similar , but Fake Object Is a lighter implementation that relies on components , It only provides interfaces like dependent components, which is convenient for the system under test ( Components ) To call , And as mentioned earlier Stub It is a real dependence , Different values will be returned according to different test scenario settings for testing . ​

In front-end testing , Often rely on other components or systems , For example, a reference to a third party UI Component library . However, in the test , We don't care about third parties UI The specific implementation of the library . These can be used Fake Object 了 .

image.png

For example, using react Frameworks often combine redux Use it together , When it comes to one connect 了 redux When testing components of , You can put redux Use redux-mock-store This lightweight test library replaces .

import configureStore from 'redux-mock-store';
 
const mockStore = configureStore([]);
 
describe('My Connected React-Redux Component', () => {
  let store;
 
  beforeEach(() => {
    store = mockStore({
      myState: 'sample text',
    });
  });
 
  it('should render with given state from Redux store', () => {
 
  });
 
  it('should dispatch an action on button click', () => {
 
  });
});
 Copy code 

Dummy Object

Dummy Object It refers to the false parameters passed in for testing , Pass in the method as a parameter Dummy Object Just to successfully call the method under test , No other role .

Such as the front util A parameter needs to be transmitted through the , This parameter is not really used , Just to pass through , So these are passed on but not really used Object Namely Dummy Object.

These test doubles should be used according to the specific situation in practice , Different schools have different use schemes . In the test , When there are component dependencies , Whether the test is state verification or behavior verification . In case of status verification , I don't care about the number of calls , In general use Stub and Fake Object; If it is behavior verification, it will generally use Spy or Mock To verify the result of the call .

Test avatar related documentation Links :

summary

The types of front-end tests are roughly the ones mentioned above , The rate of unit testing is the highest , Because the cost of each unit test is smaller than other tests , And it can also test the performance of various scenarios more comprehensively case. The front end is medium pure JS I won't say much about function testing , Almost all testing tools support well , If it involves DOM Related component testing ,Test Library It's a good choice , The specific tools used for testing can be determined according to the project situation ( Combined with project technology selection and testing strategy ). Of course, the test framework is just a tool for writing tests , To write a test well, you still need to clarify the test responsibilities of each test case , Cover as many test scenarios as possible , Avoid error , It also avoids function failure caused by subsequent refactoring , Improve software quality .

Last

WeChat search official account Eval Studio, Watch more .

copyright notice
author[EvalStudio],Please bring the original link to reprint, thank you.
https://en.qdmana.com/2021/08/20210827085844987z.html

Random recommended