public void testShouldRecordSystemException(){
exceptionMonitor.recordSystemException();
assertTrue(exceptionMonitor.getCurrentCount());
}
and this is the code that I was intending to write:
public void recordSystemException() {
storeNewTotal(++currentCount);
}
The problems with this test are:
- exposing internal state and breaking encapsulation with getCurrentCount() without anybody using it. (Yes, I know that I can make it protected instead of private, but this is not good enough for me. Changing private methods to protected in order to be tested is a topic, hugely discussed already in the blogsphere and I don’t have too much to add. I share the opinion that exposing private methods to protected just for testing looks like design smell and I try to avoid it. However, from time to time, I could make a method protected just for testing if this is the most pragmatic thing to do).
- This is pure state based testing. Although, sometimes state based testing is the better solution in many other cases verifying behavior is leading to much better design.
As a result, this small test reminded me about one presentation that I gave in our office about a year ago. The presentation is about “Mocking Abuse” but the final point of that presentation is that there is a middle ground between state base testing and interaction base testing (using mocks) that I call “verifying observable behavior”.
What that means is that this test should be changed to observe the behavior instead of verifying the object state:
public void testShouldRecordSystemException(){
assertEqual(1, exceptionMonitor.recordSystemException());
}
What happened here is that the method recordSystemException() changed its return type from void to int:
public int recordSystemException() {
storeNewTotal(++currentCount);
return currentCount;
}
The first question that comes to mind is how to verify that the method executes correctly because you can pass the test above with implementation like:
public int recordSystemException() {
return 1;
}
This is where the thinking about testing state vs verifying behavior should change. In practice, in order to verify behavior, we perform series of steps. Similarly, in mathematics we have so called “Mathematical induction”. Very simplistically put, a given statement is true for all natural numbers if the statement is true for 1, 2 …. n, and n + 1. Similarly, just one verification for verifying observable behavior is not enough:
public void testShouldRecordSystemException(){
for(int n = 1; n< 5; n++){
assertEquals(n, exceptionMonitor.recordSystemException());
}
}
Now, this test should make me implement the right behavior.
That is not a ground braking technique or something not widely known. This is just a change in the way of thinking about design and testing. What is more, in functional languages like Ruby and Python this kind of thinking is encouraged from the fact that every method (function) has always a return value, which allows for observing the behavior of every method.
So, back to the presentation that I mention earlier, it is about something similar. Generally, in our desire to avoid state based testing (which sometimes leads to poorer design, braking encapsulation and so on) we go to the other extreme with interactive testing using mocks (exposing implementation being one of the negative consequences). I already wrote way back about this development behavior in Stubs or Mocks and State or Behavior.
This presentation is building the case on top of those two blog posts that mocks are not the new silver bullet and there is a middle ground between state based testing and interactive based testing that I call verifying observable behavior.
As usual, any feedbacks are welcome!
I'm guessing that's not the case, though. Does the exceptionMonitor write an exception to a file? Mock the file object and make sure something relevant gets written to it. Does it use a Log object? Mock the log. Does it write to a database? Mock the database. Does it record to something, but you don't know exactly what? Then you should have an interface on top of the various implementations (file, log, db, etc.) which you can then mock.
I like "verifying observable behavior" as a phrase, but I think that the behavior observed should be natural to how the code is actually used, not an artificial observation that is provided only for testing purposes.