-
-
Notifications
You must be signed in to change notification settings - Fork 353
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
test: improve tests for NameFilter class. #3975
test: improve tests for NameFilter class. #3975
Conversation
I have added one additional test to improve the line coverage of the tests for NameFilter class. I would be glad if @slarse or maybe someone else may tell me what should be the scope for a single PR and a commit for improving tests. |
There's no easy answer to that, it depends. Arguably, a PR can't really be too small, so there's really no minimum scope. Somewhat arbitrarily we can say that a maximum scope would be adding tests relating to a single public method. That is to say, don't target multiple pieces of functionality with a single PR. |
This comment has been minimized.
This comment has been minimized.
Fully agree. You can run Descartes every other day, in order to:
|
@Rohitesh-Kumar-Jain See my comment in #3967. For measuring code coverage, using Descartes is incredibly inefficient.
Considering that it takes @Rohitesh-Kumar-Jain 20 hours to run Descartes on Spoon, I wouldn't recommend running it more than once a week. |
Yes it keeps running for 20 hours in the background, my effort is to just run this, but still, I have to wait almost a day to see the new report. Although Descartes can be run with subsets however in this case the reports aren't as expected after targeting the exact class and the test. |
That's most likely because there are other tests that exercise the code you target. A large part of Spoon's test code is not unit tests, but rather run the entire thing from parsing a model to printing a result, and so there can be many tests that touch e.g. |
Thanks for letting me know this |
Hi, I feel this line of code looping in @nharrand |
Hi team, Now there are two options available :
|
As one point of feedback, avoid working on things in parallel that conflict with each other. If either this or #3978 is merged, there will be a merge conflict between them. With that said, this PR needs a little bit of work. When it comes to tests, it's important that each test tests one thing, one concept. That doesn't necessarily mean that each test needs to have precisely one assert, but it does mean that you shouldn't test happy-path behavior (everything works as it should) and crashes in the same test. Having a lot of assertions in the same test is also indicative that it's testing too many concepts. As a rule of thumb, you should only call the method under test once in the test itself. This doesn't always hold (sometimes you specifically want to test what happens if you call it twice, for example), but I find that most often this rule is good. Furthermore, I strongly recommend that you follow the arrange/act/assert pattern (AAA). You can look it up in your preferred search engine. So, in terms of this PR, break out your new assertions into separate test cases and write them according to all of the rules we've discussed, including a contract for the test clearly specifying what it's supposed to test. Note that the original test that you altered here is not a good test, as it does not say what it's actually supposed to verify ("NameFilter is tested and works" is extremely vague). |
I will keep this in mind for future PRs.
I will surely have a look at this, thanks for making me aware of the AAA pattern. |
This PR looks good in the sense that it add new assertions. We don't need to remove |
IMO this is not an inherently good thing, arbitrarily adding new assertions to an existing test is not sustainable. It then becomes unclear what that test is supposed to verify. The original test doesn't have a clear goal (the contract essentially just says "test that name filter is correct"), which doesn't make things better. "God tests" that verify everything about some method or class are a recipe for maintainability degradation. As for the removal of the guard assertion, I agree that it's unrelated to remove. I don't think it should have been there to begin with as it is essentially just clutter, but that's a separate issue. |
yes, it's an engineering tradeoff between adding assertions to an existing test (if the test intention is the same), and creating a new test. |
If the test intention is the same, I'm all for adding a "missing" assertion, in no way do I mean that there should be only one assertion per test. In the test case in this PR, I don't think the test intention is clear, and so wouldn't want to add anything to it. |
OK for me. @Rohitesh-Kumar-Jain is NameFilter in the Descartes report? |
Sorry for the late reply
yes, it is here |
OK, then let's finish this PR :) it's in our plan. Could you document the test intention of the added assertions with a comment line |
I have added contract before each assertion : ) |
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 is a good start. Please see my comments.
I also have some general comments.
Also note that each test should have one contract. The assertions you've added in this test each have their own contract, and so should be separate tests. Yes, there are tests in the test suite which have multiple contracts, but that's not great. One test should test one concept <==> one test should have one contract.
In other words: break out your added assertions into separate tests.
assertEquals(1, elements.size()); | ||
// contract: tests NameFilter constructor for null value case |
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 contract should not say what the test does, but what is expected to happen. For example, this one ought to say contract: NameFilter constructor should throw IllegalArgumentException when passed null
The same problem exists with all of the contracts you have written here, they just say what the test does, while they should detail the expected outcome of some action.
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.
Got it 👍🏼
Hi @slarse, thanks for reviewing PRs : )
Understood 👍🏼 |
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.
Just two things left to fix, final contract seems incorrect and there's a rogue parentheses on one of the @Test
annotations.
|
||
@Test() | ||
public void testNameFilterGetType() { | ||
// contract: getType method returns the object of class NameFilter |
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 contract is not correct, the NameFilter.getType()
is not tested to return a NameFilter
object.
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.
should the contract be like this : getType method returns the type of class which is begin 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.
There's no one answer to what a contract should say, but it cannot be incorrect. What the getType
method actually is supossed to return is the CtNamedElement
class, which is not an object of class NameFilter
, and thus the comment here is explicitly incorrect.
The contract comment should state in human-readable terms what the test is supposed to verify. One potential contract here would be: // contract: NameFilter.getType() should return the CtNamedElement class
A less specific one would be: // contract: NameFilter.getType() should return the correct class
, where the test itself then specifies that correct class.
Note that we don't usually test getters directly, because they are trivial pieces of code, and so the test and its contract here necessarily become a bit contrived.
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.
Note that we don't usually test getters directly, because they are trivial pieces of code
I have one question, so I can skip adding tests for getter like getFactory()
in this class ?
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 one question, so I can skip adding tests for getter like getFactory() in this class ?
Directly testing that would be fairly far down the priority list, yes. Note however that this isn't a pure getter in the sense that it doesn't just return a static value or a field of the class, but actually calls some other method. In that sense, it's not pointless to test it, but it would for example be much more important to thoroughly test spoon.template.Substitution.getFactory
(the method that's delegated to), as that method is more complex. Overall, the Substitution
class looks fairly poorly tested when looking at the Coveralls report, so perhaps that'd be a good target for you!
@@ -111,13 +111,46 @@ public void setup() throws Exception { | |||
|
|||
@Test | |||
public void testNameFilter() throws Exception { | |||
// contract: legacy NameFilter is tested and works | |||
// contract: the constructor of the class NameFilter creates and initialises an object. |
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 contract is also not correct, this test is not for NameFilter
's constructor. The test verifies that NameFilter
finds the expected amount of elements when filtering with it (it's a very weak test).
The original comment was technically correct, it was just pointless. But you don't have to change this contract when adding your other test methods, as you don't touch this test method. I'm guessing this is just left over from when you modified this method.
Either fix this comment such that it's correct, or revert to the original contract.
|
||
@Test | ||
public void testNameFilterGetType() { | ||
// contract: getType method returns the datatype that the NameFilter class returns |
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 truly sorry for going on and on here, but this contract still doesn't make sense to me. It says that a method returns the datatype that a class returns. But a class does not return anything, and so it does not make sense.
It's very important to be able to succinctly specify what a test is supposed to verify, so we need to keep iterating on this until you've nailed it. Feel free to just comment in this comment with suggestions before committing to something.
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 truly sorry for going on and on here
no no, it's my fault that iterations are still going on, had I raised a perfect PR, no iterations would have occurred. I will try to come with a succinct contract.
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 would like to go with the one suggested by you only // contract: NameFilter.getType() should return the CtNamedElement class
@@ -111,13 +111,46 @@ public void setup() throws Exception { | |||
|
|||
@Test | |||
public void testNameFilter() throws Exception { | |||
// contract: legacy NameFilter is tested and works | |||
// contract: NameFilter finds the expected amount of elements when filtering |
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.
Very good!
I am sorry for taking so many iterations, I am going to spend tomorrow reading lots of existing tests of Spoon so that my next PR may require lesser iterations : ) |
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 am sorry for taking so many iterations, I am going to spend tomorrow reading lots of existing tests of Spoon so that my next PR may require lesser iterations : )
Don't be sorry, it's perfectly normal and expected for the first contributions to a project to be take some time :)
@monperrus good for merge
Many thanks @Rohitesh-Kumar-Jain and @slarse |
#1818
This PR aims to improve the code coverage of the class NameFilter.
Link to #3967