takeaway

In Head-to-toe Single test

— On Effective Unit Testing (Part I) mainly introduces: pyramid model, why do single test, single test phase and indicators, in the next part we mainly introduce

about

Take a look at mocks, strategies for not abusing mocks, and use case writing

Seven. We must talk about mocks

test doubles

In xUnit Test Patterns, the author first proposed the concept of Test Doubles. A mock is just one of them, and one of the easiest to confuse with stubbing. In the introduction to Gomonkey in the previous section, you noticed that I didn’t use mocks, it was all stubs. Yes, GomonKey is not a mock tool, but an advanced piling tool that fits most of our usage scenarios.

There are five types of test surrogates:

,

Dummy Object

Objects that are passed to the caller but are never actually used, usually just to fill argument lists

,

Test Stub

Stubs are often used to provide encapsulated responses in tests, such as when they are programmed not to respond to all calls. Stubs also keep track of downcalls, such as an email Gateway, which can be used to keep track of all messages sent or the number of messages sent. In short, Stubs are generally a encapsulation of a real object

,

Test Spy

The Test Spy, like a Spy, is embedded in the SUT and is responsible for transferring its indirect outputs to the outside world. It is characterized by returning indirect internal output to the Test case for verification. The Test Spy is only responsible for obtaining internal intelligence and sending it out, but not verifying the accuracy of the intelligence

,

Mock Object

Encapsulate the appropriate object for the specified calling method and parameters that need to be responded to

,

Fake Object

Fake objects often work with class implementations, but only to keep other programs running, such as in-memory databases, a good example.

Stubs and mock

Pile driving and

Mock is probably the easiest thing to confuse, and it is customary to use mock to describe the ability of a mock to return.

As far as I understand it,

Stubs can be understood as a subset of mocks, which are more powerful:

The mock,

You can verify the implementation, whether a function is executed, and how many times it is executed

The mock,

It can be conditional, such as passing in a specific parameter

mock

Effect is

The mock,

Returns can be specified

,

when

mock

Specifies that any argument returns fixed results when it is equal to

stub

Just,

Go’s mock tool gomock works only on the interface and is not suitable for news or Penguin projects, while gomonkey’s stub covers most usage scenarios.

8. Don’t mock

I’ll give this section a separate chapter to show its importance. Need to understand Xiao Peng’s

Mock seven Deadly SINS, on Gitchat.

The two factions

About from

In 2004-2005, there were two main schools of thought: classic test-driven development and mockist.

Say first

Mockist. He argues that the function will be measured

六四风波

all

六四风波

Call the outside function, all

The mock. That is, focus only on the lines of the function under test, mock them out whenever you call other functions, and test them with fake data.

And then there’s the classic test-driven development school, which says don’t abuse it

Mock, can not mock not mock, test unit is not necessarily a specific function, may be multiple functions, string together.

六四风波

When necessary

六四风波

again

The mock.

The two schools have been contending for many years, and their theories have both advantages and disadvantages. Being is reasonable. Such as

Mockist, where too many mocks are used to override function interfaces, is also error-prone. Classic school, string too much, and is questioned integration testing.

For our practical applications,

六四风波

You don’t have to force conformity, you can combine

六四风波

, when needed

Mock. Mock as little as possible.

When is it appropriate to mock

It is appropriate to use an object if it has the following characteristics

The mock object:

,

This object provides nondeterministic results (such as the current time or the current temperature)

,

Some state of the object is difficult to create or reproduce (such as network errors or file read/write errors)

,

Slow execution on object methods (such as initializing the database before testing begins)

,

The object does not yet exist or its behavior may change (such as driving the creation of a new class in test-driven development)

,

The object must contain some data or methods specially prepared for testing (the latter is not suitable for statically typed languages, popular

Mock

The framework cannot add new methods to objects.

Stub

Yes.)

So don’t abuse it

Mock (stub) : when a method under test calls another method function, the first response should be to walk in and string it up, rather than mock it out at the root.

Use case design method

Read an article:

Think like a machine

This article describes the fundamental thinking of programming

— Consider inputs and outputs. We design case, want to get the most comprehensive design, is to consider the combination of all input and all output, of course, on the one hand, this is too time-consuming, many times is not executable; On the one hand, this is not the desired outcome, you have to consider the input-output ratio. At this time, it is necessary to combine theory with practice, theory to guide practice, practice to elaborate theory.

Say first theory

1. Starting from the previous article, considering input and output, we should first know which ones belong to input and output:

2. White box & Black box design

White box method:

,

Logical coverage (statements, branches, conditions, combination of conditions, etc.)

,

Path (full path, minimum linearly independent path)

,

Cycle: combine

5

