Gomock Framework Usage Guide

Source: Internet
Author: User
Tags key string create domain in domain
This is a creation in Article, where the information may have evolved or changed.

Preface

To write good test code, you must be proficient in the relevant testing framework. For Golang programmers, there are at least three test frameworks that need to be mastered:

    • Goconvey
    • Gostub
    • Gomock

Readers through the previous three articles to learn the framework Goconvey and gostub elegant combination of use, this article will then introduce the third framework of the use of Gomock, the purpose is to enable the reader to grasp the framework Goconvey + gostub + gomock combination of the correct posture, This improves the quality of the test code.

Gomock is a test framework developed and maintained by Golang, which implements a more complete interface-based mock function that integrates well with Golang's built-in testing packages and can be used in other test environments. The Gomock test framework includes two parts of the Gomock package and the Mockgen tool, in which the Gomock package completes the management of the pile object life cycle, and the Mockgen tool is used to generate interface corresponding mock class source files.

Installation

To run a command at the command line:

go get github.com/golang/mock/gomock

After running, you will find that there is a github.com/golang/mock subdirectory under the $GOPATH/SRC directory, and there are gomock packages and Mockgen tools in that subdirectory.

To continue running the command:

cd $GOPATH/src/github.com/golang/mock/mockgengo build

An executable program, Mockgen, is generated under the current directory.

Move the Mockgen program to the $gopath/bin directory:

mv mockgen $GOPATH/bin

At this point, run Mockgen at the command line, if the use of Mockgen and examples are listed, the Mockgen has been installed successfully, otherwise it will be displayed:

-bash: mockgen: command not found

This is typically caused by not configuring $gopath/bin in the environment variable path.

Document

After the Gomock framework installation is complete, you can use the Go Doc command to obtain the document:

go doc github.com/golang/mock/gomock

In addition, there is an online reference document, the package Gomock.

How to use

Define an interface

Let's first define an interface repository that intends to mock:

package dbtype Repository interface {    Create(key string, value []byte) error    Retrieve(key string) ([]byte, error)    Update(key string, value []byte) error    Delete(key string) error}

Repository is an element of tactical design in domain-driven design, used to store domain objects, typically persisting objects in a database, such as Aerospike,redis or ETCD. For the domain layer, it is the responsibility of the infrastructure layer to know that the object is maintained in repository, not where the care object is persisted. When the microservices are started, the repository interface is instantiated according to the deployment parameters, such as Aerospikerepository,redisrepository or etcdrepository.

If you have a domain object movie to persist, first pass through JSON. Marshal is serialized and then called by the Repository create method to store it. When looking for a domain object based on key (entity ID), the byte slice of the Realm object is obtained first through the repository retrieve method, and then through JSON. The unmarshal is deserialized to the domain object. When the data for a domain object changes, it is first passed through JSON. Marshal is serialized, and then the Update method of repository is called. When the domain object life cycle ends and dies, the Delete method of repository is called directly.

Generate a mock class file

This is the Mockgen tool. Mockgen has two modes of operation: source files and reflection.

The source file mode generates a mock class file with a file containing the interface definition, which takes effect through the-source identity and is also useful in this mode for-imports and-aux_files identities.
Example:

mockgen -source=foo.go [other options]

Reflection mode generates a mock class file with a reflection-understanding interface by constructing a program that takes effect through two non-flag parameters: The import path and a comma-delimited list of symbols (multiple interface).
Example:

mockgen database/sql/driver Conn,Driver

Note: The first parameter is based on the relative path of the Gopath, the second argument can be multiple interface, and the interface can only be separated by commas and cannot have spaces.

There is a source file that contains the interface that you intend to mock, and you can use the Mockgen command to generate a mock class source file. The options supported by Mockgen are as follows:

    • -source: A file containing a list of interfaces intended for mock
    • -destination: The file that holds the mock class code. If you do not set this option, the code will be printed to the standard output
    • -package: The package name used to specify the source file for the mock class. If you do not set this option, the package name is cascaded by Mock_ and the package name of the input file.
    • -aux_files: See the attached file list to parse interface similar to nested definitions in different files. Specifies that the list of elements is separated by commas, with the element form Foo=bar/baz.go, where Bar/baz.go is the source file, and Foo is the package name used by the source file specified by the-source option

In a simple scenario, you will only need to use the-source option. In complex situations, such as a file that defines multiple interface and you want to mock only part of the interface, or if there are nested interface, then you need to use reflection mode. Because the-destination option input is too long, the author generally does not use the identifier, but uses the redirect symbol, and the path to the output file of the mock class Code must be an absolute path.

Now we run the Mockgen command to generate the Repository mock class source file through reflection mode:

mockgen infra/db Repository > $GOPATH/src/test/mock/mock_repository.go

