PouchContainer Engineering Quality Practice

1. Consistent Coding Style Specification

PouchContainer is a project constructed using Golang. It uses shell scripts to complete automatic operations such as compiling and packaging. In addition to Golang and shell scripts, PouchContainer includes many Markdown documents to help users understand PouchContainer. The standard typography and correct spelling of the documents are the focus of projects. The following describes the tools and use cases of PouchContainer in terms of coding style specification.

1.1 Golinter — Keeps a Consistent Coding Format

Golang has simple syntax, and the complete CodeReview guide of the community from the start helps achieve a consistent coding style across many Golang projects and minimize disputes. Based on the conventions in the developer community, PouchContainer defines specific rules for developers to follow, so as to ensure code readability. For more information, read the code style rules.

  1. golint — Google’s (mostly stylistic) linter.
  2. gofmt -s — Checks if the code is properly formatted and could not be further simplified.
  3. goimports — Checks missing or unreferenced package imports.
  4. go vet — Reports potential errors that otherwise compile.
  5. varcheck — Find unused global variables and constants.
  6. structcheck — Find unused struct fields
  7. errcheck — Check that error return values are used.
  8. misspell — Finds commonly misspelled English words.

1.2 Shellcheck — Reduces the Potential Problems of Shell Scripts

Despite powerful functions, shell scripts require syntax check to avoid potential and unpredictable errors. For example, unused variables may be defined. Though such variables do not affect the use of scripts, they may be a burden on project maintainers.

#!/usr/bin/env bashpouch_version=0.5.xdosomething() {
echo "do something"
}
dosomething
In test.sh line 3:
pouch_version=0.5.x
^-- SC2034: pouch_version appears unused. Verify it or export it.

1.3 Markdownlint — Keeps Consistent typography

As an open source project, PouchContainer attaches equal importance to documents and code, because documents are the optimal way users can understand PouchContainer. Documents are prepared using Markdown, and their typography and spelling are the project focus.

2. How to Compile a Golang Unit Test

A unit test ensures the correctness of a single module. In a test pyramid, a unit test with wider coverage of more functions is more likely to reduce the debugging costs of integration testing and end-to-end testing. In a complex system, a longer link of task processing results in a higher cost of problem locating, especially problems caused by minor modules. The following lists the conclusions on how to compile Golang unit test cases in PouchContainer.

2.1 Table-Driven Test — DRY Principle

Simply put, a unit test is intended to determine whether the output of a function meets expectations based on a given function input. When a tested function has various input scenarios, we can organize test cases in Table-Driven mode. See the following code. Table-Driven uses arrays to organize test cases, and verify the correctness of functions by means of cyclic execution.

// from https://golang.org/doc/code.html#Testing
package stringutil
import "testing"func TestReverse(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, world", "dlorw ,olleH"},
{"", ""},
}
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want)
}
}
}
{
name: "Normal",
input: "docker.io/library/nginx:alpine",
expected: taggedReference{
Named: namedReference{"docker.io/library/nginx"},
tag: "alpine",
},
err: nil,
}, {
name: "Punycode",
input: "xn--bcher-kva.tld/redis:3",
expected: taggedReference{
Named: namedReference{"xn--bcher-kva.tld/redis"},
tag: "3",
},
err: nil,
}

2.2 Mock — Simulates External Dependencies

Dependencies are frequently encountered during testing. For example, a PouchContainer client requires an HTTP server. However, such dependencies exceed the processing capability of units and fall in the integration test scope. How can we complete these unit tests?

http.Client -> http.RoundTripper [http.DefaultTransport]
// https://github.com/alibaba/pouch/blob/master/client/client_mock_test.go#L12-L22
type transportFunc func(*http.Request) (*http.Response, error)
func (transFunc transportFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return transFunc(req)
}
func newMockClient(handler func(*http.Request) (*http.Response, error)) *http.Client {
return &http.Client{
Transport: transportFunc(handler),
}
}
// https://github.com/alibaba/pouch/blob/master/client/image_remove_test.go
func TestImageRemove(t *testing.T) {
expectedURL := "/images/image_id"
httpClient := newMockClient(func(req *http.Request) (*http.Response, error) {
if !strings.HasPrefix(req.URL.Path, expectedURL) {
return nil, fmt.Errorf("expected URL '%s', got '%s'", expectedURL, req.URL)
}
if req.Method != "DELETE" {
return nil, fmt.Errorf("expected DELETE method, got %s", req.Method)
}
return &http.Response{
StatusCode: http.StatusNoContent,
Body: ioutil.NopCloser(bytes.NewReader([]byte(""))),
}, nil
})
client := &APIClient{
HTTPCli: httpClient,
}
err := client.ImageRemove(context.Background(), "image_id", false)
if err != nil {
t.Fatal(err)
}
}
// https://github.com/alibaba/pouch/blob/master/apis/server/image_bridge_test.go
type mockImgePull struct {
mgr.ImageMgr
handler func(ctx context.Context, imageRef string, authConfig *types.AuthConfig, out io.Writer) error
}
func (m *mockImgePull) PullImage(ctx context.Context, imageRef string, authConfig *types.AuthConfig, out io.Writer) error {
return m.handler(ctx, imageRef, authConfig, out)
}
func Test_pullImage_without_tag(t *testing.T) {
var s Server
s.ImageMgr = &mockImgePull{
ImageMgr: &mgr.ImageManager{},
handler: func(ctx context.Context, imageRef string, authConfig *types.AuthConfig, out io.Writer) error {
assert.Equal(t, "reg.abc.com/base/os:7.2", imageRef)
return nil
},
}
req := &http.Request{
Form: map[string][]string{"fromImage": {"reg.abc.com/base/os:7.2"}},
Header: map[string][]string{},
}
s.pullImage(context.Background(), nil, req)
}
type Do interface {
Add(x int, y int) int
Sub(x int, y int) int
}
type mockDo struct {
addFunc func(x int, y int) int
subFunc func(x int, y int) int
}
// Add implements Do.Add function.
type (m *mockDo) Add(x int, y int) int {
return m.addFunc(x, y)
}
// Sub implements Do.Sub function.
type (m *mockDo) Sub(x int, y int) int {
return m.subFunc(x, y)
}

2.3 Other tricks

In some cases, third-party services are the subject of dependency. For example, the PouchContainer client represents a typical case. Such testing can be completed using Duck Type. We can also register http.Handler and start mockHTTPServer to process requests. The preceding test method is cumbersome, and recommended for use only when testing cannot be completed by Duck Type, or it can be used in integration testing.

Conclusion

Code style checking, unit testing, and integration testing must be performed by means of continuous integration during code review to help reviewers make accurate decisions. Currently, PouchContainer uses TravisCI or CircleCI and pouchrobot for code style checking and testing.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Alibaba Cloud

Alibaba Cloud

Follow me to keep abreast with the latest technology news, industry insights, and developer trends. Alibaba Cloud website:https://www.alibabacloud.com