generated from broadinstitute/golang-project-template
-
Notifications
You must be signed in to change notification settings - Fork 0
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
[DDO-3280] TestData mechanism #348
Merged
Merged
Conversation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
No API changes detected |
Kudos, SonarCloud Quality Gate passed! 0 Bugs No Coverage information |
em-may
approved these changes
Oct 31, 2023
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Test-only PR, no risk. I'm going to spew a bunch of explanation here about why I think this is so cool.
The Problem
We want to have some sort of example data available for use in tests. This helps make tests less verbose -- without it, for example, creating a chart release requires also creating a chart, environment, and cluster. The repetition of doing so throughout the codebase makes refactoring or adding validation more difficult, too.
What Sherlock v2 Did
Inside test files, Sherlock's v2 controllers would define
var
s with essentially API request bodies.There'd also be a
var
list of all the request bodies, and a method to lob them all at Sherlock to add them all to the database.In a test, you'd need to call
suite.seedCharts(suite.T(), suite.db)
, then if you query the database it won't be empty anymore.The Problem With What Sherlock v2 Did
The issue rears its head as soon as we get into relational types. Take what we do for chart versions:
The database defines relations in terms of IDs. We certainly don't know those IDs statically, so we're forced to define our test data at the API level, where we can define relations in terms of API-user-friendly things like the name. This means that our test data is only useful to us in one package! We'll get circular import errors if we try to bring this test data into other packages, since everything ends up being re-imported by the API package.
More importantly, though, this completely breaks down for types that don't have another way to reference them (types that only have IDs, like changesets or deploy hooks) or for anything that's more internal and doesn't have a creation endpoint.
How We Want To Fix It
For API tests we will always need to define some request payloads, but we can solve a lot of our problems if we can figure out how to define test data at a lower level. To do that and still be able to set up relations, we'll need to somehow reference IDs that we don't know at compile-time (in fact, they'll potentially be different on each test run).
The Solution
In Sherlock v3, the low-level models package already exports a
TestSuiteHelper
that is meant to be embedded side-by-side with the stretchr/testify library'sSuite
(example). That helper sets up and tears down the database connection and is model-aware to let tests impersonate aUser
(so the tests from any package that uses the helper can properly use the database).What's new is that the
TestSuiteHelper
now includesTestData
.TestData
provides methods that return sample data, and those same methods handle making sure it's in the database.For example, to get ahold of an example value for Leonardo's chart, you'd call
TestData.Chart_Leonardo()
.The trick is the
testDataImpl
struct. It holds a back-reference to theTestSuiteHelper
(so it can call the database) and it also caches within the context of each test. You can callTestData.Chart_Leonardo()
repeatedly and only the first call will hit the database. At the end of each test function, theTestSuiteHelper
wipes away thetestDataImpl
to reset the cache as it wipes away the database.Here's
TestData.Chart_Leonardo()
:It's not quite as nice as a
var
orconst
, but here's the magic. Suppose we want a chart version. Here'sTestData.ChartVersion_Leonardo_V1()
:We can get the chart ID here! That call to
td.Chart_Leonardo().ID
will create the Leonardo chart if it doesn't exist, just in time.Chart versions are a little special too -- they like to read the user that's creating them to store it in the
AuthoredBy
field. No big deal: we have a reference to theTestSuiteHelper
here (h
), so we can treat this like a test and set the user before creating the chart version. Many other data types do something similar but for validating permissions, so this capability is deceptively important.But wait, how does
TestSuiteHelper.SetSuitableTestUserForDB()
work? Guess what, it just borrowsTestData.User_Suitable()
, it's all self-consistent (TestSuiteHelper.SetSuitableTestUserForDB()
existed before this PR, so we're bringing it along for the ride):The wonderful part about this is that it's lazy. We can add as much test data as we like without bogging down or changing any existing tests. In fact, this
TestData
is already available everywhere because it's inTestSuiteHelper
, but all of our tests that expect an empty database etc. still work just fine. There's still certainly times where manually setting up data is nice (here's a good example) but this should make ongoing Sherlock development faster and easier.Example
Here's an example included with this PR of using the test data and how it can eliminate dummy data and boilerplate: