go-testing

module
v0.0.9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 4, 2024 License: MIT

README

Testing framework

Build Coverage Coverage Quality Report FOSSA License Docs

Introduction

Goal of the testing framework is to provide simple and efficient tools to for writing effective unit, component, and integration tests in go.

To accomplish this, the testing framework provides a couple of extensions for to standard testing package of go that support a simple setup of gomock and gock in isolated, parallel, and parameterized tests using a common pattern to setup with strong validation of mock request and response that work under various failure scenarios and even in the presense of go-routines.

Example Usage

The core idea of the mock/gock packages is to provide a short pragmatic domain language for defining mock requests with responses that enforce validation, while the test package provides the building blocks for test isolation.

type UnitParams struct {
    mockSetup    mock.SetupFunc
    input*...    *model.*
    expect       test.Expect
    expect*...   *model.*
    expectError  error
}

var testUnitParams = map[string]UnitParams {
    "success" {
        mockSetup: mock.Chain(
            CallMockA(input..., output...),
            ...
            test.Panic("failure message"),
       ),
        ...
        expect: test.ExpectSuccess
    }
}

func TestUnit(t *testing.T) {
    test.Map(t, testParams).
        Run(func(t test.Test, param UnitParams){

        // Given
        mocks := mock.NewMock(t).
            SetArg("common-arg", local.input*)...
            Expect(param.mockSetup)

        unit := NewUnitService(
            mock.Get(mocks, NewServiceMock),
            ...
        )

        // When
        result, err := unit.call(param.input*...)

        mocks.Wait()

        // Then
        if param.expectError != nil {
            assert.Equal(t, param.expectError, err)
        } else {
            require.NoError(t, err)
        }
        assert.Equal(t, param.expect*, result)
    })
}

This opinionated test pattern supports a wide range of test in a standardized way. For variations have a closer look at the test package.

Why parameterized test?

Parameterized test are an efficient way to setup a high number of related test cases cover the system under test in a black box mode from feature perspective. With the right tools and concepts - as provided by this testing framework, parameterized test allow to cover all success and failure paths of a system under test as outlined above.

Why parallel tests?

Running tests in parallel make the feedback loop on failures faster, help to detect failures from concurrent access and race conditions using go test -race, that else only appear randomly in production, and foster a design with clear responsibilities. This side-effects compensate for the small additional effort needed to write parallel tests.

Why isolation of tests?

