Testing Sorely Needed

I’m working on an implementation of the dgemm matrix multiply function for the BLAS interface [1] for a Parallel Computation course. While trying to introduce a feature to take advantage of the processor’s cache hierarchy, I ran into a reoccurring issue in my software development career. This time it bothered me more than usual.

In order to complete the feature, four components would need to be added or changed for the program to function correctly. For someone who isn’t habituated to test their code after incremental changes, four changes is basically nothing. “Watch me perform dependency inversion while thrashing every class in the codebase,” says the programmer who had it coming. [2] But for someone who now expects more from himself as a software developer, this was now uncomfortable.

“If I think this through, map it out on paper, and act cautiously, I can probably get it on my first try,” is what I use to say. I knew better this time, and I was correct. It didn’t work the first time.

The problem isn’t that it didn’t work the first time. The problem was that I had made too many changes at once and now it was impossible to pinpoint which piece was failing just by looking at the result of my Frankenstein-memory-access-matrix-multiply.

Three of the four components were mostly self-contained and should have been unit tested. These were also the most complex changes, so they DEFINITELY should have been unit tested. What were the two obstacles that prevented me from unit testing this code?

First, was inexperience. I should own up to my own shortcomings before criticizing the code of other people. Even though I’ve been using C/C++ longer than any other language, there’s a lot I still need to learn. Just in this codebase alone: make files, extern ‘C’ code, and static inline functions [3] all showed me how little I truly know. Furthermore, I’ve never properly tested C/C++ software before with unit and integration testing. So there was no way I was going to slap a testing framework into this codebase without first receiving many well deserved battle scars.

Second, was code structure. Two things seem almost inescapable in software.

  1. If you don’t start a program with the intention of testing, as the program grows, it will probably get too difficult to ever test.
  2. Even when you start out testing a codebase, as the project grows and becomes more complex, it will eventually also get too difficult to test.

This code is strictly academic, for learning purposes only. They thankfully included some handy debugging functionality to the project, but its creators didn’t imagine people would need to properly test this code. From the complexity of how the code is linked together to the static inline functions that make some important components impossible to access, this is not convenient to test.

I write this the day of my well learned lesson. In the coming weeks, I hope to learn a number of things about software testing in general, testing C/C++, and understanding some of the internals that have given me so much grief today.

References:

  1. http://www.netlib.org/blas/
  2. Yea, I’ve done that. Not proud of it. I got what I deserved.
  3. Static inline functions actually made a lot of sense in this context since we’re trying to optimize the code to be wicked fast. Why declare a C function as static inline?