Various scenarios (skip loop, loop once, loop maximum, loop

m

Second hit, loop

m

Missed a time)

The black box method:

Equivalence classes: True, false (legal, illegal)

Boundary method:

[1,10] ==> 0,1,2,9,10,11 (is a valid complement to equivalence classes)

3. Combine applications

Full input and output is difficult to implement, so we consider that the gods in the industry designed the white box and black box design method. Through careful thinking, we can judge that it is the embodiment of the methodology of full input and full output.

So, white boxes

& Black box use case design method, I personally practice each one, understand its advantages and disadvantages, from the perspective of design coverage, condition combination > minimum linearly independent path > Condition > branch > statement.

The diagram below is an early practice in my thinking about use-case design that, in retrospect, was overdesigned.

But in practice, we worry

“Overdesign”

“, and there is no answer yet

“What method is used to ensure the design is foolproof”.

,

Overdesign can also make

The case is fragile

,

We seek to maximize revenue within a limited time frame

1. Small functions & Important (calculation, object handling) : Try to be as comprehensive as possible

2. Heavy logic, large lines of code: branch, statement overwrite + loop + typical boundary handling (let’s look at GetUserGiftList for example)

3. Lead to “implementation-based” and “intent-based” design: Too many internal calls to Stub the function under test are closer to “implementation-based” (second mention of “intent-based”).

Intent-based versus implementation-based

This topic is very important.

Intent-based: Think about what the function ultimately wants to do, think of the function under test as a black box, think about its output and output, not how it was implemented, what temporary variables were generated, how many times the loop was looped, what judgments were made, etc.

Based on implementation: INPUT and output I also consider, how to achieve in the middle I also consider.

A good example is a mock. For example, if we write a case, we will use the mock to verify which external methods are called within the function, how many times they are called, and in what order the statements are executed. Applications can change faster than they need to, refactoring can happen all the time, and cases can fail in droves if they change, as mentioned in Mock’s Seven Deadly SINS.

We want to be intent-based, away from implementation-based.

Combined with actual combat experience, I summarize as follows:

1. “Do it or don’t do it”. Cases are code, they need to be maintained, they need to be done, so they need to be done, not done. Write a bunch of useless, you have to maintain, you might as well delete.

2. Take a function, ask yourself, what function is the function to achieve, what is the final output; Then, ask yourself where the risk of the function lies, and which parts of the logic are less confident and most prone to error (calculations, complex judgments, a hit on an abnormal branch, etc.). These are the points that our case will cover.

3. Inline function, direct get/set, few lines without logic, as long as you judge there is no risk, do not write case.

4. Determined the case to be written, and then designed specific use cases with branch condition combination, boundary and other core aspects to implement the writing.

Can be combined with the news several single test

Case review records for detailed understanding.

Let’s look at a specific one

Case:

1. When I got this function, AS a test student, I first asked the developer to understand the purpose of this function: to add the user’s gift in accordance with the format and time

2. Read the code, understood the code process and several abnormal branches, and made code review first

3. Design case coverage according to necessary exception branches

4. The normal business process is designed according to the functional intention described in the development, and the case is as follows:

Functions to be tested

Single test of normal paths

case

func TestNum_CorrectRet(t *testing.T) {

giftRecord := map[string]string{

“” 00″, “10”,

“1:01” : “100”,

“” 99″, “20”,

“2” : “200”,

“a”: “30”,

“2:10 01” : “20”,

“Then 99” : “200”,

}

expectRet := map[int]int{

1:110,

2:20.

}

var s *redis.xxx

patches := gomonkey.ApplyMethod(reflect.TypeOf(s), “Getxxx”, func(_ *redis.xxx, _ string)(map[string]string, error) {

return giftRecord, nil

})

defer patches.Reset()

p := &StarData{xxx }

userStarNum, err := p.GetNum(10000)

assert.Nil(t, err)

assert.JSONEq(t, Calorie.StructToString(expectRet), Calorie.StructToString(userStarNum))

}

Students will ask: But did you end up looking at the code? See how the correct logic of the code is handled before you design it

Case and construct data, right? And if you don’t look at the code, how do you know which exception branches to cover?

A:

1. As a test student, I am writing and developing students’ case. Indeed, I need to know which abnormal branches to deal with, but not limited to several in the code, but also the abnormal branches I understand, which should be reflected in the case. Our case is never meant to prove how the code is implemented! Through single testing, we often find bugs. But the future will be developed to write a single test, and the function he designs will know which exception branches to cover.

2. Well, I need to see what the normal flow of code is, but that doesn’t mean ripping it down to make a case. In fact, the input and output of case is designed by understanding the structure of input data, output format, data verification and calculation process after communication with developers.

