Sufficiency In Software Testing
When I was finishing up my B.S. I took a class in embedded software testing. The big assignment was to write the software that controls a single elevator, test the software to our satisfaction and deliver the whole shebang at the end of the semester. The critical lesson I learned from the course was not that the elevator software was difficult to write, but that there are an infinite number of odd and unfortunate events that could happen to any component involved, at any time, and there is no way to declare with 100% confidence that you have accounted for all possible defects.
So most software is not about perfection, but sufficiency. Everyones wants ultra-high quality, defect free wares, but at some point you must put down the keyboard and declare the product “sufficient” for release. Key problems: “How do you know when you’ve done enough testing?” And just as important, “When is the right time to test?”
This topic has been a open talking point at OpenRain. Marc is a strong proponent of many TDD/BDD principles and goes knife-throwing-freak-show when stuff isn’t well covered. (Ed. note: possible slight exaggeration… maybe.) I am also highly concerned with sufficient tests, but prefer a incremental approach and am wary to invest too much effort in automated tests up front for several key reasons.
- While development is underway, you incur unnecessary overhead to maintain tests developed before design stabilization. This overhead is inevitable during long-term maintenance, but the last thing I want to do on the project I started yesterday is refactor all my tests because I dropped a single column from the “users” table.
- When inexperienced developers write tests too early, they oft end up testing the dummy data and underlying framework, not your design. It is not our job as application-level developers to write test cases for all underlying dependencies, but since that’s all you have at the beginning of a project, it’s easy to waste time here.
- The benefits of writing tests first to flush out design details is diminished in dynamic languages. In Java, writing a quick block of pseudo-code to use your interface is a great way to explore your design from an “external” perspective. Once you’ve achieved design clarity, you can easily use your compiler errors to create correct interfaces. Dynamic languages such as Ruby, however, do not offer this compile-time help, lowering the benefit of the technique.
- There’s no freaking way we’re checking in code that doesn’t compile. Sorry, but if I’m writing a Java unit test, there’s no way I’m putting up with 800 compiler errors (and no autocomplete) over the next day while I generate all my stubs. I don’t care if TDD says otherwise; it’s a stupid practice for statically typed languages.
Granted, if any of our systems crash, we probably aren’t going to irreparably harm anything except for my phone that goes flying across the room for ringing at 5AM, but we still have the issue of “sufficiency”. For OpenRain’s Rails-based applications, I’ve been using the following philosophies on a personal level.
- Models tests should be implemented first and as soon as possible. Validation logic and other constraints should be verified up front, as key bugs here will likely effect other code. Add sample data as necessary.
- Only functional/integration tests for core use cases should be done early. Adding too many upfront tests to the yet-to-stabilize design tends to add maintenance liability before it’s able to pay itself off.
- Tests for non-core features should be tested shortly after a brief “breathing” period, wherein others can comment on the design/code before you’re fully committed to it. Don’t waste your time with a massive test suite until people stop telling you it sucks.
- Avoid complex methods of testing. Multi-threaded and singleton-based designs have inherent testing complexities, and should be designed out if possible.
- Aim for 100% coverage in dynamic languages. Otherwise you won’t catch retarded bugs like syntax errors.
- Have all known, likely and anticipated issues resulting in a significantly negative state covered by an automated case. This is, perhaps, the crux of my “sufficiency” perspective. You must have some mental benchmark that determines when you are “done”. This does not imply that all issues are resolved, only that they are tracked and, hopefully, all the significant ones are fixed.
I’d love to hear your thoughts on practical testing philosophy. Please let me know what you think!
Tags: methodology, opinion, software, testing
Software Engineering Curse Words

Here lie terms frequently used in software development which I don’t particularly care for.
Programmer
Commercial software is as much about programming as building bridges is about installing steel I-beams. Writing actual code is only part of the engineering effort. When I see a job posting entitled “Java Programmer” I usually suspect that this is either (1) a low-level monkey position and/or (2) the person behind the post doesn’t really understand the scope of typical developer work.
Developers are required–much unlike the mechanical nature of an assembly line worker–to make decisions and assumptions about the external purpose and internal nature of their work, often part of a seemingly ingrokable ecosystem. Unless you have a micro managing boss or a heavy-weight process itemizing every last byte of work, you must personally exercise critical thinking, time management and interpersonal skills to balance your never ending stream of unclear and incompletely stated priorities. Being a successful programmer thus requires much more than programming knowledge.
The Point: The term “programmer” in an inaccurate trivialization of the real job. I prefer “Software Engineer” formally and “developer” in colloquial usage.
Senior
For HR purposes, “Senior” is a nice way of labeling someone as having a bit more knowledge, responsibility, general weight, and more income than a non-senior person. The problem is that both senior and non-senior developers tend to have very similar job duties; so aside from income, the criteria of senior personnel are inherently qualitative, subjective, relative to a particular domain (read: not necessarily guaranteed to transfer being projects), and/or effectively indistinguishable from non-senior status.
The effect is that, in a matrix organization, a new project may end up with n00bs who are senior, experts who are junior, and a pay structure which reflects an old project now completely irrelevant to the current situation. Senior and non-senior developers often work together as peers, and everybody quickly figures out who the real leaders are. And that’s frequently very different from the formal structure and correlating pay grade.
The Point: “Senior” tells me that you’re expecting to make more and are probably good at something, which may or may not be relevant to me. It’s not a global implication of elevated wisdom.
Architect
Most “software architects” I’ve met do far more operational and project management than architectural design work. This isn’t to say that they don’t or aren’t capable of making significant design contributions to the project, but that all the overhead of email and meetings between business/team/customer/whomever members sucks up so much time that lower level engineers have to either make the design decisions for the architect or block indefinitely as the architect plays Inbox-fu.
The Point: If you’re an “architect” who doesn’t have time to sit down with the engineers and talk about design, you’re really a technical manager who needs to officially delegate the design work to avoid becoming a bottleneck for the team.
Resource
I shudder whenever I hear or use this word, usually in a managerial, Mythical Man Monthian context trying to quantize everyone into tiny cube shaped units. I find it so important to account for individual character when planning and estimating that I consciously use the word “people” instead of “resources”; it’s a simple trick to force yourself into remembering the undeniable human individuality of the worker bee.
The Point: People aren’t Legos, so let’s not pretend they are.
Tags: methodology, opinion, software
Singletons Cause Cancer
It’s been said before. I’ll say it again. The singleton pattern sucks. From a pragmatic point of view, it has two primary drawbacks: reuse and testability.
Reuse
A public static getInstance() method is, by definition, statically bound at compile time. Since you can’t override static methods, reusing singleton code via inheritance means you’ll need to create a new getInstance2() method. No matter how creative you get with this method name, you have to accept that users of your code will periodically call the parent types public getInstance() method instead of your spiffy new getInstance2(). Working off an interface largely becomes a moot point since the developer must know the exact type of singleton they want to use at compile time in order to invoke the correct getInstance() method.
How do you configure a singleton without a parameter to getInstance(), which would not be consistent with the intentions of the pattern? Since the instance is constructed internally using a non-publically-accessible constructor, there isn’t a convenient way of introducing configuration information before it’s created.. unless the singleton is aware of a configuration source at compile time with yet more static binding. This makes the code very inflexible, as developers intending to reuse it will be at the mercy of your pre-chosen configuration mechanism, which may not be appropriate for their circumstances, or even unit testing.
Testability
Unit tests generally require control over the lifecycle of the class under test to fully validate proper state transition and contractual validity. Since you, the master of the known universe, are writing the software, you’ll certainly write negative scenarios into your unit tests to assert proper failure handling. If intentionally introducing a negative test results in an irrecoverable state, how do you throw out the singleton and start the next case with a new one? You can’t. What if your test case is creating a tricky concurrency scenario emulating multiple systems within the sandbox of a single JVM? You can’t (trivially). What happens when you discover you need multiple instances of the singleton within your application? You can’t. Time to refactor.
Additionally, unit testing of code using static singleton dependencies has a high potential of awkwardness due to an inability to swap out implementations for mock objects. Under the principle of designing for testability, quality and maintainability, hackishness is not a quality to aspire to.
Conclusion
Singletons can be hazardous to your health, seriously jeopardize your family’s safety, and have been classified as ‘terrorist patterns’ by the U.S. government. The fact that an application only needs one instance of something does not mean the object should be designed that way, and there aren’t very many scenarios where singletons are appropriate. Do as the Jedi do and use them with consideration and responsibly.
Tags: architecture, design, pattern, rant, singleton, software



