Sunday, February 23, 2003

Abstract Unit Tests

I WANT TO UNIT TEST Requirements, Specifications, Use Cases, etc, and not just fully coded Implementations!

I believe that there needs to be a language that allows sufficiently abstract logic to be specified/programmed such that tests can be written against use cases, high level specifications, etc that are very general. An example of a high level use case might be "customer can get account balance from the ATM".

In other words, I'd like to write, compile, and lock away in a test suite, a test that verifies that if joeBlow has $10 in his account, the result he gets from requesting his balance is $10, even though no user interface, extra details like logging in first, etc have been decided.
Then later refinements of the system details can be "plugged in" such that the test can still run without being rewritten. Traditionally, tests must be written at a level of detail that is very specific such that it can interact with the actual system being tested, user interface screens, UI testing tools, etc.

On the other hand, "high level tests" have traditionally been written as high level test plans in (possibly structured) English; just as the use case descriptions themselves were in English.  A language is needed that can capture this logic (in either spec or test-case form) that is precise enough to be "compilable", and have a robust enough notion of "interfaces" that future detailed implementations can be passed at test runtime as "implementors of those abstract interfaces".

E.G. If test cases can take the system-being-tested itself as an explicit parameter (even though the system is normally the ultimate of implicit parameters), that explicit parameter's type is an abstract interface, and the system-being-tested is defined as implementing that interface. [Ed. note: the rest of the world will later get this idea and call it dependency-injection!]
The abstract interfaces must be able to be associated with the more specific interfaces that make up the various levels of abstraction that are captured as the system design is fleshed out.

So, at the very high level (described in the use case above), the interface provides a simple "get balance" request that returns a "number".  Eventually, the actual system has a whole sequence of interaction required to login, navigate to account, request balance, receive formatted display, logout which is encapsulated in a test case defined at that level of abstraction but which none the less can "implement" the getBalance() interface and return the dollar formatted amount as a "number".  So, the specific test case implementation "subclasses" the abstract test case implementation.

QUESTION: when (i.e. at what level of abstraction) does the "interface" analogy get replaced by the "subclass" analogy?
ANSWER: silly rabbit, each level of abstraction is really a framework that defines interfaces for the more detailed level(s) to implement, and defines "subclasses" to implement the interfaces defined by the framework(s) of the higher level(s) of abstraction.  [Ed. note: don't confuse frameworks with implementation-via-subclassing.  They can use other mechanisms than subclassing to override generic default behavior with specific behavior.]

No comments: