-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
refactoring pipelinerun unit test #2876
Conversation
This PR cannot be merged: expecting exactly one kind/ label Available
|
/kind cleanup |
This PR cannot be merged: expecting exactly one kind/ label Available
|
@@ -45,7 +45,6 @@ import ( | |||
corev1 "k8s.io/api/core/v1" | |||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | |||
"k8s.io/apimachinery/pkg/runtime" | |||
k8stesting "k8s.io/client-go/testing" | |||
ktesting "k8s.io/client-go/testing" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the same module was imported twice with different labels 😕
if err != nil { | ||
t.Fatalf("Somehow had error getting reconciled run out of fake client: %s", err) | ||
if len(actions) < 2 { | ||
t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Have added check on length before accessing that index in the list
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for explaining! really helps when reviewing :D
and good call!!
9e2766a
to
18d55b3
Compare
18d55b3
to
8ee2753
Compare
@vdemeester I rebased this PR but the label ( |
8ee2753
to
4beb2e3
Compare
/test pull-tekton-pipeline-integration-tests |
4beb2e3
to
d90e9e2
Compare
Diff in this PR looks huge 🤦♀️. Its all about introducing a common function to build PipelineRun which is used by all unit tests in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SGTM
/cc @afrittoli @bobcatfish
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome, thank you!
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: sbwsg The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
it's pretty amazing that it's |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks great! I have some feedback on how the code is organized over all (i.e.: you've made the code a lot better, here are some ways we can make it even better).
The only thing I'd want to see changed before merging this PR is the names of the new functions, to better reflect that they do more than just "get", other than that I'm happy to merge :D
Thanks again @pritidesai !!
if err != nil { | ||
t.Fatalf("Somehow had error getting reconciled run out of fake client: %s", err) | ||
if len(actions) < 2 { | ||
t.Fatalf("Expected client to have at least two action implementation but it has %d", len(actions)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for explaining! really helps when reviewing :D
and good call!!
rs []*v1alpha1.PipelineResource, | ||
conditions []*v1alpha1.Condition, | ||
namespace, prName string, | ||
wantEvents []string, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor: using newlines like this is not very standard in our Go codebase, we're much more likely to put these together on one line. ways to improve this:
- Docstring
- Long param lists are often identifed as a "code smell" (https://refactoring.guru/smells/long-parameter-list https://www.codingrevolution.com/long-parameter-list/#:~:text=Long%20parameter%20list%20in%20a,four%20is%20considered%20too%20many) the solution is usually to group these args together into an object you pass in - in this case I could see maybe instantiating a struct with these args and calling functions on it - that might help with some of the ambiguity around the many responsibilities of this function as well (if you wanted to break it up into multiple functions)
} | ||
} | ||
|
||
func getReconciledRun( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's important to try to indicate with naming whether mutations / assertions are happening in functions - reading the name of getReconciledRun
I would expect it to be only retrieving a run, but it seems to be doing several things:
- instantiating a controller
- reconciling
- asserting against events
Some alternative naming ideas: reconcileRun
(dropping "get" makes it seem less like it's just retrieving) assertRunSucceeds
Another idea, mentioned below as well: create a struct that holds all the args below and expose several functions instead of just one - this will probably still be significantly easier to read and work with than the amount of repetition we have today
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed getReconciledRun
to reconcileRun
Creating struct
is a great suggestion, I created a struct PipelineRunTest and made reconcileRun
as an attribute to PipelineRunTest
.
recorder := testAssets.Recorder | ||
|
||
if reconcileError := c.Reconciler.Reconcile(context.Background(), namespace+"/"+prName); reconcileError != nil { | ||
t.Fatalf("Error reconciling: %s", reconcileError) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have you considered using t.Helper? (https://blog.golang.org/go1.9)
It might be worth comparing what it looks like when one of the tests using this helper fails using it vs not using it (it might make more sense if you were to break this function up into a few)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, tried it, very useful, instead of pointing back at the common utility (reconcileRun
), it points back where the failure is originated, in actual test function 👍
d90e9e2
to
0361e49
Compare
Based on @bobcatfish's comments, I have added these changes:
|
0361e49
to
514da32
Compare
/test pull-tekton-pipeline-integration-tests |
@bobcatfish its ready for review again, PTAL 😸 There could be more refactoring done, mainly the way namespace and pipelineRun name are hard coded in the functions. |
@pritidesai: PR needs rebase. Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes/test-infra repository. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @pritidesai !! I described one step further that I think we can go that would make the PipelineRunTest interface just a little bit better, but im also happy to merge as is if you'd like (esp if you want to just get this improvement in so you can stop dealing with rebasing all the time!!)
} | ||
|
||
// Check generated events match what's expected | ||
if len(prt.WantEvents) > 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is fairly minor but something feels weird to me about having WantEvents and PRName be attributes of PipelineRunTest - i think it's that this makes it so that if you want to call reconcileRun more than once in the same test, e.g. for two different pipelineruns, one reconciled after the other, you'd need to modify PipelineRunTest
I could see making 2 changes:
- Make WantEvents and PRName and Namespace arguments to this function
- Have a NewPipelineRunTest function that returns an instance of PipelineRunTest and takes Test and Data as arguments
this way you can call NewPipelineRunTest to create your controller with the state it needs (store the testAssets as an attribute of PipelineRunTest) and you can call reconcileRun with as many runs as you want
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @bobcatfish, I have added NewPipelineRunTest and updated reconcileRun by adding function parameters (Namespace, PipelineRunName, wantEvents, and permanentError).
Now, the PipelineRunTest has:
test.Data `json:"inline"`
Test *testing.T
TestAssets test.Assets
Cancel func()
Also, have added defer
on Cancel
as part of each unit test instead of making it part of common function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Data
can be dropped from the PipelineRunTest
struct since NewPipelineRunTest
is creating the controller and instantiating PipelineRunTest
with assets, data
is not used/needed anywhere beyond NewPipelineRunTest
- But I think it makes PipelineRunTest
complete (with inputs and assets), can create separate PR to drop it if needed 😉
creating a common function to build reconciledRun and updating all tests to use that function wherever possible
514da32
to
b57563c
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for cleaning this up and tolerating all the back and forth @pritidesai !
/lgtm
|
||
// NewPipelineRunTest returns PipelineRunTest with a new PipelineRun controller created with specified state through data | ||
// This PipelineRunTest can be reused for multiple PipelineRuns by calling reconcileRun for each pipelineRun | ||
func NewPipelineRunTest(data test.Data, t *testing.T) *PipelineRunTest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm now just nitpicking, but i'd put the functions that are related to PipelineRunTest near the declaration of the struct. dont wanna block the PR tho!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks @bobcatfish I was planning on moving all helper functions including:
PipelineRunTest
struct declaration and related functions- getPipelineRunController
- conditionCheckFromTaskRun
- checkEvents
- eventFromChannel
etc, to a separate file /pkg/reconciler/pipelinerun/pipelinerun_helpers_test.go
as a follow up PR. pkg/reconciler/pipelinerun/pipelinerun_test.go
is a combination of helper utilities and actual unit tests and helper functions are spread across this file 😞 Will be easier to introduce more tests and/or reviewing them with such separation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay great!
A couple of thoughts:
- Somewhere in the top level test directory might be a good place to go, since that is where we are already putting our test libs there (might even make sense to work it into https://github.com/tektoncd/pipeline/blob/master/test/controller.go)
- I want to caution against naming and calling code "helpers" - this is totally a common practice but I think it's useful to get out of the habit, for a couple of reasons:
- People tend to use this terminology about libs used for test code - sometimes they use it for the code under tests as well but i think that's less common - and it seems almost like it's putting the code down, i.e., "i need this code to help me" whereas I think that well factored test code is just as important as any other well factored code, so libraries for test code are just as important as other code < rant over >
- The other (probably bigger and less my personal opinion) reason is that "helper" is not a very specific name - good naming and code organization requires thinking about what the code's responsibility is and naming and organizing it based on that; using names like "helper" "manager" "library" "util" is a short cut that prevents us from doing that. More on this at https://dave.cheney.net/2019/01/08/avoid-package-names-like-base-util-or-common and https://blog.golang.org/package-names in the section "bad package names":
Avoid meaningless package names. Packages named util, common, or misc provide clients with no sense of what the package contains. This makes it harder for clients to use the package and makes it harder for maintainers to keep the package focused. Over time, they accumulate dependencies that can make compilation significantly and unnecessarily slower, especially in large programs. And since such package names are generic, they are more likely to collide with other packages imported by client code, forcing clients to invent names to distinguish them.
Changes
Creating a common function reconcileRun to:
PipelineRun
controller -getPipelineRunController
Data
- PipelineRun, Pipeline, Tasks, etc.All of the above steps were repeating in all the test functions 😢
Updated all unit tests in
pipelinerun_test.go
to utilize this common function.Further possible refactoring:
Great suggestion from @afrittoli on using
Matches
instead of retrieving resources directly, here, which can go in a separate PR.One more PR would be great to consolidate
namespace
instead of hardcodingfoo
everywhere.One test failure results in
metrics recorder
failure for all unit tests and very hard to spot what was the actual cause of failure. Possibly fix the way we register metrics view.Submitter Checklist
These are the criteria that every PR should meet, please check them off as you
review them:
See the contribution guide for more details.
Double check this list of stuff that's easy to miss:
cmd
dir, please updatethe release Task to build and release this image.
Reviewer Notes
If API changes are included, additive changes must be approved by at least two OWNERS and backwards incompatible changes must be approved by more than 50% of the OWNERS, and they must first be added in a backwards compatible way.
Release Notes