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

GEODE-10084: CliFunction should handle all throwable #7395

Merged
merged 7 commits into from
Feb 28, 2022

Conversation

jinmeiliao
Copy link
Member

No description provided.

Copy link
Contributor

@onichols-pivotal onichols-pivotal left a comment

Choose a reason for hiding this comment

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

comment deleted

Copy link
Contributor

@demery-pivotal demery-pivotal left a comment

Choose a reason for hiding this comment

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

The new ImportDataFunctionTest does not test ImportDataFunction at all. It tests CliFunction. So move this test from ImportDataFunctionTest to CliFunctionTest. And change it so that instead of spying the unit under test, it creates a derived class where executeFunction() does nothing but throw.

Then add unit tests for the new and changed functionality in ImportDataFunction.

@jinmeiliao
Copy link
Member Author

The new ImportDataFunctionTest does not test ImportDataFunction at all. It tests CliFunction. So move this test from ImportDataFunctionTest to CliFunctionTest. And change it so that instead of spying the unit under test, it creates a derived class where executeFunction() does nothing but throw.

Then add unit tests for the new and changed functionality in ImportDataFunction.

Oh, yeah, that's true. fixed. I don't have a test for ImporrtDataFunction since I don't know what kind of test would make sense for the little change I made besides the test I already wrote.

Copy link
Contributor

@demery-pivotal demery-pivotal left a comment

Choose a reason for hiding this comment

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

Thanks for moving that test to CliFunction. It fits much better there.

These two problems remain:

  1. Do not spy the thing being tested. CliFunction is designed to be extended. So make the test create a derived class, and make its executeFunction method throw.
  2. Write tests for ImportDataFunction.

Here are some test ideas.

One important change is that ImportDataFunction.executeFunction(…) returns a result, where the old execute(…) did not. Here are some things to test:

  • If the region exists, is the result correct (status code, member name, message)?
  • If the region doesn't exist, does the result correctly describe the problem (status code, member name, message)?

Here are some other things to test, to make sure they did not change:

  • Does executeFunction invoke the callbacks from the snapshot service?
  • Does executeFunction set parallel mode according to arg 3?
  • Does executeFunction tell the snapshot service to load the snapshot with the right parameters?

@jinmeiliao
Copy link
Member Author

Thanks for moving that test to CliFunction. It fits much better there.

These two problems remain:

  1. Do not spy the thing being tested. CliFunction is designed to be extended. So make the test create a derived class, and make its executeFunction method throw.
  2. Write tests for ImportDataFunction.

Here are some test ideas.

One important change is that ImportDataFunction.executeFunction(…) returns a result, where the old execute(…) did not. Here are some things to test:

  • If the region exists, is the result correct (status code, member name, message)?
  • If the region doesn't exist, does the result correctly describe the problem (status code, member name, message)?

Here are some other things to test, to make sure they did not change:

  • Does executeFunction invoke the callbacks from the snapshot service?
  • Does executeFunction set parallel mode according to arg 3?
  • Does executeFunction tell the snapshot service to load the snapshot with the right parameters?

Thanks for the suggestion. More tests added. I didn't change the implementation of that function, only the error handling. so that part is tested.

Copy link
Contributor

@demery-pivotal demery-pivotal left a comment

Choose a reason for hiding this comment

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

Thank you for adding those tests. I like that you tested them through execute() rather than through executeFunction().

This problem remains, and in fact is getting worse:

  • Do not spy the thing being tested.

@jinmeiliao
Copy link
Member Author

Thank you for adding those tests. I like that you tested them through execute() rather than through executeFunction().

This problem remains, and in fact is getting worse:

  • Do not spy the thing being tested.

Oh, you are right. I don't need to use spies.

Copy link
Contributor

@demery-pivotal demery-pivotal left a comment

Choose a reason for hiding this comment

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

Do not mock the thing being tested. CliFunction is designed to be extended. So make the test create a derived class, and make its executeFunction method throw.

I'm honestly astonished that the CliFunction test passes, given that it's testing a mock. My guess is that the mock is behaving like a spy.

@jinmeiliao
Copy link
Member Author

jinmeiliao commented Feb 25, 2022

That was I did originally, I thought you don't want that. So should I use a spy or create a new derived class or just as is?

I did want a derived class. Here's way to do that without mocking or spying the thing being tested:

    function = new CliFunction<Object[]>() {
      @Override
      public CliFunctionResult executeFunction(FunctionContext<Object[]> context) {
        throw new InternalGemFireError("test");
      }
    };

Either that or create an inner class in the test called something like ThrowingCliFunction that's implemented the same way.

Something I just noticed: It would be a good idea to assert that the result contains the exception that was thrown. Like this:

    ArgumentCaptor<CliFunctionResult> captor = ArgumentCaptor.forClass(CliFunctionResult.class);
    verify(resultSender).lastResult(captor.capture());
    CliFunctionResult result = captor.getValue();
    assertThat(result.getResultObject())
        .isSameAs(theThrownException); // Or maybe isEqualTo() the actual instance that was thrown, or isInstanceOf()

