Skip to main content
HomeTutorialsPython

Test-Driven Development in Python: A Beginner's Guide

Dive into test-driven development (TDD) with our comprehensive Python tutorial. Learn how to write robust tests before coding with practical examples.
Feb 2024  · 7 min read

Testing gives you the confidence that your code does what you expect it to do. In this tutorial, we'll learn the basics of test-driven development (TTD) using Python. To start, we will cover some background on test-driven development – what it is and why writing tests first is helpful when writing code. We will take a test-first approach where we will first write failing test cases for the functionality we want to implement before writing the actual functions.

Tutorial Prerequisites

For this tutorial, you will need a basic understanding of Python; if you need to brush up on your Python knowledge, you can check out this course: Introduction to Python for Developers.

A test-first approach will allow us to think through the desired behavior and expected outputs. You can use various libraries to test in Python, such as pytest and unnitest. However, for now, we will focus on how to write simple test cases using Native Python that will validate that the desired functionality produces the expected output.

We will start with a simple test case and incrementally expand the test cases to cover different scenarios. As we expand our test cases, we may find bugs or gaps in our initial implementation logic that we need to improve. This overall process of iteratively writing more advanced test cases, seeing them fail, and then going back and improving our implementation is at the core of test-driven development with Python.

For this tutorial, we will be using DataCamp's Workspace. Workspace is a cloud-based development environment that lets you write and run Python code without installing anything locally. Let’s get started!

Getting Started with Workspace

Before we can get started with test-driven development, we need to explore Workspace and understand how to use it so it can help us on our learning journey. If you open up Workspace, you’ll notice you have many options. For now, we will focus on writing Python in Workspace.

image5.png

If you select Python, the workspace will open a coding cell for you to write Python in. It will look something like this:

image4.png

You have some useful options available here. For instance, you can run the cell to produce output, add comments or notes to help support your learning and utilize AI to help you get started or unblocked. You can also add or delete as many cells as you need. We will focus on running the cell with the "Run" option. As you can see in the code below, any output your code produces is logged underneath when you run the cell.

A quick note: If you have used multiple cells and defined a variable in one cell but are trying to access it in another cell, you need to run each cell separately or the whole workspace from the "Run" button on the top right-hand of your workspace.

image7.png

Now that we have the basics of workspace down, let’s get started with test-driven development.

Test Driven Development

Test-driven development means writing tests for your code before you write your code. To explain TTD, let's start with a straightforward example that involves writing a Python script to add numbers together. However, before we write the function, we will write a test to check if 1 + 1 equals 2. You can accomplish this using native Python, as shown below.

assert 1 + 1 == 2

The assert keyword in Python is used to write test cases and confirm that the program conditions evaluate to True. It allows you to validate the behavior and outputs of your code. When an assert statement fails, it raises an AssertionError, signaling an issue with the code. This would look like this inside of the workspace:

image1.png

Example 1: Testing a Simple Function

Let's start with the testing function. To begin, open up a Python file in Workspace. Test-driven development involves writing a failing test first. So, let's write a failing test by calling the add() function, which has not yet been defined.

assert add(1, 1) == 2

This will result in a NameError, indicating that no implementation exists, known as the "red" step. You get a NameError when you try to call a function or variable that has not been defined.

image3.png

Now, it's time to implement the function. We can do this by defining the function in Workspace.

def add(num1, num2):
    return

We have defined our function add, which takes in two parameters, num1 and num2. Our function does not return anything yet. Let’s see if our implementation of our function passes our tests!

image6.png

We no longer see a NameError. However, running the cell still produces an error! This time is an AssertionError. So our test has still failed! Why might we be getting this error? It’s because we defined a function that doesn't return anything yet.

When running the test, we get an AssertionError because 1 + 1 does not equal 2 with our empty implementation. Finally, we can make it pass by correctly implementing the logic in the function.

image9.png

You’ll notice the green dot in the corner of the workspace. This shows that your code is valid and has passed when you run the cell. Previously, you’ll notice it had been red with a run error and displayed the type of error below the cell. This feature is useful for debugging your code in the workspace.

