Skip to main content

test-standards

Apply when writing, modifying, or reviewing test files (_test.go). Covers test level selection, assertion quality, BDD naming, and use of shared helpers.

Informations de source

Dépôt
openshift/cluster-capi-operator
Dernière activité de la source
19 février 2026 à 16:28
Langue détectée de SKILL.md
anglais
Étoiles
15
Forks
76

Options d'installation

Le prompt qui vérifie d'abord la source est sélectionné par défaut. Vous pouvez passer à une commande directe ou télécharger une copie locale.

Vérifiez les fichiers source

Lisez SKILL.md et les fichiers associés affichés par SkillsMP avant de décider de l'installer.

Affichage de SKILL.md

SKILL.md
Instructions source · Aperçu en lecture seule
name
test-standards
description
Apply when writing, modifying, or reviewing test files (_test.go). Covers test level selection, assertion quality, BDD naming, and use of shared helpers.
When writing tests, follow these standards. When reviewing tests, flag violations and suggest fixes with code examples. ## Choosing the right test level Pick the cheapest level that can adequately cover the behaviour. Do not escalate without reason. **Unit test** (no client or fake client, no envtest) — for pure logic, conversions, single-function behaviour, error paths that don't depend on server-side behaviour. Use `fake.NewClientBuilder()` only when the test doesn't depend on realistic API server responses (field defaulting, status subresource semantics, conflict errors, SSA merge, etc.). These run in milliseconds. **Integration test** (envtest) — for anything that interacts with a Kubernetes API: controller reconciliation loops, multi-resource interactions, watching, status updates, and any scenario where fake client behaviour diverges from a real API server. Prefer envtest over fakes when in doubt — faking accurately is hard and flaky fakes waste more time than the slower test. Use `pkg/test.StartEnvTest()` in `suite_test.go`. These run in seconds. **E2E test** (`e2e/`) — only for behaviour that requires real infrastructure: actual machine provisioning, cloud API interactions, cross-component migration flows. These run in minutes. Rules of thumb: - If you're testing "does this function return the right value/error" and it doesn't need a client → unit test. - If you're testing any controller or client interaction → integration test (envtest). - If you're testing "does a real machine appear in the cloud" → e2e test. - If envtest can reproduce the scenario, do not write an e2e test. ## Use existing shared helpers Before writing new test utilities, builders, matchers, or setup code, search the repo for existing ones — particularly in `pkg/test/`, `pkg/conversion/test/`, `pkg/admissionpolicy/testutils/`, `e2e/framework/`, and the vendored `testutils/resourcebuilder/` package. Do not duplicate what already exists. If you need a variant, extend the existing helper rather than creating a parallel one. ## Debuggable failures Every test failure must be debuggable from the output alone — without reading test source code. This means two things: **1. Assertion messages.** If a failure's stack trace and default matcher output wouldn't tell you what went wrong, add a description. This applies especially to generic matchers like `BeNil()`, `BeTrue()`, `HaveLen()` where the default output doesn't convey intent. ```go // good — failure output explains the scenario Expect(transport).To(BeNil(), "expected nil transport when additionalTrustedCA is not set") // bad — failure output is just "expected nil, got &http.Transport{...}" Expect(transport).To(BeNil()) ``` **2. Stack traces.** Do not call `Expect`, `Fail`, or panic from helper functions — failures will point at the helper, not the test that called it. Return errors to the calling test instead. If assertions inside a helper are unavoidable, use `GinkgoHelper()` so the stack trace shows the caller: ```go func expectResourceReady(obj client.Object) { GinkgoHelper() Expect(obj.GetAnnotations()).To(HaveKey("ready")) } ``` ## BDD test names Describe **expected behaviour**, not implementation details. Names should read as specifications. ```go // good — describes behaviour It("should reject machines with duplicate provider IDs", func() { ... }) // bad — describes implementation It("should return an error from validateProviderID", func() { ... }) ``` Use nested `Context` blocks with `when` to set up preconditions: ```go Context("when the infrastructure cluster is not ready", func() { It("should requeue after 30 seconds", func() { ... }) }) ``` Use `By()` to document distinct phases within a test. ## Concise assertions Prefer precise matchers over multiple loose ones. Combine into one `Expect` when possible. ```go // good — single assertion, exact match Expect(err).To(MatchError(expectedErr)) // bad — two assertions, string matching Expect(err).To(HaveOccurred()) Expect(err).To(MatchError(ContainSubstring("connection refused"))) ``` When an expected error is reused across multiple test cases, declare it as a variable rather than duplicating the literal: ```go // good — declared once, reused across cases expectedErr := fmt.Errorf("connection refused") Expect(err).To(MatchError(expectedErr)) // bad — same string repeated in every test case Expect(err).To(MatchError("connection refused")) ``` Combine related assertions into a single matcher (e.g., `SatisfyAll`, `ConsistOf`). With `Eventually`, each separate assertion polls with its own timeout — multiple assertions checking the same object multiply the wait time on failure. ## No sleeps, no timeout bumps In event-driven systems, tests should wait for conditions, not for time to pass. - **Never use `time.Sleep()`**. Use `Eventually` with a condition that checks the actual state you're waiting for. - **Do not bump `Eventually` timeouts to fix flaky tests.** A flaky test means the test is waiting for the wrong condition or the code has a race. Fix the root cause. - **`Consistently` durations should be meaningful.** Too-short durations prove nothing — the condition might change immediately after. Use a duration long enough to cover at least a few reconciliation cycles. ```go // good — waits for the actual state change Eventually(komega.Object(machine)).Should(HaveField("Status.Phase", Equal("Running"))) // bad — arbitrary sleep hoping the controller has finished time.Sleep(5 * time.Second) Expect(machine.Status.Phase).To(Equal("Running")) ``` ## General - Follow patterns from CLAUDE.md (Komega, DescribeTable, resource builders, testutils cleanup). - Remove `FIt`/`FContext` before committing.
Voir sur GitHub