11. Use case writing strategy

For how to write a single test in order, we focus on practice, basically there are only three cases:

,

Independent atom:

mockist

We overthrew it. Of course, the function at the bottom may have no external dependencies, so testing it alone is sufficient.

,

Top down (red line)

: Tests down from the entry function. In practice, I found it difficult to execute because I had to figure out from the start what data and format to return for each call and string them together

case

Already very difficult.

,

From the bottom up

(yellow line

)

We find that entry functions, often without logic, call another function and return with a response. So the entry function, maybe I don’t have to write it, right? Let’s go ahead and look at each function called, and also call up the past online and offline

bug

We found that the parts of the code where problems occurred tended to be at the bottom of the call chain, especially when it came to calculations, complex branching loops, and so on. Moreover, functions at the bottom tend to be more measurable.

So consider two things,

* *

We chose bottom-up design to select function writing

Case:

* *

1. The bottom function is usually very measurable

2. More core logic, especially involving calculation, splicing and branching.

Solution to the testability problem — refactoring

An important reason for not writing single tests is that the code is not testability. If you have a function that has 80 or 90 lines, 200 or 300 lines, it’s basically unmeasurable

“Untestable.” Because there is too much logic inside, what goes through from the first line to the last line, various function calls external dependencies, various if/for, various exception branch handling, the number of lines of code to write a case may be several times that of the original function.

Therefore, it is necessary to promote single test and improve testability through reconstruction. Moreover, through refactoring, the code structure is indirectly clearer, more readable and maintainable, and easier to find and locate problems.

Common problems: duplicate code, magic numbers, arrow code, etc

Recommended theory books are Refactoring: Improving the Design of Existing Code, 2nd edition,

Clean code”

I posted an article on refactoring.

use

Codecc (Tencent Code Inspection Center) cyclomatic complexity, function length to evaluate the quality of code structure, we learn and practice together with the development, and continue to produce results.

For arrow code, consider the following steps:

1. Check for exceptions and return exceptions

2. Separate the statement

3. Separate the core parts into functions

Use case maintenance, readability, maintainability, reliability

Use case design elements

,

Test internal logic separately from external requests

,

For service boundaries (

interface

) for strict validation of inputs and outputs

,

Use assertions instead of native error functions

,

Avoid random results

,

Try to avoid making claims about the outcome of time

,

Timely use

setup

and

teardown

,

Test cases are isolated from each other and do not affect each other

,

Atomicity, all tests have only two outcomes: success and failure

,

Avoid logic in tests that should not be included

if

,

switch

,

for

,

while

Etc.

,

Don’t protect,

The try… The catch…

,

Only one concern is tested per use case

,

To use less

sleep

It’s not healthy to delay the test

3 a,

Strategy:

arrange

.

action

.

assert

Use case readability

,

The title should make the intent clear, e.g

Test+

Name of the function under test

+condition+result

.

case

After a failure, you know which scenario failed by name, rather than having to read the code line by line.

Someone else might be maintaining this test code in the future, and we need to make it easy for them to read

,

The content of the test code should be clear,

3A

Principle:

arrange

.

action

.

assert

Divided into three parts. Data preparation section

arrange

If there are too many lines of code, consider pulling them out.

,

The intent of assertion is obvious, so consider turning magic numbers into variables and naming them colloquial

,

a

case

Don’t overdo it

assert

To fully

,

Consistent with the requirements of the business code, both should be readable

Use case maintainability

,

Repetition: text string repetition, structure repetition, semantic repetition

,

Refuse to hardcode

,

Intent-based design. Don’t cause a batch of business code to refactor once

case

failure

,

Note the various bad smells of code, see Refactoring, 2nd edition

Use case reliability

Unit tests, small and fast, are not designed to discover this time

Bug, but also in order to put it on the assembly line and try to find out whether there is a bug in each MR. Single test runs fail only because of bugs, not external dependencies, implementation-based involvement, etc. Long-term failure will lose the warning effect of unit testing. The story of “the Wolf is crying” is a painful lesson.

,

Non-test program defect, random failure

case

,

unfailable

case

,

There is no

assert

the

case

,

Not worthy of the name

case

Xiv. The driving process of news unit testing

We mentioned that unit testing practices are divided into

There are four stages, each with a goal.

The first stage will be written, written by all members, not required to write well

,

Promoted from top to bottom, from the director to the group leader, gave full support without hesitation, which made the team members in high spirits

,

Quickly determine the single test frame, skilled use

,

Combined with the development requirements, output each scenario

Methods of use of the single test framework, including

assert

,

mock

.

table-driven

Etc.

,

encapsulation

http2WebContext

, convenient generation

