How to write unit tests

How to write unit tests

This article focuses on the importance of unit testing (UT), and explain some of the common confusion, to help us write better quality of UT. As for how to use Mocha similar problems like how to use the library's assertions, it is recommended to see the official documents.

First, why the need to write UT

I found many of my friends are not aware of the importance of unit testing. Before talking about how to write UT, I would like to talk about the necessity test, which will help improve the driving force us to write UT.

If, Joe Smith is responsible for developing interface John Doe is responsible for developing specific business. Joe Smith John Doe would call interface development , but for various reasons, Joe Smith developed the interface may be some bug.

In the absence of the test unit, the bug is often found by the user interface, John Doe. This is tantamount to John Doe adds extra work , because the interface to ensure the quality of the work of the people who write the interface is Joe Smith should do thing. Suppose Mr Tan is a more careless people, including extra time cost will be greater (frequently encountered in actual development).

Joe Smith may be aggrieved, said: Who can guarantee that the code will never produce bug.

Yes, no one can absolutely guarantee the correctness of the code. But Joe Smith need to have some minimum guarantee for these interfaces provided by him. He needs to ensure that these interfaces do have those functions described in the documentation of the interface, and can perform normal.

Ever since, Joe Smith lovingly looked at John Doe's eyes and said: I promise.

Ha ha ha, obviously, to ensure the verbal always do not fly, we need to ensure that black and white, which is the subject of our article: unit testing.

Go back to the above scenario. If those interfaces Joe Smith as he wrote wrote some pretty good quality test cases.

John Doe might be excited to Zhangsan Gong main hold up.

Ha ha ha ha, for the following reasons:

 

One reason: John Doe do not worry, there are some simple bug interface. Because you know these simple bug, Joe Smith John Doe needs to feedback, and then wait for Joe Smith modify, Joe Smith likely will not immediately go to modify it, John Doe can only wait to do the other thing, until Joe Smith after saying repaired, John Doe order to continue the development of the original . God, this is such a big time cost ah.

 

 

Two reasons: John Doe can understand the use of the interface by the test case . In the actual development, interface documentation may not be real-time high, many small companies do not even say the interface documentation. In this case, the use of interfaces and data structures involved can only rely on Joe Smith and John Doe of the communication. However, after writing a UT, this part of the communication costs to a large extent it can be avoided. John Doe because by reading each test case can clearly know how to use the interface and how to get results. Of course, that Joe Smith UT write well enough.

 

OK, back to our scenario. Now if Joe Smith is the author of an open source library, user John Doe is the open source library, the two did not know each other. Imagine, if John Doe encounter any bug, communication costs he and Joe Smith will undoubtedly be even greater. This is why the open source community has always stressed test program coverage reasons .

Summary of what to write unit tests have several advantages as follows:

The code is guaranteed in black and white, either careless or avoid some subtle bug.

UT can serve as a good role interface documentation, reducing the number of unnecessary communication costs.

There are also some benefits in the above example not reflected, not discussed here, you feel yourself:

Refactoring code convenience.

Clearer idea of ​​the design code.

Second, some confusion about the UT

By exchange with close colleagues and feel a little personal, I summed up some confusion about the common UT follows.

What is the nature 2.1 UT is?

UT nature: to ensure the interface in specific scenarios have in line with the expected output (or behavior).

Explanation: Due to the nature of the code is abstract actual behavior. So in theory when we test cases covering all the scenes all acts of projects and the corresponding, we can absolutely ensure the quality of the software. Although this is only an ideal state, but it is our intention to write UT. Always have a dream, right?

2.2 What is the test coverage?

Many large companies will require that the project has sufficient test coverage in CI (Continuous Integration) when there will be a valve test coverage, test coverage if less than this valve, do not let submit code. For example, our company is 90% of the valve.

About test coverage, we have a general understanding of the following are a few common dimensions can be calculated:

Line coverage: the proportion of executable statement executed.

Function coverage: the proportion of the called function.

Branch coverage: determining the ratio of branch statements to be executed.

It should be noted here that are measurable coverage. There is also a coverage can not be measured, which is mentioned above: test coverage ratio as well as all acts corresponding to all scenes. It is worth mentioning that the latter is what we really seek coverage when the coverage is high enough, that coverage will not necessarily observable low. As a good developer, we need to figure out which of the primary and secondary relationship.

UT 2.3 need to write it as a private method?

On this issue, the community has many different views, in  stackoverflow  on our argument is very hot. Here, I also talk about my own views.

Below, write or do not write, have their own saying:

Write : because we are test-driven, so I passed the written test to better design their own code. Moreover, many of the core logic is written in the private methods, those interfaces exposed just call these private methods, we do not want to write a lot of test cases for a particular interface which makes test code seems difficult to read.

Do not write : It takes up a lot of effort, I just need to ensure that the external interface have enough coverage can be.

In my opinion, this is clearly not a black and white issue. If I were a team of Leader, I will never mandatory for all UT team must write a private method , this is a very foolish thing. If I were an ordinary developer, said that even if the Leader of our team can not write UT private method, I may still write UT for some private methods .

