-
Notifications
You must be signed in to change notification settings - Fork 19
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
Add file assertion #147
Add file assertion #147
Conversation
Hey @glesica, Thank you for your PR and your interest in commander we're always looking for more contributor! Expect some feedback this weekend 😄 Cheers |
Makefile
Outdated
|
||
integration-linux: build | ||
$(info INFO: Starting build $@) | ||
./integration/setup_unix.sh | ||
commander test commander_unix.yaml | ||
commander test commander_linux.yaml | ||
./commander test commander_unix.yaml |
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 was by intention. With this line you would use the dev-build for integration testing. Normally inside the path should be a stable version of commander, especially in CI environments like travis.
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.
Oh I see, very meta! I'm not using GOPATH so I'll just add a stable build to my personal bin/.
pkg/suite/yaml_suite.go
Outdated
@@ -101,6 +102,24 @@ func convertNodes(nodeConfs map[string]YAMLNodeConf) []runtime.Node { | |||
func convertYAMLSuiteConfToTestCases(conf YAMLSuiteConf, fileName string) []runtime.TestCase { | |||
var tests []runtime.TestCase | |||
for _, t := range conf.Tests { | |||
stdout := t.Stdout.(runtime.ExpectedOut) |
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 should be moved outside of the YAML convertion for several reasons.
- Adding a new file format would need to expand the files too, resulting in duplicate code (i.e. adding json)
- Separation of concerns would break here because the reading of the file is considered logic of the matcher and not of the YAML convertion
I can understand the approach by just expanding the file and using a TextMatcher
under the hood. I would suggest introducing a FileMatcher
instead, which does the same but at a different place.
The FileMatcher
reads the file and matches it with the TextMatcher
. Further it would allow easier testing of the FileMatcher
with unit tests.
So basically it does the same, but in the matcher
pkg.
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.
Gotcha, that makes sense. I wasn't sure where ya'll would want the file I/O to happen since it always makes testing harder.
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 pretty much in agreement with Simon, if you have implementation questions please ask!
On another note, I was thinking about how this would affect the I personally think this output isn't optimal. Do we want to think about writing a string method to make this printing better (such as labeling)? We could always make a note and bundle it into #119 @SimonBaeumer thoughts? We may also want to think about better diffing especially for file assertion. If we implement the
This can be tough to see differences in very large files. Do we want to do something where we show differences a bit better, like pinpointing where differences occur by line? I think better diffing is out of the scope for this PR so i'm fine with the current implementation. Maybe we make a note on #112. |
Would
I am with you on this, I would prefer a view which displays a table where it can be compared directly. |
6916ee8
to
781486a
Compare
OK, this should be more in line with what ya'll were thinking. I omitted a unit test in |
assert.Contains(t, got.Diff, "+0") | ||
assert.Contains(t, got.Diff, "-1") | ||
assert.Contains(t, got.Diff, "+3") | ||
assert.Contains(t, got.Diff, "-2") |
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 noticed that there was always a "-1" in the diff output because it tells the user how many lines changed, so this makes the test a tiny bit less brittle.
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 really like this MR already. This is something i've personally been wanting since I start contributing and haven't gotten around to it.
One thing: if you don't mind adding an example case in examples/
. You can follow the same idea as the big_out.txt
and add _fixtures/
and place whatever.txt
underneath.
Cheers
command: cat ./integration/unix/_fixtures/big_out.txt >&2 | ||
stderr: | ||
file: ./integration/unix/_fixtures/big_out.txt | ||
|
||
it should inherit from parent env: |
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.
Would you mind adding a failing case as well?
pkg/matcher/matcher_test.go
Outdated
|
||
func TestFileMatcher_Validate(t *testing.T) { | ||
m := FileMatcher{} | ||
got := m.Match("zoom", "zoom.txt") |
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 okay with reading directly from the filesystem here, there's not really a good way to mock ioutil.ReadFile
, that I know of. The best thing I've found is this, and even this would require some code change. I personally think this is too much for well tested code already.
Another direction that could be taken is removing the actual code needing to be tested and placing it in a private helper function. For example, it wouldn't be stretch to write a helper function that could be used in both FileMatcher
and TextMatcher
, and just perform the I/O in the FileMatcher
and then call the helper method. I'm neither here nor there on this approach.
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.
One thing @SimonBaeumer if we take the approach from reading from the FS should we designate a folder to do this?
Or a temp file could be created and then used/removed.
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 thought about the temp file approach but I wanted input since that didn't feel great either. Ordinarily I'd probably accept a Reader
instead of a filename but then we're back to the original question of where does the file actually get opened.
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, the only other thing I can really think of is to FileMatcher
take an argument for a function, that basically wraps ioutil.ReadFile(filePath string). Like:
type TestingMocking struct {
readFile func(filePath string) ([]byte, error)
}
You could then setup FileMatcher
in NewMatcher
.
And then:
func (m FileMatcher) Match(got interface{}, expected interface{}) MatcherResult {
buf, err := m.readFile(expected.(string))
// rest goes here
This would allow us to mock readFile. Again I think this may be too robust. I think I'll defer @SimonBaeumer on this as I don't really have preference.
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.
Good call, I'll try that out and see how it ends up.
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 Together,
so yes, ideally we would mock the filesystem but this not often done and not necessary imho.
I am totally fine with using libraries and access to filesystem or env variables.
The downside of mocking is in general that it gets very complex and often takes a lot of effort to maintain.
Mocking is nice if there is some complex things I want to replace, like network connections, tasks that need a lot of time or simply complex stuff to setup, i.e. you would need to run a kubernetes cluster.
Go ignores directories starting with underscores, like _assets
and directories matching testdata
.
Just put the asset inside a pkg/matcher/testdata
directory.
You should be able access it like this:
wd, _ := os.Getwd()
asset := path.Join(wd, "testdata/file.txt")
content, _ := ioutil.ReadFile(asset)
The testing framework of go will execute all tests with the working directory pointing to the currently executed file, so it is easy to access the testdata.
Mocking:
Another option like @dylanhitt mentioned is to use dependency injection. That means that we inject (normally as input parameters in the constructor or functions) dependencies on other structs (or classes).
These classes / structs do implement an interface, therefore you could replace the implementation inside your tests with a fake one.
Original implementation, i.e. in file_matcher.go
:
type FileReaderInterface interface {
func Read(file string) string
}
type FileReader struct {}
func (r *FileReader) Read (file strinng) string {
ioutil.ReadFile...
// direct access to filesystem
}
type FileMatcher struct {
Reader FileReaderInterface
}
func NewFileMatcher() *FileMatcher {
var reader FileReaderInterface
reader = FileReader{}
return &FileMatcher{
Reader: reader,
}
}
In your test file_matcher_test.go
you would than do something like this:
type FileReaderMock {}
func Read(file string) string {
if file == "test.txt" {
return "Mocked this for a test"
}
if file == "test2.txt" {
return "mocked for another test"
}
// maybe panic if the file wasn't mocked
panic("file was not mocked")
}
func TestFileMatcher(t *testing.T) {
var readerMock FileReaderInterface
readerMock = FileReaderMock{}
// Inject the reader mock into the FileMatcher
matcher := FileMatcher{Reader: readerMock}
}
Nevertheless, I am totally happy with both approaches.
I added a mutable function to the |
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 good to me. The last thing is to update the README to have a bullet in Table of contents under Documentation.Tests.stdout
and add short example/definition similar to say xml.
We have also found it very useful to add an example in examples/
. You can follow the examples/_fixtures
pattern for your fixtures. That's all from me 😄
Code Climate has analyzed commit 571c5e4 and detected 1 issue on this pull request. Here's the issue category breakdown:
The test coverage on the diff in this pull request is 80.0% (50% is the threshold). This pull request will bring the total coverage in the repository to 92.4% (0.0% change). View more on Code Climate. |
I think this is ready to merge unless anyone has comments (which I'd be happy to address). Thanks for all the discussion and suggestions so far! |
skip: true |
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.
Is this example still intended to work?
I am not sure the relative path to the fixtures would work
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.
So the reason why this is needed is because we're not properly mocking ioutil.Readfile in the unit tests for app.TestCommand
... 100% my fault. Here is the exact line. I opened #149 for this already and pasted the failing build that showed why this pathing was need.
I can move it over to the testdata
approach this afternoon. So the pathing can be fixed here.
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.
ahhh okay, than it can be merged 👍
LGTM, just the example needs a little adjustment to the relative path or removing the file assertion. Otherwise @dylanhitt feel free to merge |
Hmm, sorry, I'm not following. Which example needs to be updated? The one in the README? Should those be runnable? |
See here: https://github.com/commander-cli/commander/pull/147/files#r510672628 |
Merged! |
Hey how would ya'll feel about doing a minor version release? I want to use the file matcher but I don't want to make people download a Go toolchain to install it. |
Of course 👍 |
Fixes #105
Happy to discuss and make changes!
Checklist
Linux
,Windows
andmacOS
?