by Miško Hevery
I think of bugs as being classified into three fundamental kinds of bugs.
- Logical: Logical bug is the most common and classical “bug.” This is your “if”s, “loop”s, and other logic in your code. It is by far the most common kind of bug in an application. (Think: it does the wrong thing)
- Wiring: Wiring bug is when two different objects are miswired. For example wiring the first-name to the last-name field. It could also mean that the output of one object is not what the input of the next object expects. (Think: Data gets clobbered in process to where it is needed.)
- Rendering: Rendering bug is when the output (typical some UI or a report) does not look right. The key here is that it takes a human to determine what “right” is. (Think: it “looks” wrong)
NOTE: A word of caution. Some developers think that since they are building UI everything is a rendering bug! A rendering bug would be that the button text overlaps with the button border. If you click the button and the wrong thing happens than it is either because you wired it wrong (wiring problem) or your logic is wrong (a logical bug). Rendering bugs are rare.
Typical Application Distribution (without Testability in Mind)
The first thing to notice about these three bug types is that the probability is not evenly distributed. Not only is the probability not even, but the cost of finding and fixing them is different. (I am sure you know this from experience). My experience from building web-apps tells me that the Logical bugs are by far the most common, followed by wiring and finally rendering bugs.
Cost of Finding the Bug
Logical bugs are notoriously hard to find. This is because they only show up when the right set of input conditions are present and finding that magical set of inputs or reproducing it tends to be hard. On the other hand wiring bugs are much easier to spot since the wiring of the application is mostly fixed. So if you made a wiring error, it will show up every time you execute that code, for the most part independent of input conditions. Finally, the rendering bugs are the easiest. You simply look at the page and quickly spot that something “looks” off.
Cost of Fixing the Bug
Our experience also tells us how hard it is to fix things. A logical bug is hard to fix, since you need to understand all of the code paths before you know what is wrong and can create a solution. Once the solution is created, it is really hard to be sure that we did not break the existing functionality. Wiring problems are much simpler, since they either manifest themselves with an exception or data in wrong location. Finally rendering bugs are easy since you “look” at the page and immediately know what went wrong and how to fix it. The reason it is easy to fix is that we design our application knowing that rendering will be something which will be constantly changing.
Logical | Wiring | Rendering | |
Probability of Occurrence | High | Medium | Low |
Difficulty of Discovering | Difficult | Easy | Trivial |
Cost of Fixing | High Cost | Medium | Low |
How does testability change the distribution?
It turns out that testable code has effect on the distribution of the bugs. Testable code needs:
- Clear separation between classes (Testable Seems) –> clear separation between classes makes it less likely that a wiring problem is introduced. Also, less code per class lowers the probability of logical bug.
- Dependency Injection –> makes wiring explicit (unlike singletons, globals or service locators).
- Clear separation of Logic from Wiring –> by having wiring in a single place it is easier to verify.
The result of all of this is that the number of wiring bugs are significantly reduced. (So as a percentage we gain Logical Bugs. However total number of bugs is decreased.)
The interesting thing to notice is that you can get benefit from testable code without writing any tests. Testable code is better code! (When I hear people say that they sacrificed “good” code for testability, I know that they don’t really understand testable-code.)
We Like writing Unit-Tests
Unit-tests give you greatest bang for the buck. A unit test focuses on the most common bugs, hardest to track down and hardest to fix. And a unit-test forces you to write testable code which indirectly helps with wiring bugs. As a result when writing automated tests for your application we want to overwhelmingly focus on unit test. Unit-tests are tests which focus on the logic and focus on one class/method at a time.
- Unit-tests focus on the logical bugs. Unit tests focus on your “if”s and “loop”s, a Focused unit-test does not directly check the wiring. (and certainly not rendering)
- Unit-test are focused on a single CUT (class-under-test). This is important, since you want to make sure that unit-tests will not get in the way of future refactoring. Unit-tests should HELP refactoring not PREVENT refactorings. (Again, when I hear people say that tests prevent refactorings, I know that they have not understood what unit-tests are)
- Unit-tests do not directly prove that wiring is OK. They do so only indirectly by forcing you to write more testable code.
- Functional tests verify wiring, however there is a trade-off. You “may” have hard time refactoring if you have too many functional test OR, if you mix functional and logical tests.
Managing Your Bugs
I like to think of tests as bug management. (with the goal of bug free) Not all types of errors are equally likley, therefore I pick my battles of which tests I focus on. I find that I love unit-tests. But they need to be focused! Once a test starts testing a lot of classes in a single pass I may enjoy high coverage, but it is really hard to figure out what is going on when the test is red. It also may hinder refactorings. I tend to go very easy on Functional tests. A single test to prove that things are wired together is good enough to me.
I find that a lot of people claim that they write unit-tests, but upon closer inspection it is a mix of functional (wiring) and unit (logic) test. This happens becuase people wirte tests after code, and therefore the code is not testable. Hard to test code tends to create mockeries. (A mockery is a test which has lots of mocks, and mocks returning other mocks in order to execute the desired code) The result of a mockery is that you prove little. Your test is too high level to assert anything of interest on method level. These tests are too intimate with implementation ( the intimace comes from too many mocked interactions) making any refactorings very painful.
10 responses so far ↓
Hi Misko,
Very nice post. You have given a clear analysis of the types of bugs and the benefits of testing.
Interfacing with hard-to-test third-party code // Jan 4, 2009 at 12:29 pm
[...] is not so simple, and in most cases I just don’t bother testing it since there can only be wiring bug in that code. There should be no application logic in the LoginServlet since we have moved all of [...]
Hi, I would add two more categories of bugs, but which are not totally code related:Of Understanding: – code does wrong thing may be the right way.Of Omission – code does right things, but not all of them. For this two categories, unfortunately, unit tests does not help.Here the communication, walkthroghs and acceptance tests are of value.
Software Testing Categorization // Jul 14, 2009 at 12:35 pm
[...] ‘ifs’ and ‘loops’. This is where the majority of your bugs come from (see theory of bugs). Which is why if you do no other testing, unit tests are the best bang for your buck! Unit tests, [...]
Great post and excellent points about “Testable code is better code” and “Unit-tests should HELP refactoring not PREVENT refactorings”. Everyone should understand that in first place
What about rendering bugs at web applications? For example your a writing code at javascript, which generates complex UI, which should be adapted for all versions of browsers, but you saw that something looks bad. The same for CSS. Are these rendering bugs? Sometimes it’s harder to fix, then logical bugs.
@Mazurov,
JavaScript can be tested with JS unit test frameworks such as http://code.google.com/p/js-test-driver/, so that is a normal logical/wiring problem. CSS is definitely a rendering problem, and we do not have a good solution for that.
Swiss army knife for XP PHP projects (defining code quality) | Alternate Illusion // Mar 25, 2010 at 10:49 am
[...] @misko.hevery.com [...]
How to Flow Chart, Part 1: Principles | themanthursday.com // Apr 2, 2010 at 10:01 pm
[...] approach. But as a programmer, you’ll also use flow charting to conquer the dreaded logical bug. (I guess a really smart application of flow charting would be in pre-development, allowing you [...]
Thanks. Nice post.
Leave a Comment