-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
Java generation: allow non-final generation for classes #11193
Comments
+1 - being able to mock these stubs would make clean unit testing far easier. |
It's possible to mock final classes with Mockito. |
We do not support mocking stubs. They are It caused support issues early in gRPC's life, and so we add |
I am sure improper use of the library can cause support issues, but I think a library should be catering to a broader spectrum of use-cases. Mocking is a an important use case which should be addressed. There are more ways to do that then not making it final e.g. introducing a generated interface that a Stub implements. That, I think, would be an acceptable compromise. |
Mocking without letting using withDeadline() is too much of a toy to consider. And mockers will mess up the Alternative ways to mock services would be fine. The main point is to have in-process transport in the mix. I think the ideal solution here would be "have a blocking service API." That isn't as far away as it once seemed, but probably isn't that close either. |
Put another way: mocking stubs prevents you from using gRPC properly and from knowing you use it improperly. You can blame the heavy use of mocks internally at Google for teaching us this lesson early. In Google, when we upgraded we'd have to fix everyone's broken, garbage, useless tests. So many useless tests, that tested nothing and made us debug impossible-in-production situations. Bad tests provide negative value. We've lived in that alternate universe, and it was a bad one. |
@ejona86 I started a long response to your comments, but decided to try a slower approach here. What kind of mocking are you referring to that includes timeouts? From my perspective, mocking enables PURE UNIT TESTING where the unit is 1 java class (my own class), and mocking enables me to wire up fixed responses to method calles into classes that otherwise might do a lot of work. So the mock just immediately returns a fixed value in one specific test case, enabling me to ensure my code correctly handles that specific return value.
Correct. The point of the mock is to enable my pure unit testing of my code, not to verify how it works with other classes. That's an integration test. |
What we found was that "pure unit testing using gRPC" was not actually testing. If a substantial percentage of Google engineers's tests that mock gRPC didn't actually verify anything useful, then it was simply harmful and not useful for the masses. We have checks in our code to make sure you are using APIs correctly. By using our implementation you get additional assertions for free, for things you weren't likely to check yourself. There's basically no cost to using In-process transport with directExecutor(). You get deterministic results that are fast and reflect reality. As a bonus, if gRPC happens to have a regression, you'll find out when testing instead of production. You don't mock out ArrayList in your "pure unit tests." Don't mock out gRPC's stubs. |
You didn't really respond to me. "Pure unit testing" of MyClassThatUsesGrpc does NOT call GRPC and does NOT verify it is correctly using GRPC. That's INTENTIONAL. What you are talking about is integration testing. Using mocks for integration testing is no-bueno - just don't do it. (see https://github.com/savoirtech/black-box-system-test). As far as a substantial number of your colleagues doing it wrong - that's not your problem, and the solution to prevent that is unfortunately getting in the way of others who are trying to do something that is not only reasonable - it's best practice. That is, thoroughly unit testing their own code.
That's great. And during integration testing, that's wonderful to have. Thank you. During unit testing, that is not important - the integration tests are the ones that will tell me when I've used GRPC incorrectly. The unit tests won't tell me that because they are not integrating with GRPC. |
BTW...
That sounds 100% like grey box tests. How do you pure unit test "using gRPC"? In my mind, that's just not possible. |
Did you write your own "mock gRPC"? |
@ejona86 Hm, the argument that making the classes final helped with the correctness may be flawed. That may have happened due to people acquiring more experience and having better examples for grpc. In my unit test I want to test a specific and small concern - the stub is called with the value I expect - that's all. Having to stand up the receiving end increases cognitive load on both writers and readers of the test. Just imagine that you'd have to start in process servlet container in order to test that HttpServletResponse sends the right data. Sounds horrible, right? |
That is true. However, it's not an ideal solution here. First, Mockito is no the only mock framework. Second, in my experience, Mockito sometimes breaks when using that feature (i.e. tests that were working stop working when enabling the feature that allows mocking of final classes). |
Have you seen our example tests in the helloworld example?
That's all today. And if we needed to add a deadline (which is very common), it'd start breaking. The tests then prevent you from doing things or enabling features that you should. All code starts simple, but it generally doesn't stay that way.
It definitely is our problem. If we break a test in Google, we have to fix it. And even in OSS that is our problem if you can't upgrade to a new version of gRPC because your tests break. You then plead to us to release patches to old releases.
I added the "using gRPC" a bit later. The code under test was using gRPC, and the test was mocking gRPC out. So "using gRPC APIs" may be more apt. |
I am not sure I understand how you mean it will start breaking. It might say the mock didn't expect the |
Well, by default it'd return null and you'd get a NPE. But how would you fix that? stub.withDeadline(1, SECONDS);
stub.someRemoteMethod(req); And We did consider having a mock Channel, so you'd still use the normal stub which is relatively thin. But that's essentially what in-process transport is, and in-process has the added benefit that it is production-worthy/tested. You can still use mocks, but you mock the service. |
The challenges with mocking are more generic and not specific to gRPC. Back to this topic: I don't see a problem with stub classes being final, as it's still possible to mock. |
I think
Hm, I don't see the bug that slipped in here due to our mocking above. Can you explain? |
|
Edit: I see, returning a new instance for Perhaps that [the api] is the root of the problem with the observed failures to write proper tests? |
As I mentioned earlier, making sure the code-under-test is correctly using the gRPC APIs is NOT the intent of the test. That problem will get caught later in integration testing. The POINT of a pure unit test IS to verify that the code works the way the developer intened, with or without the developer making mistakes in using other libraries. SO, the scenario of not correctly using the return result of the
Do you mean that you/your team are/is responsible to fix tests that you don't write for code you don't maintain??? |
BTW, my 2-cents on the Builder Pattern - builder patterns are kinda handy, but often super ugly - because they "pretend to be" a langauge construct when they are not, and the Java language itself does not provide great tooling to write them. We just have to take the good with the bad when using them. |
I think a lot of builder patterns use an explict For methods that change state, returning new instances is the correct way to build immutable objects. |
So first, that stated reason for
Second, not all mocking frameworks will work with final classes. And third, given "1st", why escalate the "war" in the first place? Now if there is a desire to stay with making stubs final (yuck) then perhaps a good way to support a community that does things differently from your teams/company, an option on the code generator for those of use conviced we don't want final classes and put all kinds of warnings, "WE DON'T RECCOMMEND YOU USE THIS". Also, perhaps there is a chance this discussion will lead to learning and coming to common understanding. That's always a good thing. |
If using Gradle the generated files can be easily modified before compilation. tasks.named('generateProto').configure {
doLast {
// modify generated files here
}
} |
@ejona86 thanks for your time and explanation. I now understand that |
Allow mocking stubs - don't make them final
I'd love to be able to mock stubs
@artnaseef
The text was updated successfully, but these errors were encountered: