In the interests of harmony between myself and others, I'm going to elaborate on how I view certain fundamental terms. In all cases, these are my personal views and opinions. As I have done many times in the past, I am more than willing to change my views on any and all of the following items if presented with a compelling argument.
A situation in your project that, on its face, will cause issues at some point. CodeSmells are derived the accumulated experience of the developer community's failed projects. Not all CodeSmells are necessarily bad, but any CodeSmell that is not changed should be documented as to why this CodeSmell is acceptable in this instance. Otherwise, the maintainer (which will be you in 6 months) will wonder why this CodeSmell was not addressed.
This is a situation where a generalized rule fails on certain specific inputs or input-ranges. For example, division is a generalized rule that has a corner-case when the divisor is 0.
My opinion is that only reality contains corner-cases. If your code has a corner-case that isn't in the spec, you have code that isn't necessary to meet the spec. (Or, more commonly, you have a crappy spec, but that just means you need to improve the spec.)
- Specification (aka spec)
This is where the real-world situation(s) that the code in question should handle are described. This includes the various situations that might arise, the algorithms that need to be handled, and what the code can assume about the world. This also includes any performance requirements/constraints, portability requirements/constraints, the framework that will surround this code (exceptions or not, etc), and anything else from a technical nature. Things that are not specified should be labelled as "implementation-dependent".
Leap-years and leap-seconds are part of the spec for a date-handling module. Performance requirements like "This must run in under 1 second" are part of the spec.
In legacy code, the interface may be the implementation, but that does not mean that one is necessarily white-box testing.
The interface is how anything outside the piece of code in question will deal with that piece of code. This is the list of functions/methods/variables/whatever that the user of this code will have available to them. This list should contain what each of these items does. This includes any assumptions, parameters, and outputs. In the case of an exception-based system, this should include what exceptions are thrown, handled, and any other pieces of code this item uses, so that the user can look there for unhandled exceptions.
It is very common to have a private interface as well as a public interface. This is where your private methods are tested. While this is very close to white-box testing, it is arguable that the public interface that you are providing uses a second section of code that is not for public consumption.
- Unit-tests / testsuite
The unit-tests should verify that a given implentation of the interface, as documented, satisfies the specification, as documented.
This is the actual code that does the work. It provides the given interface and satisifies the given specification. It may be optimized, or not.
- Black-box testing
These are tests written solely against the interface to validate the implementation against the specification.
- White-box testing
These are tests written against the implementation, not the specification. I consider white-box tests to be, in general, a CodeSmell. The only white-boxing I feel to be appropriate is mocking, and if mocking is required, that needs to be justified. (The justification may be as simple as "That's the spec.")
- Test-Driven Development (aka TDD)
The practice of writing a test, seeing it fail, writing some code, then seeing the test pass.
Note: TDD is not necessarily white-box testing, as some have proposed. I only have to point to Pugs for a good example of this.
- Coverage statistics
The process of determining which codepoints have been exercised by your testsuite. If there is less than 100% coverage, this is a good place to determine if any of the following are true:
- Your testsuite is incomplete vis-a-vis the specification
- You have code that is not required to meet the specification
- You have code that is untestable
Either way, this is a CodeSmell that needs to be addressed. (Again, the addressing can be very simple.)
My criteria for good software:
- Does it work?
- Can someone else come in, make a change, and be reasonably certain no bugs were introduced?