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.

Quellinformationen

Repository
openshift/cluster-capi-operator
Letzte Quellaktivität
19. Februar 2026 um 16:28
Erkannte Sprache von SKILL.md
Englisch
Sterne
15
Forks
76

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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.
Auf GitHub ansehen