In the Programming By Stealth podcast I host with Bart Busschots as the instructor, we’ve covered a lot of different programming concepts. One concept we covered twice is Test Driven Development. We covered it twice because the tool that was available the first time we covered it (QUnit) was far too cumbersome, and when a tool called Jest became widely popular he covered it again.
Quite naturally, you may be wondering why I’m bringing up a programming topic outside of Programming By Stealth. I wanted to tell you a story of what happened when Helma from The Netherlands came to visit Steve and me, and it has to do with Test Driven Development.
When Helma told me she was coming to visit us for about a day and a half on her way to an origami convention in San Francisco, I started asking her what she would like to do while in the Los Angeles area. We have such a broad set of cool things to do and I wasn’t sure what she might want to do.
We could go to the Getty Museum which is up high above the city giving extraordinary views of the coastline (and I hear there’s art inside.) We could hike to the Hollywood sign, which is a tradition for us specifically with people from other countries. We could go down to the beach for a walk and talk. We could go out to a cool restaurant. The Museum of Modern Art right next to the Disney Hall is pretty nifty to look at. We could drive up into the city of Palos Verdes estates and look at multi-tens-of-million dollar homes.
Guess what she said she wanted to do? She asked kind of apologetically, “Could we stay home and program together?” Why yes, Helma, we can do that and I’d love it!
You may not remember, but Helma is the third listed author of the book we published called Taming the Terminal based on the podcast series by that name. Bart had written all of the blog posts as tutorials for the series, I produced the podcast and I was the invaluable stooge in the front row asking questions, but it was Helma who figured out how to programmatically turn this set of blog posts and audio files into an actual book. She’s amazing.
Over the years we’ve scheduled many a play date over the interwebs where Helma helps me with my Programming By Stealth homework. Programming is the basis for our friendship so why wouldn’t we do it when she was here?
I did drag her down to the beach for walks both days, but after some healthy exercise and a couple of cups of coffee, we sat in the kitchen and coded for hours and hours. It was so much fun!
Now that you can picture two little nerds spending their precious time together coding, I want to tell you why Test Driven Development was such an interesting part of our time together. I promise not to get at all into the nitty gritty of programming, and I think I can pull this off in a way that will be interesting to the non-programmers of the audience.
I decided a while back to write a program that would help me add elapsed time. Excel, Numbers, and Google Sheets can all add time, but they do it on a 24-hour clock basis. So if you ask these tools to add 5 hours to 23 hours, instead of the expected 28 hours, they return the answer of 4 AM. These tools think 23 hours is 11 PM so when they add 5 hours to it, they get 4 AM.
Bart says that the best programs are the ones that scratch your own itch, so armed with the tools Bart has taught us, I got to scratching.
Imagine you’re me and you’ve been slaving away at your keyboard for months and months and you finally have your little Time Adder app up and running, and you decide to show it off. I sent my little web app off to a couple of people, and pretty much the first thing they tried to do broke it.
That was exactly what I wanted them to do. My process has been to try to think up every weird thing a human might accidentally do, but my imagination isn’t very good at thinking these things up. When I ask Bart how he thinks up all the weird things someone might do, he says it’s decades of experience. Until I get those decades of experience under my belt, sending my code to other people to break works quite nicely.
However, this isn’t a very robust way to test the code after I make changes. I’d have to keep torturing my friends and followers to try to break it again. When Helma flew over 5000 miles around the globe to come sit in my kitchen and code with me, we decided to work on a way to help me robustly test my code as I work on it.
But What is Test Driven Development?
I promised to explain Test Driven Development in a way normal humans could understand and we’re finally ready for me to give it a go. We know the problem to be solved, so we’ll use my little app as an example. My Time Adder app is quite simple visually. It has two rows of boxes where you type in the hours, minutes, and seconds you want to add together. As you type numbers into the boxes, the total is constantly being calculated at the top. You can type in positive or negative numbers, and you can optionally give each row a title.
The people I asked to test my app tried to type in things other than numbers. They typed in all kinds of letters and punctuation and even spaces. I fixed my code so now it throws errors with these characters. For example, if you type a letter into one of these number fields, I pop up a red message that says, “Numbers You Silly Goose!”
At the moment in time that I fix the code to tell them not to type in letters, everything’s dandy. But now time goes on and I keep messing with my code to do other things. Eventually, it’s quite possible I will do something that will break the part that says “Numbers, you silly goose!” when people type in letters. Most importantly, I could break it and never realize that I broke that part.
The idea of Test Driven Development is to write tests, (which are little programs themselves) that record what to test and how to test. For example, I could write a test that says that letters should throw an error. In the test, I would also include a sample of doing it wrong, entering letters instead of numbers. If my code is working properly and the test is written properly, when I run the test it should pass if the code throws an error when letters are entered instead of numbers. I know that sounds counterintuitive that if it throws an error it means the test passed, but are you with me so far?
I save that test and add every other test I can think of and when I have my code functioning properly, all of the tests should pass.
Now fast forward to a time when I’m working on a new feature. After I get done adding the new feature, I can rerun the tests I wrote before, and if I broke something that was working, I’ll know it because the test will fail.
Instead of having to bug my friends and followers to try and break my code, I can reliably try to break it myself. That’s Test Driven Development.
Now here’s when it gets really fun. With Test Driven Development, you can write the tests before you write the code. That sounds crazy and it sounds like a lot of work, but if your test framework is easy enough to use, it can be really helpful. I’ll explain with another example with my little app.
I wanted people to be able to subtract as well as add time. I thought about putting big plus/minus buttons in the interface but I came up with a simpler way. If I allowed negative times to be entered, the math would subtract automatically.
Now here’s the problem. A minus sign by itself is really a dash, which is a letter, not a number. When you start to type in a negative number, you get called a silly goose before you can finish typing. If you added a number after the minus sign, the goose error would disappear, but I was afraid people would think they weren’t allowed to use negative numbers because they got yelled at before they could finish.
Also, remember I said my Time Adder app is always calculating the addition of all the values. As soon as the user puts in a minus sign, the math breaks too because the minus isn’t a number.
Before we started writing the code to allow people to type a minus and not suffer name-calling, Helma and I decided to use Test Driven Development “with anger” and write the tests first.
This forced us to think about what the math should do if someone types in a minus sign. We figured minus could be the same as zero and it would do no harm. We wrote three tests that put the minus sign in the hours, then the minutes, and then the seconds field, but with real numbers in the other fields. Then we told the tests what the total should be for adding them all up.
It might be of interest why we had to test the minus sign in all three fields: hours, minutes, and seconds. The way I do the math to add up all the rows of elapsed time is to add the hours from each row together and multiply by 3600 to get seconds. Then I add the minutes from each row together and multiply by 60 to get seconds. Then I add both of those numbers to the regular seconds from all the rows to get the total elapsed time in seconds. Finally, I have to parse the total seconds back into hours, minutes, and seconds to get one total elapsed time.
We discovered that this simple act of multiplying hours by 3600 and minutes by 60 would change whether the test failed or passed. The plain seconds never got multiplied by anything so they behaved differently. We’d get test results back where hours and minutes were calculated properly but seconds failed, or sometimes vice versa! We learned that we had to test every field to be sure no funny business was happening.
Once we had our tests written, we were ready to start writing the real code.
It took a while to figure out how to allow a minus sign to be interpreted as a zero. We had two problems to deal with.
When you create an input box like I have in my Time Adder app, you tell the browser what type of input box you want. Since I wanted numbers in the box, I originally set the input boxes to
type = "number". That makes sense, right?
It makes sense until we got this idea to allow a minus sign which isn’t a number when it stands alone. Helma suggested we change the input box to
type = "text". Well, that opens a whole new kettle of fish because now you can type in any old glop and you won’t be called a goose.
How can we tell it to let you use text, but only certain text? Enter the terrifying world of Regular Expressions! Seriously, Regular Expressions are the weirdest and yet most useful concept I’ve ever seen in programming. A Regular Expression is a secret code that filters for specific types of characters. I’ll explain again with our example.
We know we want to allow a minus sign (but only one) and we want to allow all negative and positive numbers. In code I will not make you read or listen to, you can create a regular expression that says to allow one and only one minus sign and any number of positive or negative numbers.
As we started to write the Regular Expression, I realized that there were a couple of other characters that we could allow that might help people. What if you accidentally entered a space into one of these little boxes? You might never notice it was there (since it’s blank) and it would be sad face time for you because you wouldn’t know why the math was broken in the total. What do I care if you put spaces in? Why can’t they also be zero?
We added some more tests before we finished our Regular Expression.
Ooh – what if you want to put in .5 seconds? As you start to type a decimal value, the dot by itself is a period so that’s text too. So maybe I could let a single dot (but not lots of dots) be interpreted as zero.
Update-a-go-go on the tests to allow for a single dot.
Now we were ready to write our Regular Expression to allow one or more space, only one dot, only one minus, and as many numbers as you like, positive or negative.
I tried using ChatGPT to write our Regular Expression, and it got a little bit of it right, but it was wrong enough that Helma wrote most of it for me from scratch with the aid of regex101.com. Personally, I think having someone else write your Regular Expressions for you is the only way to go. I’ve tricked Allister into writing them for me too.
What’s that you say? You really do want to know what this Regular Expression looks like? Ok, you asked for it:
I know, it looks and sounds like a cat walked across the keyboard but trust me when I tell you that it works. This means one or more spaces, zero or one minus sign, zero or one dot, and any number of digits. Well, trust Helma. And trust our Test Driven Development tests!
The bottom line is that I had an absolute blast hanging out with Helma and nerding out. She repeatedly said she was delighted that we got to code while we were together. I loved getting my code actually working the way I wanted it, and it made me super happy to actually get some tests working so I could truly understand how this Test Driven Development thing works. Bart’s instructions were fabulous but without actually doing it, it never sank in until Helma and I had our little play date.
If you want to play with my fully functioning but not documented yet Time Adder app, follow the link to it on GitHub Pages in the shownotes.