Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Ginkgo/Gomega Test Practices in Scaffolded and e2e Tests #4424

Open
camilamacedo86 opened this issue Dec 13, 2024 · 5 comments
Open
Assignees
Labels
good first issue Denotes an issue ready for a new contributor, according to the "help wanted" guidelines. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. testing

Comments

@camilamacedo86
Copy link
Member

camilamacedo86 commented Dec 13, 2024

What do you want to happen?

Kubebuilder creates/scaffolds a lot of test code. This test code uses Ginkgo and Gomega. Some of the tests as written don't take full advantage of Gomega's testing framework. Since Kubebuilder is likely to introduce many developers to Gomega, it is important that the scaffolded code provides the best possible examples.

We should see that the tests scaffolded for end-users have this issue fixed already: testdata/project-v4/test.

What we need to do here is ensure that ALL our e2e tests are also following the same patterns as the end-user scaffolds. Therefore, the necessary changes must be implemented in: test/e2e.

Context

The context for this issue and similar discussions can be found here: kubernetes-sigs/kubebuilder#4135.

Tasks

  1. Review and compare the test scaffolds in testdata/project-v4/test to ensure best practices in Ginkgo/Gomega are being followed.
  2. Update all e2e tests in test/e2e to align with these patterns and provide the best examples for developers.
  3. Validate that the tests maintain readability while leveraging Gomega's framework effectively.

Extra Labels

/kind cleanup

@camilamacedo86 camilamacedo86 added the kind/feature Categorizes issue or PR as related to a new feature. label Dec 13, 2024
@k8s-ci-robot k8s-ci-robot added the kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. label Dec 13, 2024
@camilamacedo86 camilamacedo86 added testing good first issue Denotes an issue ready for a new contributor, according to the "help wanted" guidelines. and removed kind/feature Categorizes issue or PR as related to a new feature. labels Dec 13, 2024
@Sijoma
Copy link
Contributor

Sijoma commented Dec 14, 2024

/assign

Sijoma added a commit to Sijoma/kubebuilder that referenced this issue Dec 14, 2024
Sijoma added a commit to Sijoma/kubebuilder that referenced this issue Dec 22, 2024
Sijoma added a commit to Sijoma/kubebuilder that referenced this issue Dec 24, 2024
Sijoma added a commit to Sijoma/kubebuilder that referenced this issue Dec 27, 2024
Sijoma added a commit to Sijoma/kubebuilder that referenced this issue Dec 27, 2024
@mogsie
Copy link
Contributor

mogsie commented Dec 27, 2024

In #4135, I originally made a suggestion of replacing the following type of tests:

result, err := someFunc(param, param)
Expect(err).NotTo(HaveOccurred())
Expect(result).To(ContainSubstring("blah"))

with the more concise (but functionally equivalent)

Expect(someFunc(param, param)).To(ContainSubstring("blah"))

The one line above

  • checks that err is nil
  • and that the resullt of the function call matches the matcher.

Should we reconsider this? The one line is (to me) more maintainable, although it relies Gomega implicitly testing that err == nil of the last return value.

From #4135:

... this has been decided against, as it hides the number of return values, which makes it harder to learn the API being tested. The desire is therefore to keep the three-line version, instead of in-lining it.

Maybe we should provide a document that describes the desired test idioms for the scaffolded tests and for the internal tests? Because, as mentioned, kubebuilder will often be introducing both Ginkgo and Gomega to its users; we should probably spend time explaining some of those test idioms in the scaffolded tests.

e.g. adding a comment as denoted by /**/ below:

	BeforeAll(func() {
		By("creating manager namespace")
		cmd := exec.Command("kubectl", "create", "ns", namespace)
		_, err := utils.Run(cmd)
		Expect(err).NotTo(HaveOccurred(), "Failed to create namespace")

		By("labeling the namespace to enforce the restricted security policy")
		cmd = exec.Command("kubectl", "label", "--overwrite", "ns", namespace,
			"pod-security.kubernetes.io/enforce=restricted")
/**/		// Expect(...).Error() allows us to make assertions about any errors
/**/		Expect(utils.Run(cmd)).Error().NotTo(HaveOccurred(), "Failed to label namespace with restricted policy")

This way we would be "teaching" the API (in this case the utils.Run(), and then how to use Ginkgo to make assertions against that.

@camilamacedo86
Copy link
Member Author

Hi @mogsie,

We really appreciate the amazing contributions you’ve been making to the tests—it’s fantastic work!
However, regarding the use of utils.Run(cmd), we need to keep it consistent with examples like the one below:

_, err = utils.Run(cmd)
Expect(err).NotTo(HaveOccurred(), "Failed to deploy the controller-manager")

I believe we discussed this previously and even reverted a similar change in the past—right?

Why this is important:

It makes clear what is returned from utils.Run(cmd). Otherwise, requiring explicit checks for the function reduces its maintainability. If we need to add comments to explain its behavior, that indicates the code itself isn’t as clear as it could be. Don't you agree?

Most importantly, this approach allows us to check for errors encountered during the tests. Remember that we checked that?

@camilamacedo86
Copy link
Member Author

camilamacedo86 commented Dec 27, 2024

So, to close this one, IHMO it is only missing:

Replace the ExpectWithOffset. See: https://github.com/search?q=repo%3Akubernetes-sigs%2Fkubebuilder%20ExpectWithOffset(1&type=code

Then, we can close this one.

@mogsie
Copy link
Contributor

mogsie commented Dec 28, 2024

I believe we discussed this previously and even reverted a similar change in the past—right?

Yes, but I think it happened on slack, so it is hard to find. This is why I think maybe these decisions should live in a document in the repo, for contributors to get guidance.

Why this is important:

It makes clear what is returned from utils.Run(cmd).

utils.Run(cmd) is invoked many times elsewhere in the scaffolded code, capturing its results into variables, when they are necessary for next steps. This should be enough. Here is an example.

podOutput, err := utils.Run(cmd)
g.Expect(err).NotTo(HaveOccurred(), "Failed to retrieve controller-manager pod information")
podNames := utils.GetNonEmptyLines(podOutput)
g.Expect(podNames).To(HaveLen(1), "expected 1 controller pod running")
controllerPodName = podNames[0]
g.Expect(controllerPodName).To(ContainSubstring("controller-manager"))

If we need to add comments to explain its behavior, that indicates the code itself isn’t as clear as it could be. Don't you agree?

The comment is mainly to explain why two styles of assertions are used in the same function body. I would personally prefer to only do the second style, removing the need for a comment.

The implicit checking of errors of Gomega is indeed "hidden", but not embracing it results in a lot of extra noise and typing. Just in that e2e_test.go file, there are ~20 such "expect err not to have occurred" lines, and additional noise introduced by temporary variables that are often only used once By not showing this way of writing tests, the scaffolded code encourages IMHO bad habits.

Most importantly, this approach allows us to check for errors encountered during the tests. Remember that we checked that?

I'm not quite sure I understand this comment. The errors are automatically checked for nil-ness, unless you expect an error. If you're talking about making assertions on the error itself, then yes, you can still do this:

Expect(validator.ValidateCreate(ctx, obj)).Error().To(
MatchError(ContainSubstring("must be no more than 52 characters")),
"Expected name validation to fail for a too-long name")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
good first issue Denotes an issue ready for a new contributor, according to the "help wanted" guidelines. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. testing
Projects
None yet
Development

No branches or pull requests

4 participants