Re: Are Tests First Class Clients?
Posted: Jan 26, 2005 12:03 PM
There are really two questions here:
1. Should tests be first class citizens, in general?
2. Is that principle still valid if it causes private
data/methods to become more visible when, in theory
at least, they shouldn't be?
The answer to #1, I believe, is a resounding "Yes!"
I'll get to why, in a moment. As to #2, I believe that
Malcom Sparks made a most astute observation in his post when he said that package-protected is a reasonable approximation of private, in the sense that it does not permit "external" access, where external means "outside the package".
It makes sense that someone testing the package may want to access functions that aren't visible to external users. By the same token, developers who extend the package may find those functions useful when doing so. (It might even be plausible to say that, as a general rule, any function which is sufficiently granular to be testable is *likely* to useful when extending the package someone, sometime, for purposes that weren't dreamt of in the original design.
Now then, to return to question #1, when testing externally available APIs, I believe that testing is not only a first class citizen, but KING of the API. I say this out of experience as both an API designer and as a user.
When I'm designing an API, I think in terms of design consistency. I frequently use terminology that relates to the architecture and I'll do things that, at the time, seem to me to be effective and efficient.
But when I'm *using* the API, I'm not thinking about the internal architecture. I'm trying to recall from memory what the name of the function is, what arguments it takes, and what order they're in. In the process, I'll frequently "guess wrong"--the name that makes sense to me as a user of the API may not have been the name I chose as a designer. Similarly, the arguments I want to supply as a user may not be supported, or the "natural order" may be different from the one I designed, because other operations I'm doing have a different order, or because the code just naturally wants to generate item a, and then item b, so it just seems intrinsically logical to invoke "function(a,b)" rather than "function(b,a)".
Those kinds of thoughts invariably occur to me when I'm using an API, or testing it, and they frequently run counter to the ideas I had when designing it.
When that kind of discrepancy occurs--especially if it happens more than once--I invariably change the API to match my expectations as a user. (It helps that I have a poor memory. I can't remember what I coded last week, much less last month. That makes it easy to "become a user".)
I think of the world in terms of "user interface". With that perspective, an API is an interface to a library, a programming language is an interface to the computer's operations. For me, the user is king, always. When I'm designing an API, I'm frequently too caught up in the "how" (how I'm going to make things work) to keep my eye on the simple "what" (what it's supposed to do).
Testing an API can bring such oversights to light--and in my view, those are "API bugs" every bit as much as a functional faux pas.