4 tips to understand front-end unit testing

With the complexity of each project, the high reusability requirements of the code and the high cohesion and low coupling between the front-end code modules, the unit testing process in the front-end engineering becomes very necessary.

1. What is front-end unit testing

First of all, we have to clarify what the test is:

In order to detect whether a specific target meets the standard, special tools or methods are used for verification, and a specific result is finally obtained.

For the front-end development process, the specific target here refers to the code we write, and the tool is the test framework (library), test case, etc. we need to use. The result of the inspection department is to show whether the test is passed or to give a test report, so as to facilitate the troubleshooting and later correction of the problem.

Based on the "what" statement of testing, in order to facilitate the advanced understanding of colleagues who are just engaged in front-end development, then we will list the "what" of unit testing:

Tests that require access to the database are not unit tests

Tests that require network access are not unit tests

Tests that require access to the file system are not unit tests

— The art of modifying code

For the quoted explanation of unit test "what is not", so far. In view of space limitations, I think the front-end development colleagues will have a preliminary understanding of their own after seeing the content of the citation.

2. The meaning of unit testing and why you need unit testing

2.1 The significance of unit testing

For the current front-end engineering, a standard and complete project, testing is very necessary. Many times we just completed the project and ignored the part of the project test. The significance of the test lies in the following points:

  1. TDD (Test Driven Development) has proven to be an effective software writing principle, which can cover more functional interfaces.
  2. Quickly feedback your function output and verify your ideas.
  3. To ensure the safety of code refactoring, there is no fixed code, and test cases can give you a peace of mind for the changeable code structure.
  4. Easy-to-test code indicates a good design. Before doing a unit test, you must instantiate a thing. If this thing has many dependencies, the test structure will be very time-consuming and will affect your test efficiency. What should you do? To rely on separation, a class should try to ensure a single function, such as separation of view and function, so that your code is also easy to maintain and understand.
2.2 Why do we need unit testing
  1. The first is the fundamental reason for a front-end unit test: JavaScript is a dynamic language, lacks type checking, and cannot locate errors during compilation; JavaScript host compatibility issues. For example, the performance of DOM operations on different browsers.
  2. Correctness: The test can verify the correctness of the code, so you can be confident before going online.
  3. Automation: Of course, you can also test manually, and you can print out internal information through the console, but this is a one-time thing. The next test needs to be done from scratch, and efficiency cannot be guaranteed. By writing test cases, you can write once and run multiple times.
  4. Explanatory: Test cases are used to test the importance of interfaces and modules, so how to use these APIs will be involved in the test cases. If other developers want to use these APIs, then reading test cases is a good way, sometimes more clear than the documentation.
  5. Drive development and guide design: The prerequisite for the code to be tested is the testability of the code itself, so to ensure the testability of the code, you need to pay attention to the design of the API during development. TDD moves the test forward to play such a role.
  6. Guarantee refactoring: The Internet industry products are iteratively fast, and there must be a process of code refactoring after iteration. How can the quality of the refactored code be guaranteed? Backed by test cases, you can boldly refactor.

3. How to write unit test cases

3.1 Principle
  • When testing the code, only consider the test, not the internal implementation
  • The data simulates reality as much as possible, the closer to reality the better
  • Fully consider the boundary conditions of the data
  • Focus on key, complex, core code, and focus on testing
  • Use AOP (beforeEach, afterEach) to reduce the amount of test code and avoid useless functions
  • The combination of testing and functional development is conducive to design and code refactoring
3.2 Two commonly used unit testing methodology

In unit testing, there are two commonly used methodologies: TDD (Test Driven Development) & BDD (Behavior Driven Development)

3.3 I believe you will have a personal view of TDD and BDD after reading it. Here I will talk about my understanding of TDD and BDD:

TDD(Test-driven development)

The basic idea is to promote the entire development through testing.

  • The primary purpose of unit testing is not to be able to write test codes that pass all tests with large coverage, but to try various possibilities of function logic from the perspective of the user (caller), thereby assisting in enhancing code quality

  • Testing is a means, not an end. The main purpose of testing is not to prove that the code is correct, but to help find errors, including low-level errors

  • Test fast. Fast running, fast writing

  • Keep the test code concise

  • Will not ignore failed tests. Once the team begins to accept 1 test build failure, then they gradually adapt to 2, 3, 4 or more failures. In this case, the test set no longer works

It should be noted that :

  • You must not misunderstand the core purpose of TDD!

  • Testing is not for coverage and accuracy

  • But as an example, tell developers what code to write

  • Red light (the code is not perfect, the test is hanging) -> green light (write the code, the test passes) -> refactor (optimize the code and ensure that the test passes)

The process of TDD is :

  1. Demand analysis, thinking about realization. Consider how to "use" the product code, whether it is an instance method or a class method, whether to pass parameters from the constructor or method calls, method naming, return value, etc. At this time, it is actually doing design, and the design is embodied in code. The test is red at this time

  2. Implement the code to make the test "green light"

  3. Refactor, then repeat the test

  4. Eventually meet all requirements namely:

    • Every concept is clearly expressed

    • No self-repetition in the code

    • Nothing extra

    • Passed the test

BDD(Behavior-driven development):

Behavior-driven development (BDD) focuses on obtaining an understanding of the expected software behavior through discussions with stakeholders (in short, customers), and the focus is on communication

The BDD process is:

  1. Define specific and measurable goals from a business perspective

  2. Find a way to achieve the most important functions for the business

  3. Then describe each specific executable behavior like a story. The description method is based on some common vocabulary, which has accurate expression ability and consistent meaning. For example, expect, should,assert

  4. Find the right language and method to realize the behavior

  5. The tester checks whether the product operation result conforms to the expected behavior. Deliver products that meet user expectations to the greatest extent and avoid problems caused by inconsistent expressions

4. Mocha/Karma+Travis.CI front-end testing workflow

The above content talks about the methodology of unit testing from what is unit testing. So how to use common frameworks for unit testing? What is the tool environment for unit testing? What is a practical example of unit testing?

First, I should briefly introduce Mocha, Karma and Travis.CI

Mocha : Mocha is a feature-rich front-end testing framework. The so-called "test framework" is a tool for running tests. Through it, you can add tests for JavaScript applications to ensure the quality of the code. Mocha can be run based on the Node.js environment or in the browser environment.

Karma : A JavaScript test execution process management tool (Test Runner) based on Node.js. This tool can be used to test all major web browsers, can also be integrated into CI (Continuous integration) tools, and can also be used with other code editors. A powerful feature of this testing tool is that it can monitor file changes, then execute it by itself, and display the test results through console.log. A powerful feature of Karma is that it can monitor the transformation of a set of files and immediately start testing the saved files without leaving the text editor. Test results are usually displayed in the command line, not in a code editor. This also allows Karma to basically be used with any JS editor.

Travis.CI : Provides continuous integration services (Continuous Integration, CI for short). It binds the projects on Github, as long as there is new code, it will be automatically crawled. Then, provide a running environment, perform tests, complete the build, and deploy to the server.

Continuous integration means that as long as the code changes, builds and tests are automatically run, and the results are fed back. After ensuring that it meets expectations, the new code is "integrated" into the backbone.

The advantage of continuous integration is that you can see the results of each small change in the code, thereby accumulating small changes, instead of merging a large block of code at the end of the development cycle.

Assertion library

After the introduction of the basic tool framework, I believe those who know a little about testing will know that to do unit testing, you need to write test scripts, so test scripts need to use the assertion library. "Assertion", personal understanding is "use the code to determine the correctness of the test code, check and expose the error of this code." Then for front-end unit testing, there are the following commonly used assertion libraries:

Look at a piece of code example:

expect(add(1, 1)).to.be.equal(2);

This is an assertion code.

The so-called "assertion" is to judge whether the actual execution result of the source code is consistent with the expected result, and throw an error if it is inconsistent. The above assertion means that when add(1, 1) is called, the result should be equal to 2. All test cases (it blocks) should contain one or more assertions. It is the key to writing test cases. The assertion function is implemented by the assertion library. Mocha does not have an assertion library, so the assertion library must be introduced first.

Introduce an assertion library code example:

var expect = require('chai').expect;

There are many kinds of assertion libraries. Mocha does not limit which one to use. It allows you to use any assertion library you want. The assertion library introduced by the above code is chai, and it is specified to use its expect assertion style. The following common assertion libraries:

Here we mainly introduce the commonly used APIs in node assert

  • assert(value[, message])
  • assert.ok(value[, message])
  • assert.equal(actual, expect[, message])
  • assert.notEqual(actual, expected[, message])
  • assert.strictEqual(actual, expect[, message])
  • assert.notStrictEqual(actial, expected[, message])
  • assert.deepEqual(actual, expect[, message])
  • assert.notDeepEqual(actual, expected[, message])
  • assert.deepStrictEqual(actual, expect[, message])
  • assert.notDeepStrictEqual(actual, expected[, message])
  • assert.throws(block[, error][, message])
  • assert.doesNotThrow(block[, error][, message])

assert(value[, message])

Assert whether the value of value is true. The equality judgment here uses == instead of ===. message is the assertion description and is an optional parameter.

const assert = require('assert');
assert(true);

assert.ok(value[, message])

Use the same method assert(value[, message]).

assert.equal(actual, expect[, message])

