Please introduce yourself.
My name is Erin Dees. I have written or cowritten five books for The Pragmatic Programmers, including most recently Effective Testing with RSpec 3, cowritten with Myron Marston. I am an engineer by day. My duties now also include managerial duties. That’s been a fun, exciting transition. It’s been cool to have books from The Pragmatic Programmers ready to help me make that transition as well.
I’m a mother of three. Two of my kids are grown up; the other is fourteen. We live in Toronto, Ontario, and I’m working for an insurance tech startup up here. It’s a lot of fun—my first time working at a startup in its early days. It’s pretty exciting.
In my spare time, I play guitar, which I’ve done for years. I picked up bass guitar as a quarantine hobby and really love it. I’m an avid cook. I’m also a race walker: a modified form of running where you have to keep one foot on the ground at all times and land with a straightened leg. I’ve been doing that since about 2009. And at one point, I was simultaneously certified as an athlete, a coach, and an official for USA Track & Field. I also love board games, tabletop role-playing games, and video games.
In one of your bios, it says by night you convert espresso into programming books. Could you talk about that?
Writing books is a labor of love in every connotation of that phrase. I really enjoy doing it, but it’s a lot of work, as you know. It takes a ton of effort to put together a book from the authors, from the editors, from the indexers. When I think about my first couple of books, I was not an experienced author, and needed a lot more guidance and handholding and change of direction during the editorial process. And it’s exhausting, right? “I thought I was done with this chapter. How many rewrites are we on now?”
Sometimes, those late nights working into the wee hours, or in some cases getting up at 5:00 in the morning to write, yeah it took a lot of caffeine to get me through that process. So I did feel like I was converting caffeine into markup, into text.
You’ve written books using Ruby, Lua, and Idris. Can you share the story of your languages?
Computer languages have fascinated me literally from day one. When I was in sixth grade, a teacher brought to school a Timex Sinclair 1000, which was an old computer that didn’t even have a monitor. You plugged it into a television. The keyboard was built in; it couldn’t do much, but it booted right up and was instantly programmable in the BASIC programming language.
I had always just imagined that computers were programmed by rooms full of lab coat–wearing scientists. I had no idea that ordinary people could do it. And programming languages were the magic that made that possible. I literally did feel like I was performing magic, because I typed something in and a thing happened. It’s incredibly addictive.
Each time I would learn a new language either for school (like, in high school we learned Pascal and in university, we learned Lisp, and we learned C and C++) or for work, like learning Ruby (well, that started as a passion that also became my work), I’ve found them fascinating.
Each language is a take on how to represent an idea and what abstractions to provide. How to help the programmer grow once they’ve gone beyond “Hello world!” How to help them build their own abstractions, really build their own customized version of that language closer to the problem domain, and then be able to express themselves fully in that tweaked, customized language.
I’ve always been just enchanted by how programming languages make that possible for us. Lua came into my life because I needed a configuration language for something and Lua was designed to be that. It needed to be a nonstatic configuration language. So many file formats assume that the configuration is just a set of static values, but in Lua, configuration values can depend on one another.
Most of the languages that I’ve written about in my books—in fact, all of the languages I’ve written about in my books—have been ones that I’ve found some use for in my personal or professional life, including Idris. I’ve never run a production service built in Idris, but I did use Idris to sketch out ideas for what an API should look like. And then I had to go back to the somewhat less enchanting daily world of C++ to go implement those ideas. But sketching in Idris gave me some tools that perhaps sketching in C++ might not have.
What does your current work life, in your nonmanagement hours, involve in terms of expressing yourself through language?
I’ve got coworkers who come from an F# background, and they find themselves able to express very intricate and specific ideas that they’re familiar with from the F# world. I can give you a couple of examples.
Our core product is an API, and it depends on several configuration values to get started. You can think about things like database connections or perhaps API keys for calls to external services. And in TypeScript, it’s possible to write a program that won’t pass the type checker if you didn’t actually fill in those configuration values.
You can write a program that won’t even boot up if a value is missing, if you didn’t go get a value from the environment and inject it into the configuration.
That’s one of the first changes I made when I joined the company, and it felt so powerful. We actually caught several aspects of our configuration where there were missing values that weren’t being set, as well as catching a few unused values. We removed the things we could out of our configuration and somewhat hardened our security footprint in doing so by getting secrets out of our system.
What’s so expressive about those types is it makes it explicit in the declaration of that function: what can this function do? What are the ways that this might go wrong? Instead of catching an exception and then having to guess at what type it might be through a bunch of “if” statements, you actually see it laid out there: everything that can go wrong with this function. And it also forces you as the consumer of this API to handle all of those potential error cases.
You’ve talked about getting results and errors, which leads to the heart of what I wanted to talk to you about, which is testing. You started off in BASIC, and now we live in a much more nuanced world of language expressiveness. What has the journey been for you in terms of testing? Is it something that you encountered early and saw it develop, or is it something that you adopted when test-driven design and so forth became popular?
Testing is something that kind of fell into my lap. I was aware of things like test-driven development in the late ’90s and early 2000s. The language I was primarily programming in was C++. There wasn’t a great unit testing story for C++ at the time. Other languages that I worked in, I was able to do some unit testing.
Oftentimes it was manual. It was manually writing a test program, a test harness to go exercise some function of the code. So this notion of automated unit tests running as part of your build system was something I was aware that other shops were doing, but just didn’t really have the tools to do myself.
Things started to change in 2007 when I was working for a company that made a field-portable cellular tower tester. So, technicians would drive around and plug this device into the access cable of their cellular tower and it would sort of measure the health of the system. And I inherited the user interface tests for that.
Especially with our first version of that instrument’s code, there was a lot of business logic entangled with the user interface logic. We did later disentangle that somewhat, but in early days they were deeply intertwined. It often was that you could catch a regression of the program through the user interface.
Before this device had a proper programmer’s API, sometimes the user interface was the only way to test it. It was an embedded touchscreen device running Windows CE. At the time, the way testing worked was we had this expensive proprietary toolkit we had bought from somewhere. You would record a session of interacting with the device, and it would record every screen tap and every keystroke and save them in a file, which you would then convert to C++ on your computer, cross-compile it down to a Windows CE executable, copy it over to the device, and run it.
These recorded test scripts were just full of mouse clicks: down at pixel coordinate (168, 297), delay for exactly 178 milliseconds, and so on. There was no rhyme or reason to this. It was just a bunch of raw coordinates and timings. And you were supposed to add in manual assertions: oh, check that the title of the screen says this, or check that this control is visible or has this value in it.
They were really just difficult to read and write. And because we were frequently changing the user interface, they also broke all the time.
If you’ve got a hard-coded mouse coordinate and now it’s falling on dead space because somebody moved the button, then the whole rest of the script fails and you’re left trying to figure out why. I said, “There’s got to be a better way to do this.”
I started looking into what our choices were, and with this particular older version of Windows CE that we had to support, there really weren’t a lot of great scriptable choices. So I wrote a small network listener that ran on the device and listened for incoming commands, and then translated those commands into screen taps or keystrokes, or searched the window for a particular control.
I didn’t need that many commands in this language. It was sort of “find window, mouse down, mouse up, key down, key up”—that kind of thing, just a handful of different events. And then I could batch those together.
I could define a function in any programing language on the host computer and say, “Okay, to do a button click on a control with this particular ID, first find the window with that control ID, request its boundaries, calculate the middle of it, simulate a finger down in the middle of that control, delay for 50 milliseconds, simulate a finger up.” There, there’s your tap event. So now I could start to say, “Tap on this button, tap on this button.”
Then I could build those into bigger building blocks and say, “Set the frequency to 1 GHz,” or “Check the value of the Power measurement.” We could have built these host scripts in any language. This was a shop that favored Perl. I did give it a try in Perl. Perl just didn’t feel like as good of a fit for this. And I got the buy-in for them to let me try Ruby.
I wrote our own little bespoke testing framework, because I didn’t know about RSpec yet (RSpec was brand new) and just completely replaced those C++ test scripts. It was a fraction of the code. It ran faster. It never broke unless the actual behavior of the instrument broke—and it actually occasionally found regressions.
Then RSpec came out, or at least I became aware of it. It may have just been released. And I was able to see: this is doing all the things my homegrown test framework was designed to do, but better. So I threw out our homegrown framework and adapted everything to use RSpec.
We set this up to run every night so we could catch regressions. And even better, once Cucumber came along, we were able to express things in a format that was much more familiar for people who were not Ruby programmers.
So suddenly, I could do things like, if there was an ambiguity in what we were asked to build (I’m reading the requirements, and there’s two ways to interpret this statement), I could write a sketch for a Cucumber script, show it to our product marketing person, and say, “Have I captured here what this thing is supposed to do?” And then they would tell me whether I was right or not.
Then I could write the test and make sure that the instrument performed the way it was supposed to. So, that sort of testing in plain English—I know it gets a lot of bad press, but it opened up new relationships and ways for us to relate to our domain experts—which was huge for us. Behavior-driven development was really dipping my toe into this whole modern world.
Could you briefly explain what RSpec and Cucumber are?
Absolutely, yeah. RSpec is a Ruby testing framework that aims to be very expressive and very flexible. So, in typical test frameworks before RSpec came along, you would have to use kind of a special tag to identify a Ruby function as being a test case. In other languages, you would use annotations. In Ruby, you would have to remember to name your function “test_something.”
And the assertions: in typical tests, you set up some preconditions (you get the application you’re testing into some kind of state), and then you act (you make a thing happen), and then you assert (you look at the new state of the program and you assert some property of it). So arrange, act, assert. The assert step of it was often the word “assert,” and then some logical condition.
It just seemed very imperative style, and RSpec’s syntax is much more, I don’t know, closer to spoken English, more descriptive. It would say things like, “Describe this feature.” And the word “describe” was kind of the hint that you’re talking about a group of test cases together. Each test case that you’re writing, you use the word “it,” like, “It should do this,” or “it should do that.”
And then your assertions say, “expect.” “Expect person.last_name to equal ‘Smith.’” And it read a lot more fluidly and naturally, which was very appealing.
There’s a lot of other properties of RSpec that are amazing. The nice syntax is sort of like this spoonful of sugar, but then there’s all this beneficial medicine that comes along with that spoonful of sugar in terms of assertions and matchers (which is a way of checking a part of your application state). Matchers that are composable and easy to glue to each other and build more complicated assertions. A test runner that makes it very easy to run just specific parts of your test that you’ve identified.
So RSpec has all this power behind the scenes in addition to a syntax that just fits my brain well. So Cucumber is—it started as an alternative syntax for RSpec where instead of saying “describe” and “it” and using pure Ruby syntax, you are writing in plain English, in text files, not Ruby files.
You’re using a more Given/When/Then style. “Given I am an administrator, when I log into the dashboard, then I should be able to delete a post,” or something like that. Each of those lines of English text are matched against a regular expression to a little chunk of Ruby code that runs for that line of your test.
So you’re writing your test case in plain English. Now, granted it’s somewhat stilted plain English, not just conversational English, but it’s still enough for you to be able to show to a domain expert who may or may not be a programmer and have them understand the intent of the test script you’re writing.
And Cucumber later expanded to its own project, separate from RSpec and not even dependent only on Ruby anymore. There are implementations of the Cucumber syntax in dozens of programming environments.
You spoke briefly about testing on user interfaces, which fascinates me. Do you have any philosophical thoughts on what it takes to take something rather than being a pixel at a particular point and transform an interface into a representation that is testable and interactive and something that could be put into a continuous integration pipeline?
Yes. Yeah. I absolutely have thoughts there. When we started, we were testing a Windows CE application written in C++, and with the Windows API at the time, the only ways to identify a window were its title, or a private string not seen to the user called the window class, or (if it was a control specifically on a dialog box) by its numeric control ID.
Those are somewhat stable. Window classes are not always set to something meaningful. So we often went with the title. So I could often use the Windows API FindWindow function, and I don’t remember the name—I think it’s GetDlgItem or something like that—to get a dialog box control from its ID.
So it was usually feasible to find those controls on screen, but knowing what number to pass in—say I know that the “GHz” button is control number 127. How do I know that? I started by just hard-coding those values into my test script, but occasionally those would change. If somebody added a new control and they really wanted the controls to be in numeric order for some reason, and they started reordering things, occasionally I’d get into trouble there. So I wrote a very simple, super crude, regular expression-based parser for C header files that would pick up the control IDs. And I could now refer to them by IDC_GHZ instead of 127.
That was one way of future-proofing my Ruby scripts. The other way, as I mentioned, I think my earliest test scripts would say, “Tap on button ‘GHz,’ tap on this button, tap on that button,” and learning to recognize that that sequence of taps is so frequent that it needs to be in a helper method, like set_frequency.
I did what I could to build up a library of these helper functions. I think folks in the HTML world might use something called Page Objects, which is something similar: bundling up common operations you would do to the user interface.
That helped a lot because if something did break with my control IDs or my window names or window classes, I could at least only have to update them in a few places in this API layer that sits in between my test script and the UI, instead of having to update it in dozens of test cases.
That started to get even more general. We realized there were multiple ways to set, say, the frequency in the user interface. There was an onscreen keypad. There was an onscreen click wheel that you could scroll through. There might have been another way or two. And sometimes those ways would be inconsistent. You’d set the frequency one way and it wouldn’t update the other way. And so I started to realize, “Oh, it’s actually important how you set the frequency if we want to catch all these regressions.”
My test scripts now had to say, “Set frequency using the scroll wheel, using the keyboard,” etc., and now had this notion of which controls might be used to set a value that’s important to the user.
As I moved on to other runtimes and other environments, we leaned heavily on the mechanisms they provided for getting hold of a control. So once we were writing scripts to control .NET apps written in C#, a lot of those controls have an accessibility ID that you can just use those APIs to grab onto.
It’s been a while since I’ve used UIKit on iOS, but I know they’ve added a lot of automation and accessibility stuff to those controls as well. So I know you said “briefly,” I’m sorry, but this is a very long way of saying I leaned very heavily on whatever mechanism the platform provides for me to get a hold of a particular control on screen.
In production there can be a rabid belief in testing so that people put tests into testing that could be preconditions in the code and fail during compilation or put in assertions that should fail during testing, not testing, but runtime. So you have preconditions, you have assertions, and then you have the testing mechanism for doing your CI. And I’m wondering if you have some thoughts about when it is best and most appropriate to have the compilation fail or to have the run fail versus having it in the testing suite itself.
Okay. So you’re asking, what’s the role of testing in detecting production issues that could have been caught via assertions, preconditions, the type system, etc.? This is one I’ve got some feels about, for sure.
My philosophy here is to lean on what your environment is good at. So again, being right now in a type-checked language with a fairly expressive type system, my inclination is to push everything onto the type system that I can. There have been times when we have successfully done that and said, like, "This data type contains these fields and the program will not compile if somebody tries to initialize one of these things without all these required fields in it.” And that forces us at the boundaries of our system, where we’ve got data coming in, that forces us to vet that data and make sure that the first time it’s put into one of our domain objects, it is complete, it is consistent, it’s correct data.
Once it’s made it through that little gateway and been converted into a first-class type in our system, then we don’t have to check anymore. We don’t have to write assertions anywhere else through our code. We don’t have to write any tests about this because the properties of the type system are the assertions. As you point out, those are type-checked once before the program even boots.
I’m a big fan of catching errors at the type check stage, if we can. It’s what my team did. And ten years ago, writing code in C++, and leaning on what were at the time brand-new features of C++ to make our programs as expressive as they could, we made certain kinds of errors impossible to even write in the program.
I get to lean on these features because they’re built into the programming language I’m using. If I’m using something more dynamic like Ruby, maybe I’ve got to lean a little bit more on runtime code that gets evaluated.
But to your point about where do I check this, I still prefer in these cases to check at the boundaries of the system, get the code into objects that I trust, and then let the core of the system be a much more functional programming style, passing in trusted data and not feeling like I have to have assertions at every level of my object hierarchy about sort of the properties of this object. I wouldn’t say I’m super-disciplined about writing explicit pre- and post-conditions around all my Ruby methods. I’ve seen people use approaches like that and be successful with it.
How can people stay in touch and follow what you’re doing?
I have a sort of super-basic landing page website, erindees.me. That’s where my basic LinkedIn and Twitter contact info is.
What are you up to these days that’s exciting and new?
There’s a couple of things. One is I’ve got a coworker who’s really into domain-driven design, and that is such a large beastly topic, but it gets narrow enough to grapple with when you cross it with functional programming—when you look at it through the lens of a very, very functional programming style. The notion of splitting your application into domains and focusing on one domain at a time, and just assuming for the moment that the data coming into it and flowing out of it has been vetted, and is going to be correct by the time it gets to the part of the code you’re thinking about—that’s what this coworker’s encouraging me to do.
So I’m really excited at the prospect of disentangling some of the domains that our application works in. There’s already work underway along these lines to make bits of our code more functional and more isolated to their own part of the domain. And so, I’m very excited to kind of bring some discipline to that. I’ve been reading through the Domain Modeling Made Functional book. These are just going to be name-drops for a bunch of other Prag books, but yeah, that’s got me very excited.
This has been fantastic. Thank you!
And thank you!