Categories
education Python

How to Break Your Code

It is a truth universally acknowledged that it’s best to find bugs in your program before your users do.

As the old programming joke goes: a programmer walks into a bar and orders a beer. 99 beers. 0.999999 beers. Orders 0 beers. Orders an iguana.

Then a customer enters and asks where the bathroom is — and the whole bar bursts into flames.

Sure, your code runs when a user enters any number — but what if they enter a string of meaningless letters? A random symbol? Nothing at all? What if the file you want the program to open doesn’t open, or is renamed? What if a customer asks where the bathroom is?

This article explains the basics of unit testing using unittest in Python — the dos, don’ts, and and please-for-the-love-of-god-don’t-bothers.

Let’s get started. (And remember: always keep a fire extinguisher behind the bar.)

Exploratory vs. Unit Testing

If you’re reading this article, odds are you’ve a) written a program before, and b) already done exploratory tests on it. This means you’ve input a value into a function to see what the output will be.

Exploratory tests are great if your program is small.

If you’re writing a more complex program, however — and want it to be *chef’s kiss* perfection — you probably want to run some more structured tests on your program to see exactly what is going wrong and where.

That’s where unittest comes in.

It’s a built-in Python framework and test runner, which means it provides tools for us to structure automatic tests. It’s also fast — often running multiple tests in a fraction of a second — and provides useful feedback when tests fail.

All right — enough intro. Let’s get testing.

Let’s start by testing some basic functions.

We’ll save these three functions as a module (remember, a module is just a Python file).

#1) Returns x raised to the power of y
def raise_to(x, y):
return x ** y#2) Returns the result of x divided by y
def divide_by(x, y):
return x / y#3) Returns a string created by concatenating first_name and last_name
def concat_names(first_name, last_name):
return first_name + last_name

** Note: Don’t forget to save all of your files in the same folder/directory! **

Now, let’s test these functions.

Step 1) Create a test module

Create a new Python file, and save it using this naming convention: test_filename.py. (Without this ‘test_’ prefix, the module won’t function the way we want it to.)

Step 2) Import unittest and test modules

Next, we need to import two modules: unittest and our test module. (Our module is called ‘project.py’, which is why we import ‘project’.)

import unittest
import project

Step 3) Create a class inheriting from unittest.TestCase

Why? This allows us to use all of the cool testing features that unittest.TestCase has to offer.

class ExampleTest(unittest.TestCase)

Step 4) Create test cases using ‘assertEqual’ keyword

Since we have three functions to test, we’re going to create nine total test cases (three for each function)— all using the special ‘assertEqual’ keyword.

Python has several built-in assert keywords in unittest that allow us to test to see if a given statement is true. You can see a full list of them here.

For our first test cases, we’ll test our ‘raise_to’ function. Remember, this function is supposed to raise ‘x’ to the value of ‘y’.

Here’s the general format we’ll use:

def test_[functionName](self):
self.assertEqual(filename.functionName(argument1, argument2), expectedResult)

If you’ve worked with object-oriented programming (OOP) before, this format might look familiar. That’s because unittest creates an instance of the TestCast class — which is why it takes ‘self’ as a parameter.

A Note on Creating Test Cases

This is where you want to think carefully about what might throw off your program if there’s a mistake in your code. For example, in the code below, our testcase asserts that if we pass ‘3’ and ‘2’ as arguments to our raise_to function, the function should return 9. Or, if you’re multiplying two negative numbers, the result should be positive.

You can run multiple test cases at a time for efficiency’s sake. Here, we run three test cases:

# Define our class
class TestProject(unittest.TestCase):#Create our testcase
def test_raise_to(self):
self.assertEqual(project.raise_to(3, 2), 9)
self.assertEqual(project.raise_to(4, 0), 1)
self.assertEqual(project.raise_to(4, -2), 0.0625)

Personally, I like to run tests in an IDE, but you can also use command prompt. (If you’d like to go the latter route, I highly recommend checking out this tutorial by Corey Schafer.)

Now, let’s run our first test!

To do this, simply run your code the way you normally would in your IDE. In our case, here’s what the test runner returns:

.
__________________________________
Ran 1 test in 0.001sOK

That means that our tests passed — our function is working as it should!

Let’s see what happens if we change our testcase so that one of them fails. We will update our testcase to say that if 4 and 2 are our x and y arguments, respectively, our output should be 20.

Here’s what happens when we run our test:

F
====================================================================
FAIL: test_raise_to (__main__.TestProject)
--------------------------------------------------------------------
Traceback (most recent call last):
File "/Applications/test_example.py", line 6, in test_raise_to
self.assertEqual(project.raise_to(4, 2), 20)
AssertionError: 16 != 20--------------------------------------------------------------------
Ran 1 tests in 0.001sFAILED (failures=1)

The test runner tells us not only a) where the issue is (which assertion failed), but also why (16 != 20). This is a great way to quickly and efficiently isolate the issue.

Here’s what our final test module code looks like:

# Import modulesimport unittest
import project# Define our classclass TestProject(unittest.TestCase):
def test_raise_to(self):
self.assertEqual(project.raise_to(3, 2), 9)
self.assertEqual(project.raise_to(4, 0), 1)
self.assertEqual(project.raise_to(4, -2), 0.0625)def test_divide_by(self):
self.assertEqual(project.divide_by(9, 3), 3)
self.assertEqual(project.divide_by(-1, 1), -1)
self.assertEqual(project.divide_by(-1, -2), 0.5)def test_concat_names(self):
self.assertEqual(project.concat_names('Jay', 'Reyes'), 'JayReyes')
self.assertEqual(project.concat_names('Elina', 'Chen'), 'ElinaChen')
self.assertEqual(project.concat_names('Cori', 'Malone'), 'CoriMalone')if __name__ == "__main__":
unittest.main()

As always, the best way to learn about unit testing is to experiment — play around with your own code! It’s a great way to figure out gaps in your own knowledge, and to understand the concept more in-depth.

If you’d like to learn more about unittest, I highly suggest checking out the resources below.

Corey Schafer Video: Python Tutorial: Unit Testing Your Code with the unittest Module

Python.org DocumentationUnit Testing with unittest

Real Python TutorialGetting Started with Testing in Python

Good luck, and happy coding!