My principle is: if the private method complexity is high and more important (it is important to understand the word of the beholder benevolent), then I'll write UT for it.

2.4 When should mock data or methods?

Said earlier, the nature of the behavior or output UT is expected interface specified scene. Specified scene here is that we create out by means of the mock.

In the process of writing an interface to UT, the more common situation is: Enter a trigger behavior A, B input b triggers the behavior. This situation is more simple, we only need to write two case, then were mock data and a data b , then write a statement to assert the corresponding behavior can be expected.

In addition to mock the input data, we often need to mock some way. I summed up the following two cases.

2.4.1 In order to facilitate the testing, so we need to mock a method.

Here it will be better understood by specific examples. Suppose we need to write an interface for the following UT.

// index.js
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const _ = require('underscore');

exports.refreshConfiguration = function(param) {
    return fs.readFileAsync(param.path)
        .then(JSON.parse)
        .then((val) => {
            return _.extend(val, param.config);
        })
        .catch((err) => {
            throw err;
        })
}

This is an interface configuration information updated. When the parameters are normal, code execution is successful, we can obtain the latest configuration information, the configuration information includes configuration information param in the existing configuration information and the specified file. Thus it was following this test case.

// mock/config.json
{
    "foo": "foo"
}

// test.js
const _ = require('underscore');
const assert = require('chai').assert;
const testModule = require('./index');

describe('refreshConfiguration', () => {
    const fakeParam = {
        path: './mock/config.json',
        config: { bar: 'bar' }
    };

    it('should be seccessed when everything is right', () => {
        testModule.refreshConfiguration(fakeParam)
            .then((ret) => {
                assert.deepEqual(ret, _.extend({ 
                    foo: 'foo' 
                }, fakeParam.config));
            });
    });
});

As above, we need to create a test file for this test case. However, you may find a new file is very troublesome. So, by mock us  fs.readFileAsync() to achieve the same purpose method.

//  rewire 是为 NodeJS 提供 mock 功能的第三方库 
const rewire = require('rewire');
const _ = require('underscore');
const assert = require('chai').assert;
const Promise = require('bluebird');
const fs = Promise.promisifyAll(require('fs'));
const testModule = rewire('./index');

describe('refreshConfiguration', () => {
    let fakeParam;
    let fakeFileConfig;

    beforeEach(() => {
        fakeParam = {
            config: { bar: 'bar' }
        };
        fakeFileConfig = {
            foo: 'foo'
        };
    });

    it('should be seccessed when everything is right', () => {
        testModule.__set__('fs', {
            readFileAsync: () {
                return  Promise.resolve(JSON.stringify(fakeFileConfig));
            }
        });

        testModule.refreshConfiguration(fakeParam)
            .then((ret) => {
                assert.deepEqual(ret, _.extend(fakeFileConfig, fakeParam.config));
            });
    });
});

We just wrote a case for this interface. There is also a possibility that if the acquisition after the failure of the configuration file, this interface should catch to the corresponding error. You may wish to try hands-on writing case in this case.

2.4.2 If the method in other module interfaces (in theory) must mock

Or that the above example, refreshConfiguration the interface has to use third-party modules  underscore and built-in node  fs modules. For both the corresponding interface module, without the need to mock, we can not mock, because our default they are safe and secure.

However, if there are calls to other service module interface, in theory, the method to be tested, we must mock those interfaces. This involves a very important concept in UT: isolation (Isolation). We need to isolate the current test method has nothing to do, so there are two benefits:

UT reduce the complexity of writing, just focus on the implementation of specific logic in a scene, and the rest mock.

Avoid the interdependence between the various test modules. Such an interface will not appear there was a bug, lead to a lot of test cases to run, but the.

The actual development, for various reasons we may not so strictly observe the principles of isolation, but must understand it as much as possible to avoid some "bad taste."

2.5 How to understand and practice TDD (test-driven development)?

After accustomed to writing UT, I was deeply impressed by this development model TDD is worth trying.

Briefly explain what the next TDD Yes. TDD (Test Dirven Development), also known as test-driven development. It features the first to write test cases, further development. When I first heard that TDD feel very weird. Writing the test first? Write development? This is how efficiency is low ah?

Ha ha ha, I was a bit slow in responding.

Similarly, we need to look dialectical TDD. It just offer a new idea: In some cases, we can write the test further specific development. Instead of saying before I write any line of business need to put the code corresponding test cases to write.

So, after the first test development development model what good is it?

When we develop a specific interface if the interface is not particularly simple words, we will not fall began writing code, but the first paper in the brain or design code. We will analyze this interface has those scenes, there are those possibilities, each scene corresponding behavior is what. This sentence is not a bit familiar (above have mentioned, if you are not familiar with, certainly not a good look). Yes, we can use these processes is reflected in the design of UT out, or, UT to better design our code of practice.

So, for (and even some private method) higher complexity of some of the interface, we can use TDD for development. So I suggest that the interfaces are developed by TDD, UT anyway, these interfaces is the escape you, is to write the problem as early as late write.

 

Guess you like

Origin www.cnblogs.com/111testing/p/11653871.html