One of the articles that most has struck me is one from Martin Fowler, Mocks aren't stubs. He explains the two main different approaches to TDD; classicist and mockist.
One of the main points for me is that using Mocks seems sometimes to be testing implementation details rather than the return result is correct. However using mock allows us to avoid testing the same thing in multiple tests and I find myself writing similar tests for different classes, it feels like I am duplicating code and it just feels wrong.
An example of this can be a converter of composite structure. Imagine we have a Room which is divided in sections which are divided in rows. So to convert a Room to some string format we could have a "RoomToStringConverter" object, which uses a "SectionConverter" which at the same time has a "RowConverter" object.
I would like to test each converter independently and I want to set up what is relevant for each test, without setting up the full data structure.
This means testing a "RoomConverter", would test that the "SectionConverter" is called once per each section. And the result will be the appended result of all this calls. This test should be them implemented using mocks, where an expected number of calls with results are set up.
Testing the "SectionConverter" would be similar to the "RoomConverter". This allows to write my test in the form of:
"Giving that rowConverter returns X and Y, converting a Section would be the result of X + Y".
In the case of classic testing (no mocks) one test would be created per each class. In this example we would have three tests: "testRowConverter", "testSectionConverter", "testRoomConverter". All of them are testing the row conversion. And if the test "testRowConverter" fails the other too will fail as well. This is not necessarily bad since it could be seen as giving us more information. What I don't really like the set up method of a room, which needs to set up a list of sections and then a list of rows for each section, and it feels to be setting too much data for one unit test.
Both[David Astels 2003] and [Lasse Koskela 2007] use a classicist approach. David uses it all through his practical example, instead Lasse uses it at least to explain the first few chapters which make sense since they are introductions to TDD. But I am not sure how he progressed since I am still at the beginning of his book.
On the other side I understand that using mocks relates the test a bit more to the implementation details. In the previous example a possible change on the implementation could be to do all conversion within the "RoomConverter" class. This would break the tests even if the result would be same. It won't occur with a classicist approach.
GOOS (2009) by Steve Freeman and Nat Price, use a mockist approach. In the page 135, they refactor the "AuctionMessageTranslator" class. They create a class called "AuctionEvent", where they move the parsing message responsibility.
However they don't test this class because they argue that the class its protected by unit tests.
Lasse, in his "Template engine" example explained through chapter 2 and 3, creates first the unit test for the most outer class "Template":
And the class TestVariableSegment:
If a new feature arises now like "evaluating a variable with "*2" on the name will return its value concatenated twice". At this point we need to create a test within TestVariableSegment:
variable with name containing "*2" evaluate to its value concatenated twice
Then should we create another test within Template test class to verify this behaviour. I would say no, we shouldn't. The template class it should test the variable evaluation with the same detail as the TestVariableSegment. That's one positive thing of having those micro tests in place, that allow us to test the class directly without testing through its parents.
It'd be interesting to do a kata one with classicist approach and one with mockist approach and see the test that we end up with and how we get there.
Conclusion
There is no one way approach. Using mocks allows you to test each class isolated from the others, but the test is more linked to the implementation details. Therefore in an scenario where setting up the feature is complex using mocks could help a lot. However in an scenario where the test can be written without too much complexity it can be worth to test without mocks asserting on the final result.
On the other side having, finer grainer tests help us to progress in smaller steps, but they can get in the way if we are trying to refactor a class, because each class component is tested.
One of the main points for me is that using Mocks seems sometimes to be testing implementation details rather than the return result is correct. However using mock allows us to avoid testing the same thing in multiple tests and I find myself writing similar tests for different classes, it feels like I am duplicating code and it just feels wrong.
An example of this can be a converter of composite structure. Imagine we have a Room which is divided in sections which are divided in rows. So to convert a Room to some string format we could have a "RoomToStringConverter" object, which uses a "SectionConverter" which at the same time has a "RowConverter" object.
I would like to test each converter independently and I want to set up what is relevant for each test, without setting up the full data structure.
This means testing a "RoomConverter", would test that the "SectionConverter" is called once per each section. And the result will be the appended result of all this calls. This test should be them implemented using mocks, where an expected number of calls with results are set up.
Testing the "SectionConverter" would be similar to the "RoomConverter". This allows to write my test in the form of:
"Giving that rowConverter returns X and Y, converting a Section would be the result of X + Y".
In the case of classic testing (no mocks) one test would be created per each class. In this example we would have three tests: "testRowConverter", "testSectionConverter", "testRoomConverter". All of them are testing the row conversion. And if the test "testRowConverter" fails the other too will fail as well. This is not necessarily bad since it could be seen as giving us more information. What I don't really like the set up method of a room, which needs to set up a list of sections and then a list of rows for each section, and it feels to be setting too much data for one unit test.
Both[David Astels 2003] and [Lasse Koskela 2007] use a classicist approach. David uses it all through his practical example, instead Lasse uses it at least to explain the first few chapters which make sense since they are introductions to TDD. But I am not sure how he progressed since I am still at the beginning of his book.
On the other side I understand that using mocks relates the test a bit more to the implementation details. In the previous example a possible change on the implementation could be to do all conversion within the "RoomConverter" class. This would break the tests even if the result would be same. It won't occur with a classicist approach.
GOOS (2009) by Steve Freeman and Nat Price, use a mockist approach. In the page 135, they refactor the "AuctionMessageTranslator" class. They create a class called "AuctionEvent", where they move the parsing message responsibility.
However they don't test this class because they argue that the class its protected by unit tests.
Lasse, in his "Template engine" example explained through chapter 2 and 3, creates first the unit test for the most outer class "Template":
- test one variable
- test multiple variables
- unknown variables are ignored
- test missing value raises an exception
- variables get processed just once
- empty template renders as empty String
- template with only plain text returns a list with an element containing the plain text
- parsing multiple variables return a list of parsed variables
- parsing template into segment objects
- plain text evaluate as it is
And the class TestVariableSegment:
- variable evaluates to its value
- missing variable raises an exception
If a new feature arises now like "evaluating a variable with "*2" on the name will return its value concatenated twice". At this point we need to create a test within TestVariableSegment:
variable with name containing "*2" evaluate to its value concatenated twice
Then should we create another test within Template test class to verify this behaviour. I would say no, we shouldn't. The template class it should test the variable evaluation with the same detail as the TestVariableSegment. That's one positive thing of having those micro tests in place, that allow us to test the class directly without testing through its parents.
It'd be interesting to do a kata one with classicist approach and one with mockist approach and see the test that we end up with and how we get there.
Conclusion
There is no one way approach. Using mocks allows you to test each class isolated from the others, but the test is more linked to the implementation details. Therefore in an scenario where setting up the feature is complex using mocks could help a lot. However in an scenario where the test can be written without too much complexity it can be worth to test without mocks asserting on the final result.
On the other side having, finer grainer tests help us to progress in smaller steps, but they can get in the way if we are trying to refactor a class, because each class component is tested.
No hay comentarios:
Publicar un comentario