Skip to content
This repository was archived by the owner on Sep 9, 2020. It is now read-only.

Moving to golden-file pattern for unit tests #215

Merged
merged 14 commits into from
Feb 13, 2017

Conversation

tro3
Copy link
Contributor

@tro3 tro3 commented Feb 1, 2017

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.

@tro3
Copy link
Contributor Author

tro3 commented Feb 1, 2017

Hmm. Having issues with Travis that I don't fully understand. Will remove and try to resubmit after digging.

@tro3 tro3 closed this Feb 1, 2017
@tro3
Copy link
Contributor Author

tro3 commented Feb 1, 2017

Ah - no copyright on test/util.go

@tro3 tro3 reopened this Feb 1, 2017
@tro3
Copy link
Contributor Author

tro3 commented Feb 1, 2017

@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.

Copy link

@freeformz freeformz left a 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()

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough

Copy link
Member

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)

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.

Copy link
Contributor Author

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.

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 {

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.

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().

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.

Copy link
Member

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) {

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.

Copy link
Contributor Author

@tro3 tro3 Feb 3, 2017

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.

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.

Copy link
Member

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 {

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.

Copy link
Contributor Author

@tro3 tro3 Feb 3, 2017

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. :-)

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.

Copy link
Member

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)

Copy link
Member

@sdboyer sdboyer left a 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
Copy link
Member

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
Copy link
Member

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 {
Copy link
Member

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) {
Copy link
Member

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 {
Copy link
Member

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()
Copy link
Member

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) {
Copy link
Member

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.

Copy link
Contributor Author

@tro3 tro3 Feb 3, 2017

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.

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.

Copy link
Contributor Author

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 ==.

@tro3
Copy link
Contributor Author

tro3 commented Feb 3, 2017

@sdboyer @freeformz - Guys, thanks for the comments; learning as I go. :-) (No pun intended.)
I reduced the test file handlers to:

func (h *Helper) GetTestFile(src string) io.ReadCloser
func (h *Helper) GetTestFileString(src string) string
func (h *Helper) TempCopy(dest, src string)

(Turns out that last was needed in analyzer_test.go.) I left the JSON comparison function in for now, but if you confirm you want it gone, it will happily pull it. @sdboyer - no CI flags on these updates, as they are only aimed at the unit tests.

Please let me know your thoughts.

@googlebot
Copy link
Collaborator

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.

@googlebot googlebot added cla: no and removed cla: yes labels Feb 3, 2017
@tro3 tro3 force-pushed the unit-test-golden-files branch from 09397b0 to 54787d1 Compare February 3, 2017 22:20
@googlebot
Copy link
Collaborator

CLAs look good, thanks!

@googlebot googlebot added cla: yes and removed cla: no labels Feb 3, 2017
@tro3
Copy link
Contributor Author

tro3 commented Feb 4, 2017

@sdboyer @freeformz - the AreJSONEqual function has been removed. Let me know if you see any more needed changes. Thanks, guys.

@sdboyer
Copy link
Member

sdboyer commented Feb 6, 2017

Looks pretty good! Thanks for sticking with this. Just...

no CI flags on these updates, as they are only aimed at the unit tests.

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.

@bradleyfalzon
Copy link
Contributor

I believe the convention is to have the testdata directory called testdata not _testdata. I know some files/directories are excluded that begin with _, but I believe this is to avoid editor temporary files. For example, the go project itself has ~48 directories called testdata.

See https://github.com/golang/go/blob/c2bc727f944fe41e1567b8ac1cf306809801bec9/src/cmd/go/alldocs.go#L725-L726

@tro3
Copy link
Contributor Author

tro3 commented Feb 6, 2017

@bradleyfalzon I'm fine either way. I was just using the existing _testdata to start with, but I'm happy to move it over if the Members confirm.

@tro3
Copy link
Contributor Author

tro3 commented Feb 6, 2017

@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?

@sdboyer
Copy link
Member

sdboyer commented Feb 8, 2017

@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.

@tro3
Copy link
Contributor Author

tro3 commented Feb 9, 2017

Okay, I'm on it. I'm also going to rename _testdata to testdata per above and stdlib. That okay?

@sdboyer
Copy link
Member

sdboyer commented Feb 9, 2017

yep, SGTM

@tro3
Copy link
Contributor Author

tro3 commented Feb 9, 2017

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.

@sdboyer
Copy link
Member

sdboyer commented Feb 9, 2017

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)
Copy link

@freeformz freeformz Feb 10, 2017

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.

Copy link
Member

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?

@tro3
Copy link
Contributor Author

tro3 commented Feb 10, 2017 via email

@tro3 tro3 force-pushed the unit-test-golden-files branch from 5401092 to 59437df Compare February 10, 2017 22:25
@tro3
Copy link
Contributor Author

tro3 commented Feb 10, 2017

@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) {
Copy link

@freeformz freeformz Feb 11, 2017

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.

Copy link
Contributor Author

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().

Copy link
Member

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.

Copy link
Contributor Author

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

@tro3
Copy link
Contributor Author

tro3 commented Feb 11, 2017

TestAnalyzerInfo removed. Hoping to wrap this one up and move on to integration tests. Let me know if you'd like any more changes.

@sdboyer
Copy link
Member

sdboyer commented Feb 13, 2017

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.

@sdboyer sdboyer merged commit ef7f681 into golang:master Feb 13, 2017
@tro3 tro3 deleted the unit-test-golden-files branch February 13, 2017 15:44
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants