Learning unit testing, bid farewell to pray programming

Prayer programming

If the code contains the following code

Or such activities on the line

Then this program is the way to pray programming.

A flow chart shows the basic is like this.

Recommended worship the god of Shen Congwen border town

Prayer programming what harm it?

  1. Tired, each finished the code still need to pray
  2. Uncontrolled, the result mainly of luck to run the code, marching busy time may not bless

There are many ways to solve this problem, unit testing is one of them.

unit test

What is unit testing

Unit testing by developers to write for the software program to perform basic unit of testing. Means (unit) is the smallest part of a class test application. (Such as a function, a class

The test is divided into small test google, medium and large test test. The basic role of small unit tests and similar tests, but also typically used in the mock or stub simulate external service.

Ideally, unit tests should be independent of each other, it can be automated to run.

Objective: typically unit test to verify whether the expected code logic. Unit testing is complete and reliable code 安全网, you can verify the business logic is correct when code changes or reconstruction, early detection of coding errors, reduce debugging time. Well-designed unit tests may better reflect the function and effect than the document code in some cases.

Unit testing so many advantages why some people do not like to write unit tests do?

  1. Unit testing takes too long for the novice writing unit tests unskilled, the write unit tests than writing code may also time-consuming
  2. Unit test running too long (which is usually poorly designed test cell or test code may be caused by poor
  3. Ancestral code to see can not read, how to write unit tests (this does give the advantage tricky .. consider adding new code unit testing
  4. Not write unit tests

This article focuses on the fourth question, how to write unit tests.

Testing the structural unit

First, look at the structure of the test unit, a test unit including complete Arrange-Act-Assert (3A) of three parts.

  • Arrange-- prepare data
  • Act-- run code
  • Assert-- determine whether the results in line with expectations

For example, we give the following code (golang) plus unit testing:


func Add(x, y int) int {
	return x + y
}

复制代码

Unit test code is as follows:

import "testing"

func TestAdd(t *testing.T) {
    // arrange 准备数据
	x, y := 1, 2
    // act   运行
	got := Add(x, y)
    //assert  断言
	if got != 3 {
		t.Errorf("Add() = %v, want %v", got, 3)
	}
}
复制代码

How to write good unit tests

What kind of unit tests is a good unit test?

Look at an example:

package ut

import (
	"fmt"
	"strconv"
	"strings"
)

func isNumber(num string) (int, error) {
	num = strings.TrimSpace(num)
	n, err := strconv.Atoi(num)
	return n, err
}

func multiply(x string, y int) string {
    // 如果x 去除前后的空格后是数字,返回 数字的乘积
    //     比如 x="2" y=3 return "6"
    // 如果x 去除前后的空格后不是数字,则返回字符串的x的y倍 
    //     比如 x="a" y=2 return "aa"
	num, err := isNumber(x)
	if err == nil {
		return fmt.Sprintf("%d", num*y)
	}
	result := ""
	for i := 0; i < y; i++ {
		result = fmt.Sprintf("%s%s", result, x)
	}
	return result
}
复制代码

Test code might look something like this.


// 测试方法的名字不直观,并不能看出具体要测试什么
func Test_multiply(t *testing.T) {
	type args struct {
		x string
		y int
	}
    // 一个测试方法中有太多的测试用例
	tests := []struct {
		name string
		args args
		want string
	}{
		{
			"return nil",
			args{
				"",
				2,
			},
			"",
		},
		{
			"return 2",
			args{
				"1",
				2,
			},
			"2",
		},
		{// 测试数据有点奇葩,不直观
			"return aaa",
			args{
				"aaaaaaaaaa",
				6,
			},
			"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := multiply(tt.args.x, tt.args.y); got != tt.want {
               // 数据错误的时候有明确标明测试数据,期望结果和实际结果,这一点还是有用的
				t.Errorf("multiply() = %v, want %v", got, tt.want)
			}
		})
	}
}

复制代码

The unit test code What's wrong with it?

  1. Long codes (listed here only to the three use cases, in fact, does not completely cover all the results)
  2. If the test method is not easy to locate the position error (the test data are in a three methods, any error will point to the same location
  3. There is a relatively long test data, the test determines whether or not the data can be visually correctly
  4. Incomplete input values, such as numeric string contains a space, "1", "1", "1" is not tested.

We above in unit testing purposes described herein, a good test cell should satisfy the following conditions :

  1. Unit testing as simple as possible, a unit test only one thing
  2. For easy tracking error, if the test fails, the error message should be easy to help us locate my problem
  3. Naming Test function meets certain rules Test_{被测方法}_{输入}_{期望输出}
  4. Useful failure message
  5. Enter simple to use and can complete input code (inclusive value, special circumstances

For example, the top of the unit tests we change it to this:

// 测试特殊值 “空字符串”
func Test_multiply_empty_returnEmpty(t *testing.T) {
    // 用例简单,只包含输入、执行和判断 
	x, y, want := "", 1, ""
	got := multiply(x, y)
	if got != want {
       // 有效的失败消息
		t.Errorf("multiply() = %v, want %v", got, want)
	}
}

// 测试包含空格的数字 边界值
func Test_multiply_numberWithSpace_returnNumber(t *testing.T) {
	x, y, want := " 2", 3, "6"
	got := multiply(x, y)
	if got != want {
		t.Errorf("multiply() = %v, want %v", got, want)
	}
}
// 测试正常数据
func Test_multiply_number_returnNumber(t *testing.T) {
	x, y, want := "2", 3, "6"
	got := multiply(x, y)
	if got != want {
		t.Errorf("multiply() = %v, want %v", got, want)
	}
}
// 测试非数字字符 
func Test_multiply_String_returnString(t *testing.T) {
    // 输入简单的字符串就可以测试,没必要用太奇怪或者太长或者太大的数据数据
	x, y, want := "a", 3, "aaa"
	got := multiply(x, y)
	if got != want {
		t.Errorf("multiply() = %v, want %v", got, want)
	}
}
// 测试空格 边界值
func Test_multiply_space_returnSpace(t *testing.T) {
	x, y, want := " ", 3, "   "
	got := multiply(x, y)
	if got != want {
		t.Errorf("multiply() = %v, want %v", got, want)
	}
}
复制代码

Of course, this data is not complete, you can then add:

  • Spaces of non-numeric characters
  • The right-hand string containing spaces
  • Digital spaces on both sides of the string

Since good unit tests need to be able to complete the test code, then there any way to ensure complete coverage of unit tests can test code?

Writing unit tests based analysis is a method code path.

Unit test path

May be used when designing the test path way to analyze the flowchart, the upper take multiplyan example of analysis, this code path is as follows:

Of course, the test data of each path is not the only one, for example, x为前后包含空格的数字字符串the path contains three cases:

  • There are spaces left
  • The right spaces
  • Spaces on both sides

Test Data Unit

Rational design of test data is very important, in addition to the test in line with said above should be simple and intuitive but also important consideration than the boundary value.

Design of test data is usually possible to input data into a plurality of sub-sets, each sub-select from the centralized data as a representative test. For example, a piece of code is to calculate the effect of a tax, we should follow different tax levels to design test data, such as:

  • Part of the annual income 0-36000
  • Part of the annual income 36000-144000
  • Part of the annual income of 144000-300000
  • Part of the annual income of 300000-420000
  • ...

Then, based on this subset of doing some checking for boundary value, such as 36000,144000 and so on.

How to test private methods

Typically, if there are private methods in the public method is called, has been tested indirectly by testing public methods to the private methods.

Also some private methods written unreasonable, such as private method is not being used or function and less relevant private class method, this time on the proposed method to extract the individual into a new private function or class to test.

How to test external service

Of course, in the real world code and not be so simple, they usually contain a request, or for external calls to other classes. In the preparation of unit tests, we usually use for external dependencies Mock and Stub manner to simulate external dependencies.

Mock and Stub difference:

  • Mock object is to create a simulation of the test code, the test method of performing simulation. Tests to verify the results using mock objects is correct

  • Stub is created in the test package a simulation method, a method for replacing the code under test, the test is performed for the assertion class.

The following sample code is:

Mock

The actual code:

//auth.go
//假设我们有一个依赖http请求的鉴权接口
type AuthService interface{    
    Login(username string,password string) (token string,e error)   
    Logout(token string) error
}
复制代码

Mock Code:

//auth_test.go
type authService struct {}
func (auth *authService) Login (username string,password string) (string,error){
    return "token", nil
}
func (auth *authService) Logout(token string) error{    
    return nil
}

复制代码

AuthService achieved using interface AuthService test code, requests the external network can be simulated during such tests, wean.

Golang used here is the code, golang not support overloading, the problem is so used will have a lot of duplicate code. If python, java and other support overloading of object-oriented language, you can simply inherit the parent class, just reload an external request that contains the code can achieve the requirements Mock.

Stub

package ut

func notifyUser(username string){
	// 如果是管理员,发送登录提醒邮件
}

type AuthService struct{}

func (auth *AuthService) Login(username string, password string) (string, error) {
    notifyUser(username)
	return "token", nil
}
func (auth *AuthService) Logout(token string) error {
	return nil
}
复制代码

For this code you want to test is actually more difficult, because the call Login notifyUser, if you want to test this code:

  • One way is to use the form of Mock, authService defined interface and then implement the interface TestAuthService, notifyUser replaced in the TestAuthService Login. This practice changes is relatively large, but also more duplicate code (of course, if a python java and other languages ​​can only support overloading overloaded Login interfaces can be.
  • Another way is to reconstruct Login method, in which the notifyUser passed as a parameter, so we only need to redefine notifyUser in the test code, you can simulate the function Login send e-mail alert then passed as a parameter to.

The second way is the stub.

Through this example we can see, if you want the code easy to test code should be considered in the design testability.

Writing test code

Writing Testable Code mentioned in a very practical point of view: in the development, think more of how to make your code more convenient to test. Considering these, it is usually your code design is not too bad.

If there is a following code, it is usually not easy to test:

  1. New keywords appear in the constructor or member variables
  2. Methods using static member variables in the constructor or
  3. In addition to other operations in the field of Assignment constructor
  4. Using conditional statements in a constructor or cyclic
  5. Do not use builder or factory method in the constructor Twenty use to construct the object graph
  6. Or increasing initialization code

This article addresses: misko.hevery.com/attachments... recommended reading.

You can also reply to "test" the male No. acquire pdf

to sum up

To sum up is to write test code, using high-quality unit tests (naming clear, simple and functional, complete path, reliable data) to ensure code quality.

Reference article


Finally, thanks to support his girlfriend and inclusive than ❤️

History article is also available in the public keyword, enter the following number: 公号&小程序| 设计模式|并发&协程

Scan code concerns

Guess you like

Origin juejin.im/post/5d96e4b9e51d45780e4ce9e9