If the only reason to change ImportDataFunction was to use it to test CliFunction, that's no longer necessary. (Though it would be a shame to throw those new tests away.)

@jinmeiliao
Copy link
Member Author

Do not mock the thing being tested. CliFunction is designed to be extended. So make the test create a derived class, and make its executeFunction method throw.

I'm honestly astonished that the CliFunction test passes, given that it's testing a mock. My guess is that the mock is behaving like a spy.

yeah, I changed back to use a spy.

Copy link
Contributor

@demery-pivotal demery-pivotal left a comment

Choose a reason for hiding this comment

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

Do not mock or spy the thing being tested.

CliFunction is designed to be extended. So make the test create a subclass, and make the subclass's executeFunction method throw. See below (or my previous comment) for an example of how to do this.

It would be a good idea to assert that the result contains the exception that was thrown by the subclass.

Here's a test that does all of those things. Notice that it tests CliFunction's error handling' without mocking or spying the thing being tested, and without altering an existing implementation subclass in order to test the CliFunction:

@Test  
public void executeShouldSendCliFunctionResultIfExecuteFunctionThrowsError() {  
  Error errorThrownByExecuteFunction = new InternalGemFireError("test");  
 CliFunction<Object[]> function = new CliFunction<Object[]>() {  
    @Override  
 public CliFunctionResult executeFunction(FunctionContext<Object[]> context) {  
      throw errorThrownByExecuteFunction;  
 }  
  };  
  
 function.execute(context);  
  
 ArgumentCaptor<CliFunctionResult> captor = ArgumentCaptor.forClass(CliFunctionResult.class);  
 verify(resultSender).lastResult(captor.capture());  
 CliFunctionResult result = captor.getValue();  
 assertThat(result)  
      .isNotNull();  
 assertThat(result.getResultObject())  
      .isSameAs(errorThrownByExecuteFunction);  
}

If the only reason to change ImportDataFunction was to use it to test CliFunction, then those changes are not necessary. (Though it would be a shame to throw those new tests away.)

@jinmeiliao
Copy link
Member Author

Do not mock or spy the thing being tested.

CliFunction is designed to be extended. So make the test create a subclass, and make the subclass's executeFunction method throw. See below (or my previous comment) for an example of how to do this.

It would be a good idea to assert that the result contains the exception that was thrown by the subclass.

Here's a test that does all of those things. Notice that it tests CliFunction's error handling' without mocking or spying the thing being tested, and without altering an existing implementation subclass in order to test the CliFunction:

@Test  
public void executeShouldSendCliFunctionResultIfExecuteFunctionThrowsError() {  
  Error errorThrownByExecuteFunction = new InternalGemFireError("test");  
 CliFunction<Object[]> function = new CliFunction<Object[]>() {  
    @Override  
 public CliFunctionResult executeFunction(FunctionContext<Object[]> context) {  
      throw errorThrownByExecuteFunction;  
 }  
  };  
  
 function.execute(context);  
  
 ArgumentCaptor<CliFunctionResult> captor = ArgumentCaptor.forClass(CliFunctionResult.class);  
 verify(resultSender).lastResult(captor.capture());  
 CliFunctionResult result = captor.getValue();  
 assertThat(result)  
      .isNotNull();  
 assertThat(result.getResultObject())  
      .isSameAs(errorThrownByExecuteFunction);  
}

If the only reason to change ImportDataFunction was to use it to test CliFunction, then those changes are not necessary. (Though it would be a shame to throw those new tests away.)

I understand it as a theory. But in this particular case, isn't spy a better way or a more readable way to stub out the executeFunction method rather than having to implement an anonymous class to do exactly the same thing? I thought the purpose of spy or mock is to reduce the need for test classes.

@demery-pivotal
Copy link
Contributor

I thought the purpose of spy or mock is to reduce the need for test classes.

The purpose of mocks (and, in very rare cases, spies) is to make it easier for the test to control and observe the collaborators of the thing you're testing.

It's true that code I offered does create some noise. To make it more readable, move the noise into a helper method, and name the helper method to make its purpose in the test clear. So the body could do something like this:

function = cliFunctionThatThrows(theError);

@jinmeiliao
Copy link
Member Author

I thought the purpose of spy or mock is to reduce the need for test classes.

The purpose of mocks (and, in very rare cases, spies) is to make it easier for the test to control and observe the collaborators of the thing you're testing.

It's true that code I offered does create some noise. To make it more readable, move the noise into a helper method, and name the helper method to make its purpose in the test clear. So the body could do something like this:

function = cliFunctionThatThrows(theError);

I don't think I agree in this particular case, but I will quit making my case.

@jinmeiliao jinmeiliao merged commit 41e7a17 into apache:develop Feb 28, 2022
@jinmeiliao jinmeiliao deleted the functionExec branch February 28, 2022 18:09
jinmeiliao added a commit that referenced this pull request Mar 1, 2022
* Update ImportDataFunction to extend CliFunction

(cherry picked from commit 41e7a17)
mhansonp pushed a commit to mhansonp/geode that referenced this pull request Mar 11, 2022
* Update ImportDataFunction to extend CliFunction
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.

4 participants