What Do I Want to Accomplish:
- Does it do what I want?
- Does it do what I want all the time?
- Can I depend on It ?
- Does it document my intent?
Excuses For Not Testing
- "it takes too much time":
- If you wait to begin unit testing until then it will definitely take too long
- It's like trying to clear a couple of acres of land with a lawn mower.
If you start early on when there's just a field of grasses, the job is easy.
If you wait until later, when the field contains thick, gnarled trees and dense,
tangled undergrowth, then the job becomes impossibly difficult.
- takes a partial from the time spend on debugging code, reworking code, isolating a reported bug
- "it's not my job to write tests"
- our job is "to create working code".
What to Test: The Right-BICEP
- Right: Are the results right?
- B: Are all the boundary conditions CORRECT?
- Conformance: Does the value conform to an expected format?
- Orderering: Is the set of values ordered or unordered as appropiate?
- Range: Is the value within reasonable minimum and maximum values?
- Reference: Does the code reference anything external that isn't under direct control of the code itself?
- Existence: Does the value exist (e.g. is not-null, nonzero, present in any set, etc.)?
- Cardinality: Are there exactly enough values?
- Time (absolutely and relative) - Is everything happening in order? At the right time? In time?
- I: Can you check inverse relationship?
- C: Can you cross-check results using other means?
- E: Can you force error conditions to happen?
- P: Are performance characteristics within bounds?
What Else Can Go Wrong?
In good object oriented design, you do not use a raw native type (e.g. an int or Integer) to store a bounded-integer value such as an age, or a compass heading.
Almost any indexing concept ... should be extenesively tested. Here are a few ideas to get you started:
- Start and End index value have the same value
- First is grater than Last
- Index is negative
- Index is greater than allowed
- Count doesn't match actual number of items
If you have to make assumptions abotu the state of the class and the state of other objects
or the global application, then you need to test your code to make sure that it is well-behaved if
those conditions are not met.
[I hate when funkyObject.DoStuff() works only when something else happened before. We should try to write stateless functions, since the name of the function usually does not state when the function works, and when not; basically the object is lying. If the functions are stateful, please make them private, so that the clients don't see them.]
Make sure your method can stand up to nothing.
In most cases, the count of some set of values is only interesting in these 3 cases:
- 0 (Zero)
- 1 (One)
- More than one
[Always prepare/test for multi-threading access of your object.]
[A function name should represent the "happy" case, what the function does, but 90% of the TestCases should specify what the function does when things went wrong]
[The object must always be able to react to any situation]
[Test the boundary conditions make me think of prolog:
define the boundaries, define the "work" case. ]
Properties of Good-Tests: A-TRIP
When fixing any bug, ask yourself the key question:
Could this same kind of problem happen anywhere else?
"Don't expose your privates!" (might be a warning that another class is struggling to emerge)
"All Tests Pass All The Time"
[see resources for the "Pragmatic Unit Testing: Summary"]
[Design for Testability: Think "How am I going to test my software!?", do TDD, and everybody will thank you (including the QA department)]
[Test the class invariants (like a list is always sorted). There is something that we could check for every test scenario.]
[Make the Test code proffessional: refactor, avoid duplicates, create helper classes/functions]
Who is suppossed to check the validity of input data?
"Keep the barbarians out at the gate"