# Failure is the first step to trying

The official guidance on test-driven development is to follow the red-green-refactor cycle:

1. Write a test that fails.
2. Make it pass.
3. Refactor.

But what’s the point in starting with a test that fails? To make sure you’ve written the right test! I encountered some unexpected behaviour recently that highlighted this point.

Imagine you have a Library that aggregates Items. An Item can be either a Book or a Film, but not both at the same time. If we create a Library in "book mode", it should initially contain an empty Book. If we create it in "film mode", it should initially contain an empty Film. Let’s start by writing a test to capture the book mode behaviour:

classdef tLibrary < matlab.unittest.TestCase

methods(Test)

function bookModeInitialisesToEmptyBook(testCase)

lib = Library(Mode="book");

testCase.verifyEqual(lib.Items,Book.empty(1,0))

end

end

end


(The Name=value syntax for name-value pairs was introduced in R2021a. It’s interchangeable with the classic (…,"Name",value) syntax.)

Let’s run the test. We expect it to fail because Library doesn’t exist.

We’ll skip over the steps of creating a blank class definition, the subsequent test failures due to a missing constructor with input arguments and a public Items property, and iteratively adding them in.

classdef Library

properties
Items (1,:) Item = Book.empty
end

methods

function lib = Library(nvp)

arguments
nvp.Mode (1,1) string {mustBeMember(nvp.Mode,["book" "film"])} = "book"
end

end

end

end


We run the test and see that it passes:

So far, so good. Now we write a test to capture film mode:

function filmModeInitialisesToEmptyFilm(testCase)

lib = Library(Mode="film");

testCase.verifyEqual(lib.Items,Film.empty(1,0))

end


We run the test:

And… it passes!?

Why is it passing? We can use the debugger to inspect lib.Items manually and see that it’s an empty Book and not an empty Film. After some investigation, we find that verifyEqual relies on isequal and isequal considers two empties of different classes to be equal under some circumstances.

Whether or not this behaviour of isequal is correct, the important point for us is that we’ve written the wrong test! Our implementation could have been wrong and we wouldn’t have known. We would have achieved full coverage but our test would not have been contributing useful information.

We therefore need to rewrite our test to catch this issue:

function filmModeInitialisesToEmptyFilm(testCase)

lib = Library(Mode="film");

testCase.verifyEmpty(lib.Items)
testCase.verifyClass(lib.Items,?Film)

end


Let’s run the updated test:

The test now fails and we can continue with our implementation, confident that our test will only pass when our implementation is correct.

Published with MATLAB® R2022a

|