Attention:

  1. Output directory Test/mock must be built in advance, otherwise Mockgen will fail to run.
  2. If your project's third-party library is unified in the vendor directory, you need to copy a gomock code to $GOPATH/SRC, Gomock code is github.com/golang/mock/gomock, This is because the Mockgen command runs when you want to access Gomock in this path

You can see in the Test/mock directory that the Mock_repository.go file has been generated, and the code snippet for the file is as follows:

Automatically generated by Mockgen. Do not edit!//source:infra/db (interfaces:repository) package Mock_dbimport (gomock "Github.com/golang/mock/gomock") Mockrepository is a mock of Repository interfacetype mockrepository struct {Ctrl *gomock. Controller Recorder *mockrepositorymockrecorder}//Mockrepositorymockrecorder is the mocking recorder for Mockrepositoryty PE mockrepositorymockrecorder struct {mock *mockrepository}//newmockrepository creates a new mock Instancefunc NewMoc Krepository (Ctrl *gomock. Controller) *mockrepository {mock: = &mockrepository{ctrl:ctrl} Mock.recorder = &mockrepositorymockrecorde R{mock} return mock}//EXPECT Returns an object this allows the caller to indicate expected Usefunc (_m *mockrepository ) EXPECT () *mockrepositorymockrecorder {return _m.recorder}//create mocks base methodfunc (_m *mockrepository) Create (_param0 string, _param1 []byte) error {ret: = _m.ctrl.call (_m, "Create", _param0, _param1) ret0, _: = Ret[0]. (error) Return ret0}//Create indicates an expected call of Createfunc (_MR *mockrepositorymockrecorder) Create (arg0, a Rg1 interface{}) *gomock. Call {return _mr.mock.ctrl.recordcall (_mr.mock, "Create", arg0, Arg1)} ...

Use mock objects for piling tests

After the mock class source file is generated, you can write the test case.

Importing mock-related packages

Mock-related packages include the Testing,gmock and Mock_db,import package paths:

import (    "testing"    . "github.com/golang/mock/gomock"    "test/mock"    ...)

Mock controller

The mock controller is generated through the Newcontroller interface, which is the top-level control of the mock ecosystem, which defines the scope and life cycle of the mock objects, as well as their expectations. It is safe to call the controller at the same time for multiple threads.
When the use case finishes, the controller checks to see if all remaining expected calls satisfy the condition.

The code for the controller is as follows:

ctrl := gomock.NewController(t)defer ctrl.Finish()

The mock object needs to be injected into the controller when it is created, and if more than one mock object is injected into the same controller, as follows:

ctrl := gomock.NewController(t)defer ctrl.Finish()mockRepo := mock_db.NewMockRepository(ctrl)mockHttp := mock_api.NewHttpMethod(ctrl)

Behavior injection of Mock objects

For the behavior injection of mock objects, the controller is maintained by map, and a method corresponds to one of the maps. Because a method may be called multiple times in a use case, the value type of the map is an array slice. When a mock object is injected with behavior, the controller will add the behavior. When the method is called, the controller will remove the behavior.

Suppose there is a scenario where the domain object fails first, and then the Create domain object succeeds, and once again retrieve the domain object to succeed. Retrieve The behavior injection code for the mock object that corresponds to this scenario is as follows:

mockRepo.EXPECT().Retrieve(Any()).Return(nil, ErrAny)mockRepo.EXPECT().Create(Any(), Any()).Return(nil)mockRepo.EXPECT().Retrieve(Any()).Return(objBytes, nil)

Objbytes are the serialization results of domain objects, such as:

obj := Movie{...}objBytes, err := json.Marshal(obj)...

When you bulk create objects, you can use the Times keyword:

mockRepo.EXPECT().Create(Any(), Any()).Return(nil).Times(5)

When you bulk retrieve objects, you need to inject multiple mock behaviors:

mockRepo.EXPECT().Retrieve(Any()).Return(objBytes1, nil)mockRepo.EXPECT().Retrieve(Any()).Return(objBytes2, nil)mockRepo.EXPECT().Retrieve(Any()).Return(objBytes3, nil)mockRepo.EXPECT().Retrieve(Any()).Return(objBytes4, nil)mockRepo.EXPECT().Retrieve(Any()).Return(objBytes5, nil)

Sequence of behavior calls

By default, the sequence of behavior invocations can be inconsistent with the sequence in which the mock objects are injected, that is, not guaranteed. If you want to order, there are two ways:

    1. Use the After keyword to achieve a guaranteed order
    2. inorder keyword for guaranteed order

Sample code for the order of the orders implemented through the After keyword:

