Skip to content
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

Merged
merged 8 commits into from
Jun 11, 2021
Merged

test: improve tests for NameFilter class. #3975

merged 8 commits into from
Jun 11, 2021

Conversation

Rohitesh-Kumar-Jain
Copy link
Contributor

#1818

This PR aims to improve the code coverage of the class NameFilter.

Link to #3967

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

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.

@slarse
Copy link
Collaborator

slarse commented Jun 4, 2021

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.

@Rohitesh-Kumar-Jain

This comment has been minimized.

@monperrus
Copy link
Collaborator

In the meeting @slarse suggested it is not necessary to run Descartes all the tim

Fully agree. You can run Descartes every other day, in order to:

  1. verify that the new merged tests indeed improve the Descartes metrics
  2. select your next test target.

@slarse
Copy link
Collaborator

slarse commented Jun 4, 2021

@Rohitesh-Kumar-Jain See my comment in #3967. For measuring code coverage, using Descartes is incredibly inefficient.

You can run Descartes every other day, in order to:

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.

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

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.

@slarse
Copy link
Collaborator

slarse commented Jun 4, 2021

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. NameFilter without them being called anything relating to Filter.

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

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. NameFilter without them being called anything relating to Filter.

Thanks for letting me know this

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

Hi,

I feel this line of code assertEquals("Foo", foo.getSimpleName()); is redundant in testNameFilter method, as this assertion has nothing to do with NameFilter class, also this assertion has been called for about 11 more times in other 6 files, so I believe removing this line of won't reduce test quality and is redundant ?

looping in @nharrand

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

Hi team,

Now there are two options available :

  1. I have extracted the NameFilterTest form FilterTest in review: test: extract NameFilterTest from FilterTest #3978, so after the extraction is merged I can close this PR and raise a new one for improving the test in the NameFilterTest.
  2. If we don't wish to extract, we can close review: test: extract NameFilterTest from FilterTest #3978, and simply just focus on this PR.

@Rohitesh-Kumar-Jain Rohitesh-Kumar-Jain changed the title WIP: test: improve tests for NameFilter class. review: test: improve tests for NameFilter class. Jun 6, 2021
@slarse
Copy link
Collaborator

slarse commented Jun 7, 2021

Hi @Rohitesh-Kumar-Jain,

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

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

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.

I will keep this in mind for future PRs.

Furthermore, I strongly recommend that you follow the arrange/act/assert pattern (AAA). You can look it up in your preferred search engine.

I will surely have a look at this, thanks for making me aware of the AAA pattern.

@monperrus
Copy link
Collaborator

This PR looks good in the sense that it add new assertions.

We don't need to remove assertEquals("Foo", foo.getSimpleName());, unless it is incorrect, which is not the case here.

@slarse
Copy link
Collaborator

slarse commented Jun 7, 2021

This PR looks good in the sense that it add new assertions.

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.

@Rohitesh-Kumar-Jain Rohitesh-Kumar-Jain changed the title review: test: improve tests for NameFilter class. WIP: test: improve tests for NameFilter class. Jun 7, 2021
@monperrus
Copy link
Collaborator

arbitrarily adding new assertions to an existing test is not sustainable

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.

@slarse
Copy link
Collaborator

slarse commented Jun 7, 2021

arbitrarily adding new assertions to an existing test is not sustainable

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.

@monperrus
Copy link
Collaborator

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?

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

Sorry for the late reply

OK for me. @Rohitesh-Kumar-Jain is NameFilter in the Descartes report?

yes, it is here

Screen Shot 2021-06-08 at 11 54 34-fullpage

@monperrus
Copy link
Collaborator

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 // contract: before each of them? Thanks!

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

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 // contract: before each of them? Thanks!

I have added contract before each assertion : )

Copy link
Collaborator

@slarse slarse left a comment

Choose a reason for hiding this comment

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

Hi @Rohitesh-Kumar-Jain,

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Got it 👍🏼

src/test/java/spoon/test/filters/FilterTest.java Outdated Show resolved Hide resolved
src/test/java/spoon/test/filters/FilterTest.java Outdated Show resolved Hide resolved
src/test/java/spoon/test/filters/FilterTest.java Outdated Show resolved Hide resolved
@Rohitesh-Kumar-Jain
Copy link
Contributor Author

Hi @slarse, thanks for reviewing PRs : )

In other words: break out your added assertions into separate tests.

Understood 👍🏼

@Rohitesh-Kumar-Jain Rohitesh-Kumar-Jain changed the title WIP: test: improve tests for NameFilter class. review: test: improve tests for NameFilter class. Jun 9, 2021
Copy link
Collaborator

@slarse slarse left a 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
Copy link
Collaborator

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.

Copy link
Contributor Author

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 ?

Copy link
Collaborator

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.

Copy link
Contributor Author

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 ?

Copy link
Collaborator

@slarse slarse Jun 12, 2021

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!

src/test/java/spoon/test/filters/FilterTest.java Outdated Show resolved Hide resolved
@@ -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.
Copy link
Collaborator

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

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.

Copy link
Contributor Author

@Rohitesh-Kumar-Jain Rohitesh-Kumar-Jain Jun 10, 2021

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.

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

Choose a reason for hiding this comment

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

Very good!

@Rohitesh-Kumar-Jain
Copy link
Contributor Author

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

Copy link
Collaborator

@slarse slarse left a comment

Choose a reason for hiding this comment

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

@Rohitesh-Kumar-Jain

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

@monperrus monperrus changed the title review: test: improve tests for NameFilter class. test: improve tests for NameFilter class. Jun 11, 2021
@monperrus monperrus merged commit 414b6d6 into INRIA:master Jun 11, 2021
@monperrus
Copy link
Collaborator

Many thanks @Rohitesh-Kumar-Jain and @slarse

@Rohitesh-Kumar-Jain Rohitesh-Kumar-Jain deleted the improve-testnamefilter branch June 11, 2021 07:50
@monperrus monperrus mentioned this pull request Aug 19, 2021
woutersmeenk pushed a commit to woutersmeenk/spoon that referenced this pull request Aug 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants