Improve coverage of WorkItemType and WorkItem #240
Conversation
|
So far, the overall (unit+integration) coverage is bumped from 23.6% to 28.3% with the current tests. It may not seem like much but it is tedious. |
|
So far, the overall (unit+integration) coverage is bumped from 23.6% to 33.0% with the current tests. |
Current coverage is 47.67% (diff: 100%)@@ master #240 diff @@
==========================================
Files 32 33 +1
Lines 1199 1204 +5
Methods 0 0
Messages 0 0
Branches 0 0
==========================================
+ Hits 442 574 +132
+ Misses 692 589 -103
+ Partials 65 41 -24
|
| t.Parallel() | ||
| resource.Require(t, resource.UnitTest) | ||
| e := NotFoundError{entity: "foo", ID: "bar"} | ||
| assert.Equal(t, fmt.Sprintf(stNotFoundErrorMsg, e.entity, e.ID), e.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.
Not quite sure what you're testing 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.
@aslakknutsen, I test the Error() method of the NotFoundError struct.
Those tests seemed like low handing fruits.
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 understand that part. But what your verifying is the formatting of the error string message. We have no strict requirement around these messages at this time and they are highly subject to rapid change. Adding test code to this part is only asking for test failure on any minuscule code change moving forward. If you wanted to 'touch' the errors to get coverage, a better strategy would be to test the scenarios/paths where they are used. That probably has more meaning.
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 test the format, yes. But I'm also testing the errors as a unit. And even if we don't have a strict format right now. We might have it in the future and then we have a test for this. I strongly believe in what you said about scenarios and paths. But then I'm doing an integration test. So far I just wanted to test the errors unit. We can still layer tests that go through different packages as we go.
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.
error is not a unit in my world. It has no logic.
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.
Sure it does. It's a struct with functions on it. But no matter what the opinion is, the coverage output sees logic in there. Let's see if we run into troubles when updating the errors and talk about the concerns then, okay?
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.
The concern is already present: The addition of random tests that is purely there to satisfy a coverage report with little real value to the code base and only add a maintenance overhead.
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.
@aslakknutsen Is this your concern for the rest of this PR? Then I have wasted days basically.
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 think in general what we should test is the intent of our code. The exact text of the error messages is not something the world knows or cares about. Therefore it can change without breaking the program. A meaningful test here would be that we we get back a message of >0 length and not nil, that the code doesn't crash for expected inputs, etc.
44af5a7
to
d195513
Compare
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 have some comments/suggestions, but I can live with what's there.
| // DummyEqualer implements the Equaler interface and can be used by tests. | ||
| // Other than that it has not meaning. | ||
| type DummyEqualer struct { |
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 don't understand what we need this struct for.
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.
Seems it's only used for testing, if so, could we move it to one of the "_test.go" files?
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 is used in all the tests from different packages to test the type difference of two Equaler implementations. See here for example.
| @@ -2,6 +2,11 @@ package models | |||
|
|
|||
| import "fmt" | |||
|
|
|||
| const( | |||
| stBadParameterErrorMsg = "Bad value for parameter '%s': '%v'" | |||
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.
Why make a constant for sth that is used exactly once?
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 is used in the error test to ensure the message is correct. This is what @aslakknutsen complained about because it adds maintenance overhead. My hope is that it pays off in the long run.
| ) | ||
|
|
||
| func TestListFieldDefMarshalling(t *testing.T) { | ||
| t.Parallel() | ||
| resource.Require(t, resource.UnitTest) |
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 really unclear on what the concept behind "require" should be. "Database" is a resource we need to execute certain tests. But "UnitTest" is not a resource that can be present or absent. It's more of a label. Is there ever a reason do skip this test? I don't think 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.
So the whole idea was to decide at runtime if a test shall be executed, remember? I know that is a bit strange that a Unit test is a resource. In fact if you look at the Require() implementation you can already see that UnitTest is treated in a special way. But it is not subject of this PR to discuss this implementation. Feel free to open another PR.
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've given some thought to the resource concept. I think we would be better off if we'd call it "precondition". Then resource.Database would become precondition.HAVE_DATABASE and resource.UnitTest would become precondition.RunUnitTest. The resource.Require() function could be renamed to precondition.IsFulfilled().
In total this means:
precondition.IsFulfilled(t, precondition.RunUnitTest)Does this sound better @tsmaeder ? Feel free to create a PR an reference this discussion (#240 (comment)).
| stDuration = SimpleType{KindDuration} | ||
| stURL = SimpleType{KindURL} | ||
| stList = SimpleType{KindList} | ||
| stString = SimpleType{Kind: KindString} |
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 for consistency reasons or are you saying "SimpleType{KindString}" is bad?
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.
To be honest: I'm not sure. Most of the time to be explicit is a good thing in Go. That's probably why I've written it out.
| @@ -46,12 +46,17 @@ func (fieldType ListType) ConvertFromModel(value interface{}) (interface{}, erro | |||
|
|
|||
| type converter func(FieldType, interface{}) (interface{}, error) | |||
|
|
|||
| const ( | |||
| stErrorNotArrayOrSlice = "value %v should be array/slice, but is %s" | |||
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.
Again, I don't think these should be constants if used only once.
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.
See my comment above.
| SimpleType: SimpleType{Kind: KindList}, | ||
| ComponentType: SimpleType{Kind: KindInteger}, | ||
| } | ||
| assert.False(t, a.Equal(c)) |
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.
There should be a case where the two objects are equal:
assert.True(t, a.Equal(c))
assert.True(t, c.Equal(a)) // test for inverse
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 point.
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.
See 7b84ac8
| t.Parallel() | ||
| resource.Require(t, resource.UnitTest) | ||
| e := NotFoundError{entity: "foo", ID: "bar"} | ||
| assert.Equal(t, fmt.Sprintf(stNotFoundErrorMsg, e.entity, e.ID), e.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.
I think in general what we should test is the intent of our code. The exact text of the error messages is not something the world knows or cares about. Therefore it can change without breaking the program. A meaningful test here would be that we we get back a message of >0 length and not nil, that the code doesn't crash for expected inputs, etc.
This follows steps 1 and 2 of the article: "5 simple tips and tricks for writing unit tests in #golang" which can be found here: https://medium.com/@matryer/5-simple-tips-and-tricks-for-writing-unit-tests-in-golang-619653f90742 Shameless quote from the article above to justify this change: > 1. Put your tests in a different package > Go insists that files in the same folder belong to the same package, > that is except for `_test.go` files. Moving your test code out of the > package allows you to write tests as though you were a real user of the > package. You cannot fiddle around with the internals, instead you focus > on the exposed interface and are always thinking about any noise that > you might be adding to your API. > > This frees you up to change the internals however you like without > having to adjust your test code. > > 2. Internal tests go in a different file > If you do need to unit test some internals, create another file with > `_internal_test.go` as the suffix. Internal tests will necessarily be > more brittle than your interface tests — but they’re a great way to > ensure internal components are behaving, and are especially useful if > you do test-driven development.
Also: * removed build tags * properly renamed test files to blackbox and whitebox tests
Some tests were failing due to wrong whitebox assumptions. I'v re-written the initialization of unexported struct members from the package under test to use a New* ctor method.
629202a
to
1358a50
Compare
Click here and select the Files tab to see how much the coverage is increased per directory or file with this PR.
Fixes #216
Fixes #217