This guide assumes you can already write a test case — if not, start with the Guide to Test Cases, which covers what an example or test looks like and how to come up with good ones.

Here the question is different. For a single function, you ask whether a test is right. For a program of any size, you ask whether the tests you have are enough: whether they cover the situations the program will actually meet, and how you would know if they didn't. That question is answered with a testing plan.

Why write a testing plan

The guidelines for coming up with test cases generate ideas faster than you can write test code. The plan is where you record them: a short, high-level list of the situations your tests need to cover, written in English, before any of them exist as code.

Note: A testing plan is not code. It is an overview of what you intend to check. One line of the plan may turn into several tests, and a line you cannot yet write a test for still belongs in the plan — that's a gap you now know about.

Writing the plan first pays off in three ways:

An example plan

Take a problem in the shape of the ones we work with in this course — the same one the Guide to Planning works through. We have a table of students and a table of advisors, where every student is assigned an advisor based strictly on the semester they are in:

Students
namesemesterquiz1quiz2
Ursa38356
Isaias49279
Jackson8610
Isi79087
Thuy58585
Brigida500
Advisors
semname
1Pan
2Greene
3Greene
4Rodriguez
5Pan
6Pan
7Rodriguez
8Greene

The task: write advisor-for, which takes a student's name and returns that student's advisor. Before writing it, work down the questions from those same guidelines and write what each one turns up:

A testing plan for advisor-for
SituationWhat should happenWhy it's on the list
A student who is in the table"Ursa" gives "Greene" the ordinary case
Two students in the same semester "Thuy" and "Brigida" both give "Pan" one advisor serves many students
Students in the first and last semesters semester 1 and semester 8 both workboundary cases
A name that is not in the student tabledecide: error, or a "no such student" answer?missing data
A semester with no row in the advisor tabledecide, then make the tables disagree on purpose to test itthe two tables might not agree
Two students with the same namedecide: is this even allowed?duplicates
"ursa", " Ursa"decide: match anyway, or not? what a real user would type
An empty student tablesame as a name that isn't therebase case

Three things to notice about that plan. First, it was written without knowing anything about how advisor-for will work — that's what makes it possible to write before the code. Second, several rows say decide: the plan surfaced questions the problem statement never answered, and each one is a decision to make deliberately rather than discover by accident. Third, the last column names the guideline that produced the row, which is how you can tell whether you worked through all of them or stopped after the easy ones.

Planning at the level of the whole program

Now suppose the next part of the problem is a computation that adds an advisor column to the whole student table. That is a different granularity, so it gets its own entries — and they are about the shape of the result rather than about one answer:

Notice that these do not name a single expected value anywhere. Properties like "same number of rows" are often easier to state, and harder to get accidentally right, than a hand-computed answer for a six-row table. The Guide to Test Cases says more about tests of this shape.

Kinds of tests

Almost everything in a plan is about behavioral tests: tests of the relationship between inputs and outputs. It helps to name the flavors they come in, because a plan that is short on one of them usually looks complete until you have the names to check it against.

Performance tests

A different question is how much time or space the program uses. Such tests run on larger inputs than behavioral ones, and they are read differently:

Operating systems provide ways to track the time and space used to run a program.

Test granularity

The second thing to check a plan against is level. As programs get bigger than a single assignment's worth of functions, we test at more than one level:

A good set of tests includes all three levels, and at each level you can ask both the behavioral and (if the data are large enough) the performance questions above. Most of what you write in this course is unit tests, with system-level tests appearing on the larger projects.

Judging the plan

Three ways to judge a set of tests, and so the plan behind it:

Code coverage is tied to your particular implementation; the first two are about the problem, which is why they can be assessed before the code exists.

Keeping the plan alive

A plan is worth keeping after the code is written. When you find a bug, add the situation that produced it to the plan and then write the test — the plan now records why that test exists. When you change what the program does, the plan is where you notice which tests are now wrong. And when you ask an agent to propose tests, the plan is what you hand it: an agent working from your plan covers your list of situations, while an agent working from your implementation mostly confirms that the code does what it already does.

Testing practices (with and without agents)