Expect actual and expect values ​​to be equal. The actual and expect used for comparison are basic types (string, number, boolearn, null, undefined) data. The comparison uses == instead of ===.

it('assert.equal', () => {
  assert.equal(null, false, 'null compare with false');  // 报错
  assert.equal(null, true, 'null compare with true');  // 报错
  assert.equal(undefined, false, 'undefined compare with false'); // 报错
  assert.equal(undefined, true, 'undefined compare with true'); // 报错
  assert.equal('', false, '"" compare with false');  // 正常
})

notEqual(actual, expected[, message])

Use the same assert.equal(actual, expect[, message])just negated the expected results (ie, not equal to).

assert.strictEqual(actual, expect[, message])

Use the same assert.equal(actual, expect[, message])but more internally using == === instead.

assert.notStrictEqual(actial, expected[, message])

Use the same assert.strictEqual(actual, expect[, message])just negated the expected results (ie, not strictly equal).

it('assert.strictEqual', () => {
  assert.strictEqual('', false); // 报错
})

assert.deepEqual(actual, expect[, message])

The deepEqual method is used to compare two objects. The process of comparison is to compare whether the key and value values ​​of the two objects are the same. When comparing, use == instead of ===.

it('assert.deepEqual', () => {
  const a = { v: 'value' };
  const b = { v: 'value' };
  assert.deepEqual(a, b);
})

assert.notDeepEqual(actual, expected[, message])

Use the same assert.deepEqual(actual, expect[, message])just expected results negated (ie not strictly equal depth).

assert.deepStrictEqual(actual, expect[, message])

Use the same assert.deepEqual(actual, expect[, message])but more internally using == === instead.

assert.notDeepStrictEqual(actual, expected[, message])

Use the same assert.deepStrictEqual(actual, expect[, message])but the result is inverted (ie, not strictly equal depth).

assert.throws(block[, error][, message])

Error assertion and capture, assert that the specified code block will report an error or throw an error. If the code runs without errors, the assertion fails and the assertion is abnormal.

it('throws', () => {
  var fun = function() {
    xxx
  };
  assert.throws(fun, 'fun error');
})

assert.doesNotThrow(block[, error][, message])

Error assertion and capture, usage is similar to throws, but opposite to the expected result of throws. Assert that the specified code block will not report an error or throw an error when running. If the code runs incorrectly, the assertion fails and the assertion is abnormal.

it('throws', () => {
  var fun = function() {
    xxx
  };
  assert.doesNotThrow(fun, 'fun error');
})

After the introduction of the corresponding tools, I will talk about personal practical experience in operation for the usage of Mocha, Karma and Travis.CI.

Mocha

  • Install mocha
npm install mocha -g

Of course, it can also be installed globally or not, and only installed locally in the project

npm install mocha --save
  • Create a test file test.js
var assert = require('assert')

describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1, 2, 3].indexOf(-1))
    })
  })
})

This file is a simple test and Arrayone indexOf()method. Here I am using assertion libraries are provided by Node Assertmodules in the API. Here assertion -1 equal to the array [1, 2, 3]to perform indexOf(-1)value returned later, if the test is passed, does not complain, if there are errors it will report an error.

Here we use the global installed mochato run this file mocha test.js.
The following is the return result

Basic test case examples

const assert = require('assert');

describe('测试套件描述', function() {
  it('测试用例描述: 1 + 2 = 3', function() {
    // 测试代码
    const result = 1 + 2;
    // 测试断言
    assert.equal(result, 3);
  });
});

Mocha test cases mainly include the following parts:

  1. describe defined test suite (test suite)
  2. test case defined by it
  3. Test code
  4. Assertions

Note: There can be multiple test suites and test cases in each test file. Mocha can run not only in the node environment, but also in the browser environment; it npm i mocha -gis also feasible to run in the node by installing mocha globally and then running the test cases in the command line.

Here is a little detailed introduction to the writing of the test script

Mocha's role is to run test scripts, you must first learn to write test scripts. The so-called "test script" is a script used to test the source code. Below is the code of an addition module add.js.

// add.js
function add(x, y) {
  return x + y;
}

module.exports = add;

To test whether the addition module is correct, it is necessary to write a test script. Usually, the test script has the same name as the source script to be tested, but the suffix is ​​.test.js (for testing) or .spec.js (for specifications). For example, the test script name of add.js is add.test.js.

// add.test.js
var add = require('./add.js');
var expect = require('chai').expect;

describe('加法函数的测试', function() {
  it('1 加 1 应该等于 2', function() {
    expect(add(1, 1)).to.be.equal(2);
  });
});

The above code is a test script, which can be executed independently. The test script should include one or more describe blocks, and each describe block should include one or more it blocks.