context

object

,

Many training, explain the single measurement theory and the use of framework

,

Each team (terminal, access layer) designated single test interface person, by him first taste crab. He is most familiar with framework usage and writes most in the early stages

case

people

,

After running in the integrated use of the single test framework, the launch meeting was held, and some students first tried to use it to ensure that they had two consecutive iterations

case

The output

,

Add the data related to the single test into the summary data of each iteration: the team leader and director pay great attention to the single test data information and encourage improvement accordingly

case

Number and lines of code

The second stage is written well, effectively and fully

,

measuring

Let’s explore

mock

Share the correct use method and correct thinking of use case design with the team and reach a consensus after discussion

,

Pair programming, pair per iteration

2-3

Development, write together

case

And enhance each other.

Here the pair is flexible: some development, just use half a day to teach him the use of the framework, practice with him, he can get started without worrying; Some development, will be assigned to test students demand, test students write

case

After the development of

review

Learn and try to write your first one

case

;

Some development may not be accepted at the beginning, because the demand is not suitable for single test. After observing for a period of time, he found that other people have written, which is not so difficult and beneficial to the team. He even took the initiative to find other testers to teach him to write

case

.

,

Test what students submitted to the development

case

for

review

, follow up the development and modify again

先生

,

Two iterations in a row, invite

dot

The teacher

Joe helped the Lord

case review

The effect is very good

,

Single test data analysis of iteration, focus on demand coverage, personnel coverage,

case

The incremental

,

The team leader continues to encourage and support single testing

,

The requirements increase with each iteration

Unit testing

Field, set after evaluation by the group leader. Without a single test

先生

If you don’t pass, you pass the single test

review

Phase 3 testability improvement

,

Test and development study Refactoring edition 2 together with weekly sharing sessions

,

Some backbone students have priority in refactoring their own code

,

Test students strict requirements, first ensure that there is a single test, and then small reconstruction, each step has a single test guarantee

,

pipelined

codecc

Scanning, cyclomatic complexity and function length must meet the standards, and manual intervention is not allowed

Stage 4 TDD

,

We do not guarantee that the students will do it

TDD

, the threshold is still very high, and need to be skilled in the line after the application of business development

,

Gradually push development to write business code and test code simultaneously, rather than finishing business code later

case

,

Test your classmates

TDD

Xv. Assembly line

Single test to run on the assembly line, the client and the background are equipped with the assembly line, to ensure that every time

Both push and MR are run once to send a report.

for

For single test of GO, all modules of the news access layer are compiled by MakeFile. Because some environment variables need to be imported, I integrate GO test into MakeFile, and all test cases under this module can be run by making test.

GO = go

CGO_LDFLAGS = xxx

CGO_LDFLAGS += xxx

CGO_LDFLAGS += xxx

CGO_LDFLAGS += xxx

TARGET =aaa

export CGO_LDFLAGS

all:$(TARGET)

$(TARGET): main.go

(GO) Build − O (GO) build-o (GO)build− O @ $^

test:

CFLAGS=-g

export CFLAGS


( G O ) t e s t (GO) test
(M) -v -gcflags=all=-l -coverpkg=./… -coverprofile=test.out ./…

clean:

rm -f $(TARGET)

Note: The above method can only generate the coverage of the tested code files, not the untested coverage. You can solve this problem by creating an empty test file in the root directory to get full code coverage.

//main_test.go

package main

import (

“fmt”

“testing”

)

func TestNothing(t *testing.T) {

fmt.Println(“ok”)

}

Assembly line plus process

# cd ${WORKSPACE}

Access the current workspace directory

export GOPATH=${WORKSPACE}/xxx

pwd

echo “====================work space”

echo ${WORKSPACE}

cd ${GOPATH}/src

for file in `ls`:

do

if [ -d $file ]

then

if [[ “file” == “a” \]\] || \[\[ “file” == “b” ]] || [[ “file” == “c” \]\] || \[\[ “file” == “d” ]]

then

echo $file

echo
G O P A T H / s r c / {GOPATH}”/src/”
file

cp -r
G O P A T H / s r c / t o o l s / q a t e s t i n g / m a i n _ t e s t . g o {GOPATH}/src/tools/qatesting/main\_test.go
{GOPATH}/src/$file”/.”

cd
G O P A T H / s r c / {GOPATH}/src/
file

make test

cd ..

fi

fi

done

Appendix. Information

,

Test Driven Development

,

The Art of Unit Testing

,

Effective Unit Testing

,

Refactoring to Improve the design of Existing Code

,

The Art of Fixing Code

,

Three Lessons of Test-Driven Development

,

xUnit Test Patterns

The mock,

Seven deadly SINS