-
Notifications
You must be signed in to change notification settings - Fork 1k
Moving to golden-file pattern for unit tests #215
Conversation
Hmm. Having issues with Travis that I don't fully understand. Will remove and try to resubmit after digging. |
Ah - no copyright on test/util.go |
@sdboyer - let me know if this is an appropriate direction. Note I've added files to your _testdata directory, a test/util.go file for comparing JSON content, and added a field to the test.Helper struct to record the original working directory. (Will be needed for integration tests.) Not yet ready for merge - just looking for feedback. Thx. |
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.
Some nits, but otherwise +1 from me.
test/test.go
Outdated
@@ -45,7 +47,8 @@ type Helper struct { | |||
|
|||
// NewHelper initializes a new helper for testing. | |||
func NewHelper(t *testing.T) *Helper { | |||
return &Helper{t: t} | |||
wd, _ := os.Getwd() |
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.
Since we are in a test, this should probably panic if there is an error.
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.
Fair enough
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.
Also, just a note - go test
will always set the cwd of the test process to the directory containing the test file that's running.
test/test.go
Outdated
panic(err) | ||
} | ||
if strings.HasSuffix(src, ".go") { | ||
formatted, err := format.Source(content) |
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.
We should be writing well formatted code to disk when necessary and not reformatting it. This can lead to issues when tests will need to deal with poorly formatted code.
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.
Agreed. Was copying the existing pattern, but I did add a "style-independent" JSON comparison to test. If we agree that formatting like this makes the test more brittle, I am happy to remove it - just trying to go with the flow, as it were.
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.
For me anyway, when considering .go code, this would surprise me if I had committed poorly formatted, yet valid, go code in order to test a bug with poorly formatted code.
test/test.go
Outdated
// GetTestFileReader reads a file form the testdata directory into memory. src is | ||
// relative to ./testdata. Assumes tests take place starting in the cmd/dep | ||
// directory. | ||
func (h *Helper) GetTestFileReader(src string) io.Reader { |
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.
IMO this should return an *os.FIle instead of reading the whole thing into memory.
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.
Also return an io.ReadCloser so that whomever calls this can defer a Close().
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.
Once this returns an io.ReadCloser, GetTestFileBytes can use this instead.
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.
Agreed on all points
test/test.go
Outdated
// TempCopy copies a temporary file from testdata into the temporary directory. | ||
// dest is relative to the temp directory location, and src is relative to | ||
// ./testdata. Assumes tests take place starting in the cmd/dep directory. | ||
func (h *Helper) TempCopy(dest, src 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.
I don't see this being used anywhere ATM, so please remove.
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.
It will be in the integration tests, where os processes are called. Still wrapping my head around the correct Go way to handle them.
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, I would add that then when it's used.
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.
Yep, or just push the follow-up commits that use it :)
test/test.go
Outdated
// GetTestFileString reads a file form the testdata directory into memory. src is | ||
// relative to ./testdata. Assumes tests take place starting in the cmd/dep | ||
// directory. | ||
func (h *Helper) GetTestFileString(src string) 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.
AFAICT this isn't really needed, just use []bytes.
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. This is a newbie (ie me) one. If "forget string
- use []byte
" is the standard, happy to oblige. I'm just trying to minimize future test developer keystrokes. I'm a lazy, lazy man. :-)
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.
Well, where this method is used it seems like you are converting back to a []byte again.
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 also find the []byte
vs string
thing a bit confounding. In general, I try to just follow the rule of "don't convert unless you must." The conversion isn't terribly costly, but it also isn't free, so all other things being equal, I err on the cheaper side.
(However, if you know you have to add data, []byte
is definitely better; strings are immutable, so e.g. string concat results in a full copy. Appending to a []byte
is just a normal append
, though)
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.
Looks like good progress. No flag for updating the golden files yet, it looks like?
test/test.go
Outdated
@@ -414,6 +417,47 @@ func (h *Helper) TempFile(path, contents string) { | |||
h.Must(ioutil.WriteFile(filepath.Join(h.tempdir, path), bytes, 0644)) | |||
} | |||
|
|||
// GetTestFileBytes reads a file form the testdata directory into memory. src is |
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.
nit: s/form/from/
test/test.go
Outdated
return string(h.GetTestFileBytes(src)) | ||
} | ||
|
||
// GetTestFileReader reads a file form the testdata directory into memory. src is |
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.
nit: s/form/from/
test/test.go
Outdated
// GetTestFileReader reads a file form the testdata directory into memory. src is | ||
// relative to ./testdata. Assumes tests take place starting in the cmd/dep | ||
// directory. | ||
func (h *Helper) GetTestFileReader(src string) io.Reader { |
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.
Agreed on all points
test/test.go
Outdated
// TempCopy copies a temporary file from testdata into the temporary directory. | ||
// dest is relative to the temp directory location, and src is relative to | ||
// ./testdata. Assumes tests take place starting in the cmd/dep directory. | ||
func (h *Helper) TempCopy(dest, src 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.
Yep, or just push the follow-up commits that use it :)
test/test.go
Outdated
// GetTestFileString reads a file form the testdata directory into memory. src is | ||
// relative to ./testdata. Assumes tests take place starting in the cmd/dep | ||
// directory. | ||
func (h *Helper) GetTestFileString(src string) 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.
I also find the []byte
vs string
thing a bit confounding. In general, I try to just follow the rule of "don't convert unless you must." The conversion isn't terribly costly, but it also isn't free, so all other things being equal, I err on the cheaper side.
(However, if you know you have to add data, []byte
is definitely better; strings are immutable, so e.g. string concat results in a full copy. Appending to a []byte
is just a normal append
, though)
test/test.go
Outdated
@@ -45,7 +47,8 @@ type Helper struct { | |||
|
|||
// NewHelper initializes a new helper for testing. | |||
func NewHelper(t *testing.T) *Helper { | |||
return &Helper{t: t} | |||
wd, _ := os.Getwd() |
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.
Also, just a note - go test
will always set the cwd of the test process to the directory containing the test file that's running.
test/util.go
Outdated
"encoding/json" | ||
) | ||
|
||
func AreEqualJSON(s1, s2 string) (bool, error) { |
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.
Sorta related to the output reformatting discussion - I think it might be better not to have this function, and instead just do a direct ==
or bytes.Equal()
comparison on the golden data vs. the output data. The only way that erroneously mismatched output should be able to happen there is if something in the formatting rules changes. If that happens, though, then IMO it would be expected that the golden output would have to change.
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 guess the philosophical question here is - do you want to include the formatting as something to be explicitly tested? Is, for example, the number of spaces of indent on JSON files part of the spec? Historically I've tried to just compare the data, but I'm happy to go whichever way you want. (I was, by the way, getting all errors when comparing to the golden files, originally. Maybe an extra newline or something. I didn't dig as I was adding this anyway.)
I'll leave it in on this next pass, and if you repeat you want it pulled, I'll be happy to do so.
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'd rather a ==
or bytes.Equal()
as well. While white space isn't part of the spec, a change there should be investigated.
If we are going to keep AreEqualJSON
though, I'd rather the name be changed to JSONEqual
.
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. I'll strip out the JSONEqual function and move to ==.
@sdboyer @freeformz - Guys, thanks for the comments; learning as I go. :-) (No pun intended.)
(Turns out that last was needed in Please let me know your thoughts. |
We found a Contributor License Agreement for you (the sender of this pull request), but were unable to find agreements for the commit author(s). If you authored these, maybe you used a different email address in the git commits than was used to sign the CLA (login here to double check)? If these were authored by someone else, then they will need to sign a CLA as well, and confirm that they're okay with these being contributed to Google. |
09397b0
to
54787d1
Compare
CLAs look good, thanks! |
@sdboyer @freeformz - the |
Looks pretty good! Thanks for sticking with this. Just...
Hmm, I think there may have been a misunderstanding about what the flag is supposed to be for. (To be clear, the misunderstanding may be on my end - I just learned about this pattern a couple weeks ago). The flag isn't useful in a CI context - rather, it's a switch that causes the tests to write the generated results to the golden files, rather than comparing against them. That way, the human running the tests can make a selective decision about when the output is known to be good and overwrite the golden file automatically, rather than having to do any tedious and error prone manual copying. |
I believe the convention is to have the testdata directory called |
@bradleyfalzon I'm fine either way. I was just using the existing |
@sdboyer oh I misunderstood "flags" to mean "CI errors". Do you have an example of the flags you'd like to see that you could point me at? |
@tro3 yep yep - straight from stdlib. There's that update flag declared at the top, and then further down (line 101 as of this writing), it's checked, and if enabled, then the golden file is written to rather than compared against. |
Okay, I'm on it. I'm also going to rename |
yep, SGTM |
Okay, this one is ready to merge. Let me know if you want it squashed. I'm going to go back to test coverage on Lock and Manifest in a different PR. |
This is cropping up a weird error for me on OSX. Once I get that figured out, I'm ready to merge. |
analyzer_test.go
Outdated
} | ||
h.TempDir("dep") | ||
golden := "analyzer/manifest.json" | ||
contents := h.GetTestFileString(golden) |
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.
If we are going for consistency with other go tools I think contents
should be want
and b
(down below) should be got
which IMO also simplifies reading of the ifs. With that said, there is a lot of those cases in the code where this should probably be cleaned up, so it's fine if we do that in a different PR too.
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, good call. IMO, we should try to start setting this bar and sticking with it. Sorry, @tro3, I realize this is dragging on. - do you mind making another change?
Not at all. I'll take care of it.
|
5401092
to
59437df
Compare
@sdboyer - moved to want/got and folded in some other test coverage increases I was working on |
analyzer_test.go
Outdated
) | ||
|
||
func TestDeriveManifestAndLock(t *testing.T) { | ||
dir, err := ioutil.TempDir("", "dep") | ||
func TestAnalyzerInfo(t *testing.T) { |
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.
FWIW: I am -1 on tests like this. We wrote the same code on both sides and are asserting they are equal.
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 agree it's pretty trivial, but it has regression value for analyzer.Info().
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 tend to agree with @freeformz, though this also falls under the heading of "write code that's easy to delete," so i'm not too bothered.
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 is the call to delete it, then? I'm fine either way
TestAnalyzerInfo removed. Hoping to wrap this one up and move on to integration tests. Let me know if you'd like any more changes. |
I think we're good here. If that issue keeps recurring on my local (it's quite odd), I'll just clean it up directly. |
Moving hard-coded unit test file content into separate files, a la stdlib. The first pass just converts manifest_test.go, in order to get feedback. This PR supercedes #212 for now, as the integration tests were breaking. The unit test content is low-hanging fruit.