The describe block is called a "test suite" and represents a set of related tests. It is a function, the first parameter is the name of the test suite ("addition function test"), and the second parameter is a function that is actually executed.

The it block is called a "test case", which represents a single test and is the smallest unit of test. It is also a function. The first parameter is the name of the test case ("1 plus 1 should be equal to 2"), and the second parameter is an actual function to be executed.

The advantage of expect assertion is that it is very close to natural language. Here are some examples.

// 相等或不相等
expect(4 + 5).to.be.equal(9);
expect(4 + 5).to.be.not.equal(10);
expect(foo).to.be.deep.equal({ bar: 'baz' });

// 布尔值为true
expect('everthing').to.be.ok;
expect(false).to.not.be.ok;

// typeof
expect('test').to.be.a('string');
expect({ foo: 'bar' }).to.be.an('object');
expect(foo).to.be.an.instanceof(Foo);

// include
expect([1, 2, 3]).to.include(2);
expect('foobar').to.contain('foo');
expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

// empty
expect([]).to.be.empty;
expect('').to.be.empty;
expect({}).to.be.empty;

// match
expect('foobar').to.match(/^foo/);

Basically, expect assertions are written in the same way. The head is the expect method, and the tail is the assertion method, such as equal, a/an, ok, match, etc. Use to or to.be to connect between the two. If the expect assertion is not true, an error will be thrown. In fact, as long as no errors are thrown, the test case will pass.

it('1 加 1 应该等于 2', function() {});

In the above test case, there is no code inside. Since no error was thrown, it will still pass.

Karma

Some commonly used modules based on karma testing

Module installation

# 基础测试库
npm install karma-cli -g
npm install karma mocha karma-mocha --save-dev

# 断言库
npm install should --save-dev
npm install karma-chai --save-dev

# 浏览器相关
npm install karma-firefox-launcher --save-dev
npm install karma-chrome-launcher --save-dev

If you exchange experience in software testing, interface testing, automated testing, and interviews. If you are interested, you can add software test communication: 1085991341, and there will be technical exchanges with colleagues.

Configuration

The configuration here is mainly concerned with karma.conf.jsthe related configuration. If you want to use karma and mocha, it is best to npm install karma-cli -ginstall them globally karma-cli.

Two fields to note:

  • singleRun: If the value is true, the browser will automatically exit and close the browser window after running the test. The value of singleRun can be dynamically assigned according to the operating environment, and NODE_ENVvariables can be added to the startup command .
  • browsers: Browser configuration (multiple browsers can be configured); if the browser cannot be started, the relevant browser configuration is required. If the browser fails to start when setting the auto-start browser, you may need to set it to --no-sandboxmode.
{
  "browsers": ["Chrome", "ChromeHeadless", "ChromeHeadlessNoSandbox"],
  "customLaunchers": {
    "ChromeHeadlessNoSandbox": {
      "base": "ChromeHeadless",
      "flags": ["--no-sandbox"]
    }
  }
}

or

{
  "browsers": ["Chrome_travis_ci"],
  "customLaunchers": {
    "Chrome_travis_ci": {
      "base": "Chrome",
      "flags": ["--no-sandbox"]
    }
  }
}

Github project access Travis.CI for integration and automated testing steps

  • Create and complete a project that can be tested on github. The completion here refers to the need to complete the basic project functions and test case code.
  • Configure travis-ci to recognize the configuration file that is read, so that when travis-ci is connected, it can know some of the configuration during the test.
  • github and travis-ci are a site, in other words, if the two things can be connected. Users are required to log in to travis-ci and authorize access to your github project and make related project settings.
  • After the connection is completed, you can run the written test code according to your needs, or you can set up regular tasks to run the test.

Project creation, improvement of project functions and test code.

  • Project requirements: implement a summation method
  • Testing: By mochasumming method to test done.

Here is the project structure, created after the completion of the project by npm i mocha -Dinstalling mochamodules. Then run locally npm testto see whether the test passed. If the test passes, it means that we can continue to the next step.

Create travis-ci test configuration file

Create travis-ci configuration file .travis.yml, file content.

language: node_js
node_js:
  - "node"
  - "8.9.4"

At this point, the process of project development and test code writing is basically completed, and the next step can be connected to travis-ci testing.

Access travis-ci

Log in to travis-ci's official website via GitHub

Find the project that needs to be tested just created on GitHub, and start the test

Check the test process and find the problem in time.

Check whether the test status has passed the test. If it fails, check the problem and repeatedly modify it; if it passes, you can add a test pass mark in the project document.


The above content is the entire content of this article. The above content hopes to be helpful to you. Friends who have been helped are welcome to like and comment.

Guess you like

Origin blog.csdn.net/Chaqian/article/details/106603727