Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Intro to testing

Slides

Why Test?

Tests are crucial to writing reliable software. A good test suite allows you to:

It also does a few things you might not have expected:

In short: tests serve as a form of documentation about your expectations of the code.

Overarching principles to keep in mind

The adversarial view --- don’t be wishful. Try to find defects. Document for the user things that could cause problems but that you are not guarding against (a.k.a. make explicit caveat emptors). We’ll discuss documentation later.

Write code that’s amenable to atomic testing --- modularity, DRY vs. WET, etc all help (as we’ll see). The better your design and workflow play with Git, then generally the more amenable your code will be to systematic testing.

An ounce of prevention is worth a pound of cure --- test early, test often, and review code.

You can’t test everything --- code will have bugs or use-cases it didn’t anticipate. Your goals are to minimize the incidence of the unforeseen and to have a framework that lets you incorporate previously unanticipated scenarios into your future testing easily.

Tests are themselves code --- it should adhere to good development principles (modularity, DRY not WET, etc), and it should grow alongside your actual code (i.e. writing more and more tests is part of the code authoring process).

Test code often outnumbers real code --- or at least it should. SQLite famously has 608 times more test code than source code.

Testing well isn’t trivial --- tools can help, but it’s a skill and an art.

Code that passes all tests may still suck --- verification vs validation; Quality Assurance (QA) is also important.

Tests actually save time --- You are probably already writing tests when you write your code, you are just likely deleting them. If you record them instead, they will continue to be useful, and will cut hours of debugging and problem solving. If you are manually running a few known inputs, recording those in a framework that runs automatically can be a huge time saver.

Types of tests

There are several levels / types of tests:

Frequency of testing

How often do you run tests? Unit tests are generally run every commit (more on that later). Integration tests might be less frequent. System tests will be much less frequent.

The faster your tests run, the more often you can run them.

What do you test?

Here’s a non-exhaustive list of some types of testing:

How do you test?

When do you write tests?

We won’t discuss details of development methodologies, but we should probably at least mention them once.

More interesting for us is Test Driven Development (TDD), which we’ll cover in a later section. This tends to be more common / easier with AGILE development, since you are writing smaller portions at a time.

Test Driven Development (TDD)

Test driven development flips the tables on test vs. code; instead of writing the code then testing it, you write the tests and then make them pass by writing the code. It may seem unnatural at first, but is has some significant benefits:

The most important reason: If you write code first, you are much less likely to go back and test it. Writing tests is important, so treat it as such!

Other styles

You don’t have to lead with tests. You can write a new feature, then test it. Most of the time, you’ll develop one thing at a time (feature, refactor, dependency upgrade, etc), and then submit it as a Pull Request (even to yourself), and the only requirement is any new code also has new tests.

I’d recommend trying TDD, then deciding on a style that’s right for you - it might even be problem specific (in fact, it almost surely is).

How much do you test?

As close to 100% coverage as you reasonably can. If you are measuring coverage using a tool, there’s a huge benefit to getting 100% coverage, even if you have to explicitly mark a few tiny sections to be ignored by coverage (due to special platform or hardware requirements, perhaps).

You can do even better. Just because a line of code is tested once, it doesn’t mean every possible path through it is tested. Just because units are tested, that doesn’t mean the whole integrated system works. So you can still add tests.

There are some things that are hard to test. Code that needs hardware (like data acquisition), graphical user interfaces, remote networking, plotting, slow computations, or large volumes of data can be challenging. We’ll be discussing some ways to test these sorts of things later!

How important is it that your code work? How many users do you have? Will someone die if there’s a bug (a real worry in some areas like medical / automotive fields!)

We’ll also cover static checking, which is not as powerful, but generally doesn’t need explicit tests and so can more easily cover every line of code. It’s not a replacement, but it can help.

Testing strategies

Testing frameworks

Let’s be honest: no one likes writing tests (at least I know I don’t). And the last thing I want to do is write a lot of boiler-plate code that makes the tests runnable & easy to use. Specifically, I want these features:

A testing framework adds these things without requiring extensive per-test boilerplate. Early testing frameworks were often “x-unit” style, which came from a generalization of J-unit, a framework for Java. It solved the above requirements, but was very verbose (okay, so is Java). Modern testing frameworks utilize the languages they are in to provide a lower boilerplate, cleaner experience.

Some C++ testing frameworks:

Note that none of these are true x-unit style, because C++ doesn’t have reflection, making C++ one of the few languages without x-unit style test frameworks.

Some Python testing frameworks:

In short: Use pytest, unless you are writing a Python standard library module. Which I assume you are not.

Ruby has a couple of popular frameworks. Matlab has integrated frameworks (everything is integrated in Matlab, though). Look around and see what others are using in your favorite language.