How PouchContainer Enforces Code Style and Unit Testing in Go
This article explains how the PouchContainer project automates code‑style enforcement and comprehensive Golang unit testing using tools like golint, gometalinter, shellcheck, markdownlint, table‑driven tests, and mock implementations, integrating them into CI pipelines to ensure reliable releases.
Unified Coding Style
PouchContainer enforces a consistent code style for its Go code, shell scripts, and Markdown documentation. All style checks run automatically in the CI pipeline (CircleCI) for every Pull Request, and violations cause the PR to be rejected.
Golinter – Standardizing Go Code
The Go toolchain already provides golint, gofmt, goimports, and go vet. PouchContainer adds extra rules documented at https://github.com/alibaba/pouch/blob/master/docs/contributions/code_styles.md#additional-style-rules and runs the full set on each PR.
gometalinter – Aggregating Multiple Linters
golint – Google’s stylistic linter
gofmt -s – Simplifies formatting
goimports – Detects missing imports
go vet – Reports potential errors
varcheck – Finds unused globals
structcheck – Finds unused struct fields
errcheck – Ensures error returns are handled
misspell – Detects common misspellings
Projects can customize the gometalinter suite as needed.
Shellcheck – Detecting Shell Script Issues
#!/usr/bin/env bash
pouch_version=0.5.x
dosomething() {
echo "do something"
}
dosomething In test.sh line 3:
pouch_version=0.5.x
^-- SC2034: pouch_version appears unused. Verify it or export it.The CI pipeline scans all .sh files and runs shellcheck on each.
NOTE: If shellcheck is too strict, specific checks can be disabled via comments (see the shellcheck wiki at https://github.com/koalaman/shellcheck/wiki).
Markdownlint – Keeping Documentation Consistent
Documentation is treated as first‑class code. Every PR is linted with markdownlint and misspell. Violations block merging.
NOTE: Overly strict markdownlint rules can be disabled; see the official RULES.md.
Writing Golang Unit Tests
Table‑Driven Tests – DRY Approach
// 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, 世界", "界世 ,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)
}
}
}When a function’s behavior is too complex for a single table entry (e.g., reading until EOF), a dedicated test case is written instead of forcing a table‑driven format.
NOTE: Table‑driven tests are the community‑recommended pattern (see https://github.com/golang/go/wiki/TableDrivenTests).
Mocking External Dependencies
Mocking RoundTripper
// 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)}
}
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.Path)
}
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}
if err := client.ImageRemove(context.Background(), "image_id", false); err != nil {
t.Fatal(err)
}
}Mocking ImageManager
// 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)
}Mock Generation Tools
For large interfaces, manually writing mocks is burdensome. The community provides tools such as mockery to generate mock implementations automatically.
Other Techniques
If an external service cannot be abstracted via an interface, a temporary http.Handler or mock HTTP server can be started. This approach is heavier and is usually moved to integration tests.
NOTE: Monkey‑patching compiled binaries is possible but discouraged; design testable code instead.
Conclusion
By running code‑style linters, documentation checks, and unit‑test suites in continuous‑integration (TravisCI/CircleCI) and using the pouchrobot bot, PouchContainer ensures that every change is automatically validated for readability, correctness, and stability before it reaches production.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Alibaba Cloud Native
We publish cloud-native tech news, curate in-depth content, host regular events and live streams, and share Alibaba product and user case studies. Join us to explore and share the cloud-native insights you need.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
