diff --git a/.github/workflows/golangci-lint.yaml b/.github/workflows/golangci-lint.yaml index 766fca07..7d29bffa 100644 --- a/.github/workflows/golangci-lint.yaml +++ b/.github/workflows/golangci-lint.yaml @@ -15,18 +15,18 @@ jobs: name: lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - - uses: actions/setup-go@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 with: go-version: '1.21' cache: false - name: golangci-lint - uses: golangci/golangci-lint-action@v3 + uses: golangci/golangci-lint-action@v4.0.0 with: # Require: The version of golangci-lint to use. # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. - version: v1.54.2 + version: v1.56.2 # Optional: working directory, useful for monorepos # working-directory: somedir diff --git a/docs/contributor/testing-strategy.md b/docs/contributor/testing-strategy.md new file mode 100644 index 00000000..db5eb0ce --- /dev/null +++ b/docs/contributor/testing-strategy.md @@ -0,0 +1,104 @@ +# Testing Strategy for Infrastructure Manager + +## Introduction +This testing strategy describes how the Framefrog team tests the Kyma Infrastructure Manager product. It outlines the approach and methodologies used for testing all layers of this product to ensure the following: + +* Stability by applying load and performance tests that verify the product resilience under load peaks. +* Reliability by running chaos tests and operational awareness workshops to ensure product reliability in a productive context. +* Functional correctness by documenting and tracing all product features, their acceptance criteria and the used end-2-end tests to verify a correct implementation. +* Maintainability by regularly measuring code quality metrics like reusability, modularity, and package qualities. + + +## Testing Methodology + +We investigate the product by dividing it into layers: + +* Code: Includes the technical frameworks (e.g. Kubebuilder) and custom Golang code. +* Business Features: Combines the code into a feature our customers consume. +* Product Integration: Verifies how our product is integrated into the technical landscape, how it interacts with 3rd party systems, and how it is accessible by customers or remote systems. + +For each layer, a dedicated testing approach is used: + +* **Unit Testing for Code:** Writing and executing tests for individual functions, methods, and components to verify their behavior and correctness in isolation. +* **Integration Testing for Business Features:** Validating the integration and interaction between different components, modules, and services in the project. +* **End-to-End Testing:** Testing the application as a whole in a production-like environment, mimicking real-world scenarios to ensure the entire system is secure, functions correctly, and performs well. + + +## Testing Approach + +### Unit Testing + +This section focuses on ensuring the functionality and reliability of critical functions, methods, and components within our system. + +1. Identify critical functions, methods, and components that require testing. +2. Write unit tests using GoUnit tests, Ginkgo, and Gomega frameworks. +3. Ensure tests cover various scenarios, edge cases, and possible failure scenarios. We try to verify business relevant logic with at least 65% code coverage. +4. Test for both positive and negative inputs to validate the expected behavior. +5. Mock external dependencies and use stubs or fakes to isolate the unit under test. +6. Run unit tests periodically during development and before each PR is merged to prevent regressions. +7. Unit tests must be executed as fast as possible to minimize roundtrip times. Long-running tests should be excluded from frequently executed test runs and be triggered periodically, for example, 4 times a day. + +### Integration Testing + +This section focuses on the integration testing process, which involves testing the interaction and integration of various components and custom resources with the Kubernetes API. It provides you with a step-by-step guide to conduct integration testing, ensuring the correctness and functionality of the implemented business features. + +1. The PO and the team create a registry of implemented business features and define a suitable test scenario for each feature. +2. Create a separate test suite for integration testing. +3. Each test scenario is implemented in a separate test case. Use the Kubebuilder Test Framework and others to create test cases that interact with the Kubernetes cluster. +4. Test the interaction and integration of your custom resources, controllers, and other components with the Kubernetes API. +5. Ensure test cases cover various aspects such as resource creation, updating, deletion, and handling of edge cases. +6. Validate the correctness of event handling, reconciliation, and other control logic. +7. Integration tests must be executed fast to minimize roundtrip times and be applied for each PR. Long-running tests should be excluded from frequently executed test runs and be triggered periodically, for example, 4 times a day. + +### End-to-End Testing + +This section describes how to create and manage test clusters using mainstream Kubernetes management tools like Helm or Kustomize, and how to perform regular performance tests to ensure your application functions correctly and meets the KPIs in a production-like environment. + +1. Use a mainstream Kubernetes management tool (for example, [Helm](https://helm.sh/) or [Kustomize](https://kustomize.io/)) to create, deploy, and manage test clusters and environments that closely resemble the productive execution context. +2. For short-living Kubernetes clusters, use k3d or other lightweight Kubernetes cluster providers. +3. Run regularly, but at least once per release, a performance test that measures product KPIs to indicate KPI violations or performance differences between release candidates. + +|Testing Approach|Per Commit|Per PR|Per Release|In intervals| +|--|--|--|--|--| +|Unit Testing|X|X||Only long-running tests daily| +|Integration Testing||X||Only long-running tests daily| +|End-to-End Testing|||X|Daily| + +### Testing Tools and Frameworks +Use the following tools and frameworks to implement the above-mentioned testing levels: + +- **[golanci-lint](https://github.com/golangci/golangci-lint)**: Golang code linting for better code quality. +- **[go-critic](https://github.com/go-critic/go-critic)**: Another linter for measuring different code quality metrics. +- **[go test](https://pkg.go.dev/testing)**: For unit testing of Golang code. +- **Kubebuilder Test Framework and [EnvTest](https://book.kubebuilder.io/reference/envtest.html)**: For creating and executing integration tests that interact with the Kubernetes API. +- **[Ginkgo](https://github.com/onsi/ginkgo) and [Gomega](https://github.com/onsi/gomega)**: For writing and executing unit tests with a BDD-style syntax and assertions. +- **[k3d](https://k3d.io/)**: For creating short-living and lightweight Kubernetes clusters running within a Docker context. +- **[Helm](https://helm.sh/)**: For deploying and managing test clusters and environments for end-to-end testing. +- **[k6](https://k6.io/)**: For performance and stress testing. + +|Framework|Unit Testing|Integration Testing|End-to-End Testing| +|--|--|--|--| +|Golangci-lint| X | | | +|Go-critic| X | | | +|go test| X | | | +|Kubebuilder Test Framework| X | X | | +|EnvTest| X | X | | +|Ginkgo| X | X | | +|Gomega| X | X | | +|k3d| | | X | +|Helm| | | X | +|k6| | | X | + + +## Test Automation + +The following CI/CD jobs are a part of the development cycle and execute quality assurance-related steps: + +> **NOTE:** Jobs marked with `pull_request` are triggered with each pull request. Jobs marked with `push` are executed after the merge. + +- `golangci-lint / lint (pull_request/push)` - Is responsible for linting and static code analysis. For the configuration details, see [`golangci.yaml`](https://github.com/kyma-project/infrastructure-manager/blob/main/.golangci.yaml) and [`golangci-lint.yaml`](https://github.com/kyma-project/infrastructure-manager/blob/main/.github/workflows/golangci-lint.yaml). +- `PR Markdown Link Check / markdown-link-check (pull_request)` - Checks if there are no broken links in the pull request `.md` files. For the configuration details, see [`mlc.config.json`](https://github.com/kyma-project/infrastructure-manager/blob/main/mlc.config.json). +- `Run unit tests / validate (pull_request/push)` - Executes basic create/update/delete functional tests of the reconciliation logic. For the configuration details, see [`run-tests.yaml`](https://github.com/kyma-project/infrastructure-manager/blob/main/.github/workflows/run-tests.yaml). +- `Run vuln check / test (pull_request/push)` - Runs [govulncheck](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) on the code to detect known vulnerabilities. For the configuration details, see [`run-vuln-check.yaml`](https://github.com/kyma-project/infrastructure-manager/blob/main/.github/workflows/run-vuln-check.yaml). +- `pull-infrastructure-manager-build` - Triggered with each pull request. It builds the Docker image and pushes it to the registry. For the configuration details, see [`infrastructure-manager.yaml`](https://github.com/kyma-project/test-infra/blob/a3c2a07da4ba42e468f69cf42f1960d7bfcc3fff/prow/jobs/kyma-project/infrastructure-manager/infrastructure-manager.yaml). +- `main-infrastructure-manager-build` - Triggered after the merge. Rebuilds the image and pushes it to the registry. For the configuration details, see [`infrastructure-manager.yaml`](https://github.com/kyma-project/test-infra/blob/a3c2a07da4ba42e468f69cf42f1960d7bfcc3fff/prow/jobs/kyma-project/infrastructure-manager/infrastructure-manager.yaml). \ No newline at end of file diff --git a/internal/controller/find_last_sync_time_test.go b/internal/controller/find_last_sync_time_test.go index 60c7af33..9d41e0c8 100644 --- a/internal/controller/find_last_sync_time_test.go +++ b/internal/controller/find_last_sync_time_test.go @@ -3,8 +3,8 @@ package controller import ( "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" //nolint:revive + . "github.com/onsi/gomega" //nolint:revive ) var _ = Describe("findLastSyncTime", func() { diff --git a/internal/controller/gardener_cluster_controller_test.go b/internal/controller/gardener_cluster_controller_test.go index 30b46088..3a5f4531 100644 --- a/internal/controller/gardener_cluster_controller_test.go +++ b/internal/controller/gardener_cluster_controller_test.go @@ -5,8 +5,8 @@ import ( "time" imv1 "github.com/kyma-project/infrastructure-manager/api/v1" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" //nolint:revive + . "github.com/onsi/gomega" //nolint:revive corev1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/internal/controller/next_requeue_test.go b/internal/controller/next_requeue_test.go index fc496db6..711db27d 100644 --- a/internal/controller/next_requeue_test.go +++ b/internal/controller/next_requeue_test.go @@ -3,8 +3,8 @@ package controller import ( "time" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" //nolint:revive + . "github.com/onsi/gomega" //nolint:revive ) var _ = Describe("nextRequeueAfter", func() { diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index 1e6044b6..1ca78426 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -25,10 +25,10 @@ import ( infrastructuremanagerv1 "github.com/kyma-project/infrastructure-manager/api/v1" "github.com/kyma-project/infrastructure-manager/internal/controller/mocks" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" + . "github.com/onsi/ginkgo/v2" //nolint:revive + . "github.com/onsi/gomega" //nolint:revive "github.com/pkg/errors" - . "github.com/stretchr/testify/mock" + . "github.com/stretchr/testify/mock" //nolint:revive "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -42,12 +42,12 @@ import ( // http://onsi.github.io/ginkgo/ to learn more about Ginkgo. var ( - cfg *rest.Config //nolint:gochecknoglobals - k8sClient client.Client //nolint:gochecknoglobals - testEnv *envtest.Environment //nolint:gochecknoglobals - suiteCtx context.Context //nolint:gochecknoglobals - cancelSuiteCtx context.CancelFunc //nolint:gochecknoglobals - anyContext = MatchedBy(func(ctx context.Context) bool { return true }) + cfg *rest.Config //nolint:gochecknoglobals + k8sClient client.Client //nolint:gochecknoglobals + testEnv *envtest.Environment //nolint:gochecknoglobals + suiteCtx context.Context //nolint:gochecknoglobals + cancelSuiteCtx context.CancelFunc //nolint:gochecknoglobals + anyContext = MatchedBy(func(_ context.Context) bool { return true }) //nolint:gochecknoglobals ) const TestKubeconfigValidityTime = 24 * time.Hour