Writing Tests

CSC-430

Phillip Wright

Unit Tests Are Code

Obviously. The point, though, is that you should use all of the principles you apply to writing good code in general when writing unit tests. You will need to maintain, fix, and improve tests over time just like all other code!

Unit Tests Are Code II

If you don’t follow best practices, updating tests will become such a nightmare that you will simply start deleting tests or just stop testing at all. Then regressions creep in and the game is over.

F.I.R.S.T

  • Fast: or they won’t be executed
  • Independent: isolate tests so it’s clear what causes failures
  • Repeatable: it should be trivial to test again and again
  • Self-Validating: test should be reduced to pass/fail without need for human validation
  • Timely: should be written before/along with code being tested

What To Test

First, we need to identify what a given unit should do.

This sounds simple, but it isn’t!

We can easily create tests for the common, obvious cases, but we must also test for the boundary cases.

Boundaries

For instance, if we are implementing a function to calculate the area of a rectangle given its height and width, what inputs should we test?

First, we would do something obvious like area(10,5)=50.

Boundaries II

But what happens if we are given negative values? What if we are given zeros?

We often forget about these types of inputs when writing code and just assume we will “obvious” inputs.

Boundaries III

In this particular case, the logic is simple enough that we can easily address the problem with a couple of tests and some branching in our code.

In general, though, we really need to focus on our domains.

Boundaries IV

In general, for an integer parameter, we should probably consider the following values:

  • positive values
  • negative values
  • zeros
  • one
  • negative one

Boundaries V

If we know we are using modular arithmetic, then we may want to test one representative from each congruence class as well.

If we are working with mod m, then maybe a good set of tests would be

-m-1, -m, …, -2, -1, 0, 1, 2, …, m, m+1

Boundaries VI

Basically, what you want to do is think about the input types, identify boundary values that are likely to cause different behavior, then test those boundary values, near those boundary values, and far away from the boundaries in all directions.

Boundaries VII

And don’t forget null values!

Boundaries VIII

So what would good inputs be for a method with a List input?

What about a method that takes a Point as input?

What about String inputs?

Assertions

Once we have decided what we need to test, we will write a single unit test for each interesting input and make an assertion about what its output should be.

Assertions II

Ideally, you should have one assertion per test. if not, all assertions should at least be testing one aspect of your code.

Another Problem

How can we test code that requires a database lookup?