“You'll mistakenly test implementation instead of behavior.” I have to be a don’t knower here - could you explain what this implementation vs behavior distinction is? I have heard you mention both of these terms often in your streams, and I just get confused every time I hear the word behavior.
As you know, the same solution can be implemented many different ways.
Testing behavior is when the tests express the acceptance criteria for the given feature.
Testing implementation is when the tests express *this one* particular implementation and its details.
This isn't binary: right or wrong. It's two *non-overlapping* spectrums:
- how well the tests express behavior in the language of the customer/user (HIGH is good)
- how much implementation detail is being expressed in the tests (LOW is good)
I'm showing it on Thursday's stream, this is a hard problem, so don't worry if it doesn't land right away. I struggle with it too at times. A quick answer I can give you now is this distinction:
Your software feature has two sides: a problem it is solving and the code that was implemented to solve that problem.
To try and be very very blunt: my method for adding two numbers could be giving the correct result with regards to how this method is implemented, BUT with regards to what the actual feature should do, the results of this method might not be totally in line with the client’s requirements.
Because the solution of A+B does not require more design than the line of code already expresses, it's hard to express using such a simple example.
Here's an alternative approach.
Consider a simple API that returns a response in string format or some kind of JSON via DTO conversion. Let's say you're querying all materials used in a particular area of a construction site.
-it can be nullable
-handles situations of invalid construction site area id
-handles authorization
-handles situations where no materials are being used
-expresses special cases such as same material used in different configurations
There are many ways to express this level of detail in an API.
You can return an iterator, an object with nullable fields, an array of string, an object with arrays of strings, an array of objects, a list of arrays of objects, a map, an collection of material derivates, etc.
What matters is that I am using this API to display these materials for some purpose, ie. listing on a purveyor's list of necessary goods.
If my tests express language, behavior and coupling towards the purveyor's intent and something closer to the stringly typed response or abstracted DTOs, then I have very little knowledge of internals. This is closer to the "good example".
However, if I have tests that show intimate knowledge of internal entities and mocks from their repositories that inject concrete objects into query functions and simulate lists of a given number then the tests are much more structure-sensitive. This would be a "bad example"
Denis, first off, thanks for taking this much trouble to explain this. I believe I get the point now. Of course, I will need to try and write such tests and see how I do,m.
Im reading everything again, and it seems to me that encapsulation is important here. Even though the same person is writing both the code and the test, this person should write the test as if he/she has no knowledge about the internals of the api/method, and thus not only will the behavior be tested but the test itself should be resilient to changes of api internals. This now spawns a dozen other questions, but it’s best to pause for now.
“You'll mistakenly test implementation instead of behavior.” I have to be a don’t knower here - could you explain what this implementation vs behavior distinction is? I have heard you mention both of these terms often in your streams, and I just get confused every time I hear the word behavior.
As you know, the same solution can be implemented many different ways.
Testing behavior is when the tests express the acceptance criteria for the given feature.
Testing implementation is when the tests express *this one* particular implementation and its details.
This isn't binary: right or wrong. It's two *non-overlapping* spectrums:
- how well the tests express behavior in the language of the customer/user (HIGH is good)
- how much implementation detail is being expressed in the tests (LOW is good)
I'm showing it on Thursday's stream, this is a hard problem, so don't worry if it doesn't land right away. I struggle with it too at times. A quick answer I can give you now is this distinction:
Your software feature has two sides: a problem it is solving and the code that was implemented to solve that problem.
To try and be very very blunt: my method for adding two numbers could be giving the correct result with regards to how this method is implemented, BUT with regards to what the actual feature should do, the results of this method might not be totally in line with the client’s requirements.
…Am I approaching the intended idea?
Because the solution of A+B does not require more design than the line of code already expresses, it's hard to express using such a simple example.
Here's an alternative approach.
Consider a simple API that returns a response in string format or some kind of JSON via DTO conversion. Let's say you're querying all materials used in a particular area of a construction site.
-it can be nullable
-handles situations of invalid construction site area id
-handles authorization
-handles situations where no materials are being used
-expresses special cases such as same material used in different configurations
There are many ways to express this level of detail in an API.
You can return an iterator, an object with nullable fields, an array of string, an object with arrays of strings, an array of objects, a list of arrays of objects, a map, an collection of material derivates, etc.
What matters is that I am using this API to display these materials for some purpose, ie. listing on a purveyor's list of necessary goods.
If my tests express language, behavior and coupling towards the purveyor's intent and something closer to the stringly typed response or abstracted DTOs, then I have very little knowledge of internals. This is closer to the "good example".
However, if I have tests that show intimate knowledge of internal entities and mocks from their repositories that inject concrete objects into query functions and simulate lists of a given number then the tests are much more structure-sensitive. This would be a "bad example"
Denis, first off, thanks for taking this much trouble to explain this. I believe I get the point now. Of course, I will need to try and write such tests and see how I do,m.
Im reading everything again, and it seems to me that encapsulation is important here. Even though the same person is writing both the code and the test, this person should write the test as if he/she has no knowledge about the internals of the api/method, and thus not only will the behavior be tested but the test itself should be resilient to changes of api internals. This now spawns a dozen other questions, but it’s best to pause for now.