-
Notifications
You must be signed in to change notification settings - Fork 1
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
Suite feature #16
Suite feature #16
Conversation
An example of a custom testing pattern looks like this:
This happens to be (real!) code appropriate for wiring up a certain CLI library to generate its help text and use that as a fixture, so some of the errata in the function body concerns that. There's still a decent amount of code here -- partially because of the increased complexity of having the test function report for itself which hunks it has used! -- but drastically less than you'd have without using the suite system. We can also see that some references to the patch accumulator system remain fairly manual. I think this is natural and correct. Sometimes fixture regeneration involves updating exactly one hunk (like this case does); sometimes it's much more complicated (such as in testexec, which may update three different hunks at once!), and so this can't really be reduced any further. Compared to without using the suite system: you also would've had to repeat the filename multiple times, for loading the first time, and later for opening writably to patch, and so by using the suite system you're still saving quite a bit of boilerplate over the prior situation... as well as having the suite manager aggregate all patches to the same file for you, if more than one testing pattern is working on hunks in the same file, which is a nice improvement. |
reportUse func(string), // Should be called with the full path of any hunk that's consumed by this test. Used to detect orphaned hunks that went unused by the whole suite. | ||
reportUnrecog func(string), // If this test code owns all child hunks, it may call this to report one that it doesn't recognize. | ||
patchAccum *testmark.PatchAccumulator, // If non-nil, means regenerating golden master data is requested instead of testing. | ||
) error // Run may return errors or call t.Fatal itself. |
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 uncertain this error return makes any sense. I lean towards including an error
return when not sure, but, so far when implementing this, I've always just had return nil
.
The labeling performed when you return an error might be helpful, but... everything in that message is available in other ways:
- the filename is already in the t.Run name tree
- the hunk name is also already in the t.Run name tree
- the
tp.Name()
isn't, but... the line number in the t.Log output will point at its guts anyway.
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 fine with it returning an error
. testing.T.Run
returns a bool
and it seems more idiomatic to return an error than a bool.
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.
Right, but the callback parameter to testing.T.Run
, which is the more apt comparison, is func(*T)
with no return value.
I think I'll still side with you in keeping the error
return though, just because as a heuristic, almost any interface that doesn't return errors seems to surprise and frustrate me in the long run, much more often than not. And even if I'm wrong, foisting a "return nil" onto implementers isn't a very big burden.
@TripleDogDare and I had a little additional review discussion about this outside of github, which I'll replay some notes from: Mostly, wondered if that And we're hovering around "yeah, it's fine". We don't love the size and verbosity of that function interface, but the only alternatives we can think of are bundling two or more of those parameters into another object that we export from this
... which seems to sum to overall to pretty much a wash. |
I admit I gave a little more thought yet to if "adding more accessors without it being a breaking change" thing should be the dominant consideration, but I think I'm gonna halt that train of thought and get on with merging this. If we start introducing features that make this package less optional, then we might as well move the suite feature to the core, and then that can be the time to revisit the API with whatever we've learned in the meanwhile from usage in the wild. |
I haven't updated the |
This addresses several sources of recurring boilerplate that I'm distilling from multiple experiences of using testmark in practice, and also seeks to address several common footgun issues. especially around detecting whether or not fixtures are actually used. With this, you are sometimes required to write a more complex TestingPattern.Run function, but in exchange, you get vastly less boilerplate about file loading, parsing, index buildilng, and hunk walking, as well as a great number of guardrails such as warnings about unused hunks, and your overall setup can become very declarative: For example, the declarative top-level wiring may now look like: func TestCommandDocs(t *testing.T) { suite := sweet.NewSuiteManager(osfs.DirFS(fixtureDir)) suite.MustWorkWith("fixture-*.md", "testcase/*", customTestingPattern{}) suite.MustWorkWith("demo-*.md, "*", testexec.TestingPattern{...}) suite.Run(t) } And hopefully we can make more reusable TestingPattern APIs appear throughout other extensions as well (such as testexec). This should make such extensions much easier to wire up, and much easier to combine with other tests or extensions. This introduces a dependency on the go-fsx package, which adds writablity to the features we can expect of an FS interface. Avoiding this doesn't seem worth additional effort. (I would be happy to drop it the instant that stdlib adds equivalent features.)
Co-authored-by: CJB <[email protected]>
This replaces the strict mode feature. If you use the suite style, you get strict mode. (We could re-introduce a piece of config that causes testexec to claim use for all child hunks, or at least to decline to report any explicitly unrecognized hunks, but today, I don't feel inclined. I don't think use of that feature has been above about zero.) Removed many logf statements. Successful tests should be quiet. Switched quite a few errors that were previously conditional back to immediate fatalf, where they should be. Others disappeared into becoming part of the suite report hook system. For the testing of this thing itself: I don't love the `RunFailTest` thing we've got going on here, but I see why it's a stand-in for mocking the entirety of `*testing.T`, and I'm not gonna fight with that today. If you do run the new test with that flag (or just the condition commented out), you'll indeed see nice errors from the suite system about unused and unrecognized hunks.
Okay, I snuck updates to the It did cause one more change to the suite API, though: the |
Also change the name of "TestingPattern" to "TestingFunctor", because I noticed I used the word "pattern" to describe both globs as well as the callback interface, and that's quite confusing.
... I'll continue wondering if this API is perfect in the future 🙃 It's already valuable enough that I really want to start using it downstream, so it's merge time 😆 |
This addresses several sources of recurring boilerplate that I'm distilling from multiple experiences of using testmark in practice, and also seeks to address several common footgun issues. especially around detecting whether or not fixtures are actually used.
With this, you are sometimes required to write a more complex TestingPattern.Run function, but in exchange, you get vastly less boilerplate about file loading, parsing, index buildilng, and hunk walking, as well as a great number of guardrails such as warnings about unused hunks, and your overall setup can become very declarative:
For example, the declarative top-level wiring may now look like:
And hopefully we can make more reusable TestingPattern APIs appear throughout other extensions as well (such as testexec). This should make such extensions much easier to wire up, and much easier to combine with other tests or extensions.