Why We Test Software
»From the software part of the brain.
My first unit test
I remember my first passing unit test well.
It was written for a lab assignment for an undergraduate computer science course I took during Spring 2000 at Arizona State University, CSE200, an Introduction to Object-Oriented Programming, or something like that. Back then, though, it wasn’t called a unit test.
CSE200 had close to a hundred students every semester with each student responsible for writing about a dozen lab assignments.
As part of your assignment, you had to write a main function which read input from cin (or System.in) and passed along any arguments to the rest of your program for processing. When your program was done processing, it would write the results to cout (or System.out). To test your lab assignment, the tester (aka the QA TA) would run main and pass in arguments to your assignment. The tester would record your actual output and compare it against the expected output, complaining if there was a difference. There were many factors that went into your assignment’s final grade such as documentation, and your code’s overall object-orientyness, but generally, the more complaints recorded by the tester, the lower your grade.
For fun, I’m told, you could ask the professor to run your assignment against the extra credit data set which basically threw all sorts of data (good and bad) at your program.
Students taking this class were forced to make a decision: either they rose to the challenges of writing code to spec, or they dropped the class before the first lab assignment was due.
All code starts out as exploratory
I’d be lying if I said that I could sit down at a computer and write a program that ran perfectly, without making a single mistake. Most engineers can’t do this, nor should they.
When writing new code, most of my time is spent exploring ideas and intentionally proving to myself that the computer is doing what I’m telling it to. Programmers like to call this hacking, as in “I have a cool idea, give me a couple of hours to hack something out.”
A programming language geared towards this kind of exploratory hacking always come with an interactive console sometimes referred to as the REPL1. Languages like Ruby, Python, JavaScript, Erlang, Groovy, and Lisp ship out of the box with a REPL, while languages like C++ and the Java programming languages don’t2.
The REPL serves a single purpose: to allow programmers to freely experiment with their ideas both interactively and iteratively. It’s a forgiving environment. It neither cares if you make mistakes nor requires you to write ceremonious amounts of code in order to prove an idea works. A REPL lets you write and verify that your code works, so it often inspires or serves as the early revision of your program.
Have you ever gotten one of those “ah hah” moments when learning something new? I’m sure you know what I’m talking about. They’re the moments that further your domain knowledge on a subject. The moments that build on each other, eventually bridging the distance between novice and master. Frequently using a REPL to learn a programming language causes “ah hah” moments all over the software part of your brain.
You can think of exploratory hacking as the first step to writing solid code.
Writing solid code
In the <insert your industry here> industry, the most important thing you need to learn is how to communicate ideas effectively. To a software engineer, this means learning how to communicate ideas with your code which–depending on the collective professional experience of you and your team–ultimately means writing tests which communicate and enforce design.
There’s a ridiculously large body of knowledge on the topic of testing, addressing important questions such as:
- Whether you should strive for 100% test coverage
- Which testing framework you should use
- Would Danica Keller code first, or test first
My personal philosophy on testing is simple:
We test software to drive communication between team members, to define and enforce the behavior of software, and to share mental assertions against what might one day eventually be an extremely large body of code.
This means that a team sometimes needs to write a lot of tests, and sometimes it doesn’t. It also means you can get away without tests if you’re single person working on a prototype.
At OpenRain, when a new engineer joins our team, before they start building features, they’re assigned the task of writing tests for an existing project. Periodically, this catches them by surprise and the reaction isn’t always positive. I reassure them that this is not the case:
We’re not telling you to write tests because we think you’re a bad programmer. We’re not trying to torture you. We know you’re smart. You’ve got a long and successful track record of building software, you play four instruments, you teach the Argentine Tango, and you can program in six languages. But we didn’t hire you to work for us, we hired you to work with us and in order to do that effectively, you need to know how the software works. The process of writing tests will help you do just that. It will give you a chance to explore the code, to learn how different pieces relate to each other, and to make your own assertions about the way things work.