You've now written your first test in Python using test-driven development. Nice work! We will now move on to expanding on our tests and thinking about catching potential errors that will cause our function to fail or not produce the expected output.

Example 2: Multiple Test Cases

Testing with only one input value often fails to uncover potential errors. Bugs can easily hide, especially if your function relies on variable user input or external logic. So, how can we thoroughly test a function against multiple values?

For example, what if an incorrect data type gets passed to the function? You could utilize Python testing frameworks, but let's use basic Python instead. First, I'll define a list of input dictionaries to simulate different usage scenarios:

test_cases = [
    {"num1": 1, "num2": 3, "expected": 4},
    {"num1": 1, "num2": "invalid", "expected": "Error: num2 must be a number"}, 
    {"num1": [1,2,3], "num2": 2, "expected": "Error: num1 must be a number"},
    {"num1": -1, "num2": 1, "expected": 0},
    {"num1": 3.5, "num2": 2.5, "expected": 6.0}, 
    {"num1": 100, "num2": 200, "expected": 300}
]

The above code showcases inputs for the function along with expected outputs. However, we must update our test to iterate through these test cases. We can validate each dictionary in the list by nesting the assert within a for loop.

for test in test_cases:
    assert add(test["num1"], test["num2"]) == test["expected"]

Since the function is unchanged, would we expect this test to pass or fail? And what error might occur?

image8.png

The test throws a TypeError error because we haven’t handled non-number inputs; what if a string or boolean gets passed instead of an int? We can update the function to catch these cases! We can return a custom message on invalid types by checking that the input variable is an integer or float.

def add(num1, num2):
    if type(num1) is not int and type(num1) is not float:
        return "Error: num1 must be a number"
    elif type(num2) is not int and type(num2) is not float:
        return "Error: num2 must be a number"
    else:
        return num1 + num2

When we re-run our tests, we do not get any more errors, so our tests have passed. We can now see the green circle indicates we have now implemented some passing tests. Well done!

image2.png

Final Thoughts

In this tutorial, we covered the fundamentals of test-driven development in Python using Datacamp's Workspace. Workspace provides a cloud IDE that allows you to write and run Python code without needing local installations. Tests validate code behavior and catch errors early. Assert statements check conditions and raise exceptions on failures.

You can explore more test cases and start writing your own tests in Workspace! If you want to learn more about testing in Python, check out our DataCamp course - Introduction to Testing in Python.


Photo of Amina Edmunds
Author
Amina Edmunds

Amina is a content development intern at Datacamp. With a background in Architecture and Education, she changed careers to pursue software development, starting with JavaScript; she is now focused on Python.

Topics

Start Your Testing Journey Today!

Course

Introduction to Testing in Python

4 hr
1.1K
Master Python testing: Learn methods, create checks, and ensure error-free code with pytest and unittest.
See DetailsRight Arrow
Start Course
See MoreRight Arrow
Related

Exploring Matplotlib Inline: A Quick Tutorial

Learn how matplotlib inline can enable you to display your data visualizations directly in a notebook quickly and easily! In this article, we cover what matplotlib inline is, how to use it, and how to pair it with other libraries to create powerful visualizations.
Amberle McKee's photo

Amberle McKee

How to Use the NumPy linspace() Function

Learn how to use the NumPy linspace() function in this quick and easy tutorial.
Adel Nehme's photo

Adel Nehme

Python Absolute Value: A Quick Tutorial

Learn how to use Python's abs function to get a number's magnitude, ignoring its sign. This guide explains finding absolute values for both real and imaginary numbers, highlighting common errors.
Amberle McKee's photo

Amberle McKee

How to Check if a File Exists in Python

Learn how to check if a file exists in Python in this simple tutorial
Adel Nehme's photo

Adel Nehme

Writing Custom Context Managers in Python

Learn the advanced aspects of resource management in Python by mastering how to write custom context managers.
Bex Tuychiev's photo

Bex Tuychiev

How to Convert a List to a String in Python

Learn how to convert a list to a string in Python in this quick tutorial.
Adel Nehme's photo

Adel Nehme

See MoreSee More