Testing guidelines for the Splunk Forwarder Operator.
- Standard Go testing: Unit and controller tests use the standard
testingpackage - Ginkgo v2 / Gomega: Used only in
test/e2e/for end-to-end tests - GoMock: Interface mocking
- envtest: Kubernetes API server for controller testing
# Run all tests
make go-test
# Run specific package
go test ./controllers/splunkforwarder/
# Run specific test by name
go test -run TestReconcile ./controllers/splunkforwarder/
# Verbose output
go test -v ./...
# Container-based (CI parity)
boilerplate/_lib/container-make go-testEach package with tests includes:
*_test.go: Test cases using the standardtestingpackage
Example:
package mypackage
import (
"testing"
)
func TestMyFeature(t *testing.T) {
result := MyFunction()
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}Note: Ginkgo/Gomega is only used in
test/e2e/(end-to-end tests). Unit and controller tests use the standardtestingpackage — see existing*_test.gofiles incontrollers/andpkg/for patterns.
# Standard Go test file alongside source
touch pkg/newpackage/newpackage_test.go
# Use `testing.T` in test functions; see existing *_test.go files for patternsUse GoMock for external dependencies:
//go:generate mockgen -destination=mocks/mock_client.go -package=mocks sigs.k8s.io/controller-runtime/pkg/client ClientRegenerate all mocks:
boilerplate/_lib/container-make generateWhy container-make?
- Ensures same mockgen version as CI
- Prevents version drift in generated code
- Test individual functions and methods
- Mock external dependencies (Kubernetes client, HTTP calls)
- Fast execution (<1s per package)
- Located alongside source code
- Test reconciliation logic
- Use envtest for simulated Kubernetes API
- Test custom resource lifecycle
- Located in
controllers/*/
- Full operator deployment
- Real cluster interaction
- Located in
test/e2e/ - Run in CI via Tekton
When AI agents modify code:
Minimal validation:
# After changing controllers/splunkforwarder/
go test ./controllers/splunkforwarder/Full validation before commit:
make go-testIf tests fail:
- Read test output carefully
- Fix the underlying issue (don't skip tests)
- Rerun to confirm fix
- Regenerate mocks if interface changed:
boilerplate/_lib/container-make generate
It("should reconcile resource", func() {
// Create custom resource
resource := &v1alpha1.CustomResource{...}
Expect(k8sClient.Create(ctx, resource)).To(Succeed())
// Trigger reconciliation
_, err := reconciler.Reconcile(ctx, req)
Expect(err).NotTo(HaveOccurred())
// Verify reconciliation result
Expect(k8sClient.Get(ctx, resourceKey, resource)).To(Succeed())
Expect(resource.Status.Conditions).ToNot(BeEmpty())
})It("should return error when resource not found", func() {
_, err := reconciler.Reconcile(ctx, reqForNonExistent)
Expect(err).To(HaveOccurred())
})// Equality
Expect(result).To(Equal(expected))
// Nil checks
Expect(err).NotTo(HaveOccurred())
Expect(obj).To(BeNil())
// Collections
Expect(slice).To(ContainElement("item"))
Expect(slice).To(HaveLen(3))
Expect(slice).To(BeEmpty())
// Booleans
Expect(condition).To(BeTrue())
Expect(condition).To(BeFalse())
// Eventually (async)
Eventually(func() bool {
return checkCondition()
}).Should(BeTrue())Generate coverage report:
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out -o coverage.htmlNote: Aim for meaningful coverage, not arbitrary percentages.
- Test critical paths and error handling
- Don't test generated code or trivial getters/setters
# Verbose output
go test -v ./...
# Print statements in tests
t.Logf("Debug: %v", value)
# Run single test by name
go test -v -run "TestMyFunction" ./pkg/kube/
# Skip specific test
go test -v -run "^(?!TestFlaky)" ./...Tests run in Tekton pipeline with:
- Fresh environment
- No cached dependencies
- Strict timeout limits
Local CI parity:
boilerplate/_lib/container-make go-testTarget timings:
- Unit tests: <5s per package
- Controller tests: <15s per controller
- Full suite: <2min
If tests are slow:
- Check for unnecessary sleeps
- Use
Eventuallywith shorter intervals - Mock external calls
- Avoid creating unnecessary Kubernetes resources
Mock not found:
# Regenerate mocks
boilerplate/_lib/container-make generateenvtest not installed:
make setup-envtestTest passes locally, fails in CI:
# Run in container environment
boilerplate/_lib/container-make go-test
# Check for:
# - Time-dependent tests
# - Environment-specific assumptions
# - File path dependenciesFlaky tests:
- Use
Eventuallyinstead ofExpectfor async operations - Avoid hardcoded delays
- Ensure test isolation (clean up resources)
Tests do not run automatically in prek hooks (too slow for interactive use).
Run manually before pushing: make go-test