Test isolation is a precondition to have stable running test - especially run in parallel. Isolation must happen from input perspective, i.e. the outcome of a test must not be affected by any previous running test, but also from output perspective, i.e. it must not affect any later running test. This is often complicated since many tools, patterns, and practices break the test isolation (see requirements for parallel isolated tests.

Why strong validation?

Test are only meaningful, if they validate ensure pre-conditions and validate post-conditions sufficiently strict. Without validation test cannot ensure that the system under test behaves as expected - even with 100% code and branch coverage. As a consequence, a system may fail in unexpected ways in production.

Thus it is advised to validate mock input parameters for mocked requests and to carefully define the order of mock requests and responses. The mock framework makes this approach as simple as possible, but it is still the responsibility of the developer to setup the validation correctly.

Framework structure

The testing framework consists of the following sub-packages:

  • test provides a small framework to simply isolate the test execution and safely check whether a test fails or succeeds as expected in coordination with the mock package - even in if a system under test spans detached go-routines.

  • mock provides the means to setup a simple chain or a complex network of expected mock calls with minimal effort. This makes it easy to extend the usual narrow range of mocking to larger components using a unified pattern.

  • gock provides a drop-in extension for Gock consisting of a controller and a mock storage that allows to run tests isolated. This allows to parallelize simple test and parameterized tests.

  • perm provides a small framework to simplify permutation tests, i.e. a consistent test set where conditions can be checked in all known orders with different outcome. This is very handy in combination with test to validated the mock framework, but may be useful in other cases too.

Please see the documentation of the sub-packages for more details.

Requirements for parallel isolated tests

Running tests in parallel not only makes test faster, but also helps to detect race conditions that else randomly appear in production when running tests with go test -race.

Note: there are some general requirements for running test in parallel:

  1. Tests must not modify environment variables dynamically - utilize test specific configuration instead.
  2. Tests must not require reserved service ports and open listeners - setup services to acquire dynamic ports instead.
  3. Tests must not share files, folder and pipelines, e.g. stdin, stdout, or stderr - implement logic by using wrappers that can be redirected and mocked.
  4. Tests must not share database schemas or tables, that are updated during execution of parallel tests - implement test to setup test specific database schemas.
  5. Tests must not share process resources, that are update during execution of parallel tests. Many frameworks make use of common global resources that make them unsuitable for parallel tests.

Examples for such shared resources in common frameworks are:

  • Using of monkey patching to modify commonly used global functions, e.g. time.Now() - implement access to these global functions using lambdas and interfaces to allow for mocking.
  • Using of gock to mock HTTP responses on transport level - make use of the gock-controller provided by this framework.
  • Using the Gin HTTP web framework which uses a common json-parser setup instead of a service specific configuration. While this is not a huge deal, the repeated global setup creates race alerts. Instead use chi that supports a service specific configuration.

With a careful design the general pattern provided above can be used to support parallel test execution.

Terms of Usage

This software is open source as is under the MIT license. If you start using the software, please give it a star, so that I know to be more careful with changes. If this project has more than 25 Stars, I will introduce semantic versions for changes.

Building

This project is using go-make, which provides default targets for most common tasks, to initialize, build, test, and run the software of this project. Read the go-make manual for more information about targets and configuration options.

Not: go-make automatically installs pre-commit and commit-msg hooks overwriting and deleting pre-existing hooks (see also Customizing Git - Git Hooks). The pre-commit hook calls make commit as an alias for executing test-go, test-unit, lint-<level>, and lint-markdown to enforce successful testing and linting. The commit-msg hook calls make git-verify message for validating whether the commit message is following the conventional commit best practice.

Contributing

If you like to contribute, please create an issue and/or pull request with a proper description of your proposal or contribution. I will review it and provide feedback on it.

Directories

Path Synopsis
cmd
Package gock provides a small controller to isolate testing of services (gateways) by mocking the network communication using.
Package gock provides a small controller to isolate testing of services (gateways) by mocking the network communication using.
internal
maps
Package maps contains a collection of helpful generic functions for working with maps.
Package maps contains a collection of helpful generic functions for working with maps.
math
Package math contains a collection of helpful generic functions helping with mathematical problems.
Package math contains a collection of helpful generic functions helping with mathematical problems.
mock
Package mock provides the classes and tools of the advanced mock generator that nicely integrate with the testing framework.
Package mock provides the classes and tools of the advanced mock generator that nicely integrate with the testing framework.
mock/test
Package test contains an interface and types for testing the mock package loading and mock file generating from template.
Package test contains an interface and types for testing the mock package loading and mock file generating from template.
mock/testx
Package test_test contains an interface and types for testing the mock package loading and mock file generating from template.
Package test_test contains an interface and types for testing the mock package loading and mock file generating from template.
reflect
Package reflect contains a collection of helpful generic functions that support reflection.
Package reflect contains a collection of helpful generic functions that support reflection.
slices
Package slices contains a collection of helpful generic functions for working with slices.
Package slices contains a collection of helpful generic functions for working with slices.
sync
Package sync contains a small collection of helpful generic functions and types for synchronization.
Package sync contains a small collection of helpful generic functions and types for synchronization.
Package mock contains the basic collection of functions and types for controlling mocks and mock request/response setup.
Package mock contains the basic collection of functions and types for controlling mocks and mock request/response setup.
Package perm provides a small framework to simplify permutation tests, i.e.
Package perm provides a small framework to simplify permutation tests, i.e.
Package test contains the main collection of functions and types for setting up the basic isolated test environment.
Package test contains the main collection of functions and types for setting up the basic isolated test environment.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL