This is my notes from book reading the book Hands on software engineering with Golang and all the credits go to the Achilleas Anagnostopoulos
Go Vendoring
Also called lazy package resolution is using immutable snapshot of dependencies whenever a Go application is compiled. i.e having a copy of dependencies cached, which will be used for compilation.
Why vendoring? Behavior of application might change when dependencies are updated to newer versions automatically.
-
Approaches
- Fork dependency with particular version and point import to it
- create a manifest file which will list all the dependency versions
- cache dependencies to vendor folder(checked into vcs)
-
Advantages:
- can create LTS releases
- serves as a safety net in case an upstream dependency suddenly disappears from source (ex: left-pad)
-
Disadvantages:
- People often do not periodically update them. Some important fixes and issues resolved in dependencies are not reflected in the application. (Security issues?)
How to vendor?
go mod vendor
Go testing
As application complexity grows overtime, it becomes very important to have comprehensive tests.
Unit test: a unit is the smallest possible bit of code that we can test. functions, structs, methods and even Go packages can be considered as single unit.
Stub: A stub is the simplest test pattern that we can use in our tests. Stubs typically implement a particular interface and don’t contain any real logic; they just provide fixed answers to calls that are performed through the course of a test. ( Ex: Chapter04/captcha)
Spy: A spy is nothing more than a stub that keeps a detailed log of all the methods that are invoked on it. For each method invocation, the spy records the arguments that were provided by the caller and makes them available for inspection by the test code. ( Ex: Chapter04/chat)
Mocks: Contrary to the fixed behavior exhibited by stubs, mocks allow us to specify, not only the list of calls that the mock is expected to receive but also their order and expected argument values.
In addition, mocks allow us to specify different return values for each method invocation, depending on the argument tuple provided by the method caller. Writing mocks from for all objects is very tedious, so often use mock generation tool.
- GoMock Creates mocks based on interfaces definitions, leverages reflection. ( Ex: Chapter04/dependency)
Fake objects: fake objects also adhere to a specific interface, which allows us to inject them into the subject under test. The main difference is that fake objects do, in fact, contain a fully working implementation whose behavior matches the objects that they are meant to substitute. ( Ex: Chapter04/computer)
Black box testing: Test the exposed public interface, functions and variables of the package (exported when anything starts with capital letters). Test package name can be changed to package_test, this will help in testing the package as an external entity.
White box testing: Testing implementation details or code which is not public, (all the things which start with small letters).
We can name our file as package_internal_test
and test package name can be same as package under test, so that the internal entities are accessible.
Table Driven testing + Sub testing: Contains two parts: (test case definition and test-runner code).
All the duplicate setup can be stored in test case definition. And logic can be modified accordingly in test-runner. We can use anonymous struct to create test case definition of different scenarios.
|
|
Checkout, testing library: Testify It provides, Assertion, mocking, suite interfaces and functions.
Require vs Assert? The require package provides same global functions as the assert package, but instead of returning a boolean result they terminate current test when test fails.
Integration tests: ensures that different units (or services, in a microservice architecture) interoperate correctly.
Also, as part of integration tests, you might need to connect with a real database. Just to make the configuration and our life easier, we can use TestContainers
Before trying to write lot of integration tests, make sure you have good number of concrete unit tests in-place.
Functional tests: Used to verify end-to-end correctness by simulating a user’s journey through the system. They ensure that the complete system is working as expected and involve multiple complex actions involving multiple system components.
Smoke tests: Or build acceptance tests constitute a special family of tests that are traditionally used as early sanity checks by QA teams before going for functional testing.
If smoke tests fail, no further testing is performed. They must be quick and only check for basic acceptance criteria, in-depth testing can be done by functional tests at later stage.
Chaos testing: key point behind chaos testing is to evaluate your system’s behavior when various components exhibit different types of failure. Idea is to have a preventive fashion mechanism rather than trying to react on failure.
Using ambassador/sidecar pattern, you can inject failures into the system like latencies/ lags/ traffic etc.
Testing Techniques
Use environment variables to run tests as required. Example, DB url can change according to testing/staging/prod environments.
|
|
Run tests faster:
-short
flag togo test
, Use Short helper function to get the flag value and based on this you can skip some tests.-failfast
, used to abort all tests if there is a failing test-timeout
flag, to define timeout. Default is 10 mins. If set to 0, no time limit is defined.
Exclude test and components: Using build tags helps to decide whether a particular Go file in a package should be passed to the Go compiler.
Build tags appear at top of package, a new line is required after build tag line.
Space between tags to represent or
, comma to represent and
and ! to represent not
.
We can use build tags in both test(go test) and build(go build) Ex: +build unit_tests all_tests +build integration_tests all_tests +build e2e_tests all_tests Chapter04/buildtags
More tricks? Mocking calls to external binaries (Chapter04/pinger) and testing timeouts (Chapter04/dialer)