Unit testing

Learn this with friends

ComputingIntro to computer science - Python4:456 check-ins

About this video

Unit tests check one unit of functionality, usually a function, by calling it with set arguments and comparing the return value to the expected one. Teams run them automatically to stop changes that break requirements, and aim for code coverage by writing one test case per class of inputs. In Python, frameworks like unittest and pytest provide the syntax, but the real skill is choosing interesting test cases.

You'll be able to

  • explain what a unit test is and when it passes or fails.
  • explain why teams run unit tests automatically before accepting a code change.
  • choose test cases that cover each class of inputs (each branch) of a function.
  • read a failing test result to locate a bug.

Key words

unit test
A test that checks a single unit of functionality, usually one function, gives the expected result.
assertion
The check inside a test that the actual result matches the expected result.
code coverage
How much of a function's possible paths of execution your test cases actually run.
class of inputs
A group of inputs that all take the same path through a function, so one test can represent them all.
boilerplate
Repeated, required code syntax that stays the same from one use to the next.
test suite
A collection of test cases written together for a piece of code.
Transcript

- [Instructor] You've broken your program down into functions, but now maybe you have 100's of functions, and a team of programmers is in there every day making changes to the code. With all of these moving pieces, how is it possible that this program isn't constantly broken?

The secret? Testing. Tests can come in many forms, like performance tests or end-to-end tests. But the most common is unit testing. Unit tests test a single unit of functionality at a time. In most cases, that unit is a function. With unit tests, we can define the requirements for a function in code.

For example, my unit test might validate that when I call my function with the arguments 5 wheat and 1 ore that I get the return value false. If that assertion is correct and I do get back false, then the test passes. But if we get a different return value from the one the test expects, then the test fails.

Teams can automate their unit test to run anytime someone makes a change to the function and they can block that change from being submitted until all the related unit tests pass. This effectively enforces the requirements so programmers don't accidentally break things when they make a change.

Either we fix the function to make the unit test pass, or if we thought twice about it and we're confident that it's safe to change the requirements, then we can update the unit test to reflect that new expected behavior and submit that along with our code change.

That way the unit tests stay a living contract of the functions requirements. But how do I know which tests to write? Writing just one test case doesn't really do a whole lot because my unit test would still pass even if my function just always returned four. So I haven't really proven anything about my function logic.

I could write a test for every possible input value, but that's probably not the most efficient use of my time. Instead, programmers try and work for a goal of code coverage. As a whole, we want our test cases to execute every possible path of execution through the function.

So we try and identify different classes of inputs, and write a test case for each of those. For example, with our get_route_score function we would at least want a test case for each of these conditional branches. So our classes of inputs would be less than or equal to 2 trains, 3 trains, 4 trains, and greater than or equal to 5 trains.

With those representative inputs we've anticipated and proven out all the reasonable ways that someone might call our function. Of course, the testing process isn't always perfect. We may miss a test case, or we might make compromises on code coverage in the interest of time.

If we do find a bug later, we just wanna make sure we learn from that mistake. So most programming teams will go back in and add a test for that exact failure case to enforce that the same bug never happens twice. Okay, great, but how do I actually write unit tests?

The principles stay the same, but the exact syntax varies depending on which unit testing framework you use. in Python, many programmers use the unittest module or the pytest module. Here's an example test suite written with the unittest framework. I have seven test cases, and you can see that they're basically all the same.

I only change the name, the argument I pass to the get_route_score function, and the expected result I compare to. When I run it, I can see that one of my test cases is failing. It tells me the name of the failing test case and the assertion that failed. Here I got the result -2 instead of the expected 0.

So I know my function must have a bug somewhere because it doesn't work when I call it with a negative argument. As you can see, a lot of unit testing code is just boilerplate. That is, the same repeated required syntax, so a lot of programmers will just copy and paste the boiler plate.

That means the secret to unit testing isn't so much memorizing the syntax as it is learning how to identify interesting test cases. If you're a beginner programmer working independently just to learn, then I'll be honest, it may not be worth it to write unit tests for all your code at this point, because writing test code can often take just as long as it takes to write the program code.

However, it is worth your time to think about test cases. When you write or change a function, it's a good habit to call it with a few different arguments to verify that it works as expected, even if you're not automating that process with unit tests. That way as we build out the rest of our program, we can have confidence that the individual functions work correctly and when we inevitably encounter an error later, we can rule out those functions as the cause of the bug.

Video © Khan Academy, used under CC BY-NC-SA. Watch on Khan Academy. Puzzicle is not affiliated with Khan Academy.