firstCall := mockObj.EXPECT().SomeMethod(1, "first")secondCall := mockObj.EXPECT().SomeMethod(2, "second").After(firstCall)mockObj.EXPECT().SomeMethod(3, "third").After(secondCall)

Example code for the order of the Inorder keyword implementation:

InOrder(    mockObj.EXPECT().SomeMethod(1, "first"),    mockObj.EXPECT().SomeMethod(2, "second"),    mockObj.EXPECT().SomeMethod(3, "third"),)

Obviously, the Inorder keyword implementation of the isotonic is simpler and more natural, so this approach is recommended. In fact, the keyword inorder is after the syntax sugar, do not believe you look:

// InOrder declares that the given calls should occur in order.func InOrder(calls ...*Call) {    for i := 1; i < len(calls); i++ {        calls[i].After(calls[i-1])    }}

When the behavior of the mock object is injected into the sequence, the test fails if the order of the behavior call is inconsistent with it. This means that for the example above, if the SomeMethod method is not invoked in the test case execution, SomeMethod (1, "first"), SomeMethod (2, "second"), SomeMethod (3 , "third") in the order in which the test failed.

Injection of Mock objects

After the mock object's behavior is injected into the controller, we then inject the mock object into the interface, allowing the mock object to take effect in the test.
Before using the Gostub framework, many people used soil methods, such as set. There is a flaw in this approach: when a test case executes, it does not roll back the interface to the real object, potentially affecting the execution of other test cases. Therefore, I strongly recommend that you use the GOSTUB framework to complete the mock object injection.

stubs := StubFunc(&redisrepo.GetInstance, mockDb)defer stubs.Reset()

Test Demo

There are some basic principles for writing test cases, so let's review them:

    1. Each test case is focused on one issue, and do not write chatty test cases
    2. The test case is a black box.
    3. Test cases are independent of each other, and each use case guarantees its own pre-and post-complete
    4. Test cases to non-invasive product code
    5. ...

According to the basic principle, we do not share the mock controller between multiple test cases of a test function, so we have the following demo:

Func Testobjdemo (t *testing.            T) {convey ("Test obj demo", T, func () {convey ("Create obj", func () {Ctrl: = Newcontroller (t) Defer CTRL. Finish () Mockrepo: = mock_db. Newmockrepository (ctrl) Mockrepo.expect (). Retrieve (Any ()). Return (Nil, Errany) Mockrepo.expect (). Create (Any (), any ()). Return (nil) mockrepo.expect (). Retrieve (Any ()). Return (objbytes, nil) stubs: = Stubfunc (&redisrepo. GetInstance, Mockrepo) defer stubs.        Reset () ...}) Convey ("Bulk Create Objs", Func () {Ctrl: = Newcontroller (t) defer ctrl. Finish () Mockrepo: = mock_db. Newmockrepository (ctrl) Mockrepo.expect (). Create (Any (), any ()). Return (nil). Times (5) Stubs: = Stubfunc (&redisrepo. GetInstance, Mockrepo) defer stubs.        Reset () ...}) Convey ("Bulk retrieve Objs", Func () {Ctrl: = Newcontroller (t) dEfer Ctrl. Finish () Mockrepo: = mock_db. Newmockrepository (ctrl) ObjBytes1: = ... objBytes2: = ... objBytes3: = ... obj Bytes4: = ... objBytes5: = ... mockrepo.expect (). Retrieve (Any ()). Return (objBytes1, nil) mockrepo.expect (). Retrieve (Any ()). Return (ObjBytes2, nil) mockrepo.expect (). Retrieve (Any ()). Return (ObjBytes3, nil) mockrepo.expect (). Retrieve (Any ()). Return (objBytes4, nil) mockrepo.expect (). Retrieve (Any ()). Return (objBytes5, nil) stubs: = Stubfunc (&redisrepo. GetInstance, Mockrepo) defer stubs.    Reset () ...}) })    ...}

Summary

In this paper, the use of the Gomock framework is described in detail, not only with the example given the standard usage, but also listed a lot of points, and finally through a simple test demo shows the Goconvey + gostub + gomock combination use the correct posture. Hope readers extrapolate, at the same time, the core content of the previous three articles into, write high-quality test code, and ultimately improve product quality.

At this point, we already know:

    1. Global variables can be piling by gostub frame
    2. Process can be piling by gostub frame
    3. function can be piling by gostub frame
    4. Interface can be piling by gomock frame

So the question came, method of piling through God's horse?
There may be a good solution, but due to the author Caishuxueqian, there is no mature solution, only to minimize the impact of method calls on unit testing. If the call hierarchy of the method is very deep, and the middle layer is a method, it may lead to a higher complexity of piling, it is necessary to introduce interface at the appropriate level, so that the unit test by the interface piling call chain from the middle truncation, thereby effectively reducing the complexity of piling.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.