Write Your First Python Unit Test

Write and run your first Python unit test step by step, using unittest to check a small function and understand the test output.

7 min read

Writing your first Python unit test takes less than five minutes. You do not need to install anything or set up a special project structure. If Python is installed on your computer, you already have the unittest module built in.

A unit test checks one small piece of behavior in isolation. If you have a function that validates a password, the unit test calls that function with a known input and checks that the returned value matches your expectation. It does not care about databases, network connections, or other parts of your application.

This article assumes you have read how to test Python code and are comfortable with Python functions. You will create a small function, write a test class, run the tests, read the output, and intentionally break something to see what a failure looks like.

Create a function to test

Start with a small, realistic function. This example checks whether a password meets a minimum length requirement, a common validation task that is simple enough to test thoroughly but real enough to matter.

pythonpython
def is_strong_password(password):
    if len(password) >= 8:
        return True
    return False

Save this in a file named password_checker.py. The function is straightforward: a password is strong if it has eight or more characters, and weak otherwise.

Write your first test case

Create a new file named test_password_checker.py in the same directory. You will import both unittest and the function you want to test, then create a class that inherits from unittest.TestCase. The class groups related tests together so the framework can discover and run them as a batch.

pythonpython
import unittest
from password_checker import is_strong_password
 
class TestPasswordChecker(unittest.TestCase):
    def test_short_password(self):
        result = is_strong_password("abc")
        self.assertFalse(result)
 
    def test_long_password(self):
        result = is_strong_password("mysupersecretpassword")
        self.assertTrue(result)
 
if __name__ == "__main__":
    unittest.main()

Each method whose name starts with test_ becomes a separate test. The method names describe the scenario, which matters because they appear in the output when a test fails. Use assertTrue when you expect True, and assertFalse when you expect False. The block at the bottom lets you run the file as a script; unittest.main() discovers all test methods and runs them.

Run the tests

Open your terminal, navigate to the directory containing both files, and run the test file:

bashbash
python test_password_checker.py

unittest prints a short summary to the terminal rather than the details of every test, so a successful run is easy to spot at a glance. When both tests pass, the output looks like this:

texttext
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
 
OK

Each dot represents one passing test, and OK means every assertion was satisfied. This confirmation tells you that your function behaves correctly for the inputs you tested.

You can also run the tests with the module syntax, which gives you access to extra options. The verbose flag is useful when you have many tests and need to see precisely which ones ran:

bashbash
python -m unittest test_password_checker.py

Add the -v flag for verbose output, which prints each test name on its own line instead of dots.

See what a test failure looks like

A passing test is reassuring, but understanding a failing test is equally important. Add a deliberate bug by changing the threshold from eight to ten characters:

pythonpython
def is_strong_password(password):
    if len(password) >= 10:
        return True
    return False

Run the tests again. The test for an eight-character password now fails because the function requires ten characters while the test still expects eight to count as strong. Here is what the failure output shows:

texttext
.F
======================================================================
FAIL: test_exactly_eight_characters (__main__.TestPasswordChecker.test_exactly_eight_characters)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_password_checker.py", line 12, in test_exactly_eight_characters
    self.assertTrue(result)
AssertionError: False is not true
 
----------------------------------------------------------------------
Ran 2 tests in 0.001s
 
FAILED (failures=1)

Read the output from bottom to top. The summary confirms one failure out of two. The AssertionError tells you the actual value, and the traceback points to the exact line.

This feedback loop is the core of testing. Change your code, run the tests, and the framework tells you which expectation no longer holds.

Fix the threshold back to eight, run again, and you are back to OK.

Add tests for edge cases

Two tests are a good start, but your test suite should also cover boundary values where bugs often hide. For a password length check, add tests for an empty string, a password of exactly eight characters, and a None input.

pythonpython
def test_empty_password(self):
    result = is_strong_password("")
    self.assertFalse(result)
 
def test_exactly_eight_characters(self):
    result = is_strong_password("abcdefgh")
    self.assertTrue(result)
 
def test_password_is_none(self):
    with self.assertRaises(TypeError):
        is_strong_password(None)

The first two tests check the boundary at exactly eight characters. The third test uses assertRaises, which expects the code inside the with block to raise a TypeError. If the function raises a TypeError when given None, the test passes because the code correctly refuses invalid input.

A good unit test is independent, fast, and readable. Independence means one test does not rely on the side effects of another test. Fast means each test runs in milliseconds so you can run the whole suite after every save. Readable means someone unfamiliar with the code can look at the test name and the assertion and understand what the function is supposed to do.

Avoid these common mistakes. Do not test Python built-in behavior like checking that len() works. Do not write a test that calls five unrelated functions and asserts ten things at once. Do not skip the assertion and just call the function without checking the result; a test without an assertion always passes, which gives false confidence.

The next article covers Python unittest explained in depth, including setUp and tearDown for sharing test data, the full set of assertion methods, and how to skip tests that are not ready to run.

Rune AI

Rune AI

Key Insights

  • A unit test is a small, focused check on a single function or method, written with assertion methods like assertEqual.
  • Create a test case by subclassing unittest.TestCase and writing methods that start with test_.
  • Run the test file directly with python test_file.py or with python -m unittest test_file.
  • A passing test shows a dot and an OK summary; a failing test shows an F followed by the expected and actual values.
  • Write tests that cover the happy path, edge cases, and error conditions for each function.
RunePowered by Rune AI

Frequently Asked Questions

What is the difference between a unit test and an integration test?

A unit test checks one small piece of code in isolation, typically a single function or method, without depending on external systems like databases, files, or network calls. An integration test checks that multiple pieces work together correctly, such as a function that reads from a real database and returns formatted results. Unit tests run faster and are easier to debug, so most test suites contain more unit tests than integration tests.

Do I need a special file structure to run unittest?

No. You can write test classes in the same file as your production code or in a separate file. For small scripts, keeping tests in the same file with a `if __name__ == '__main__': unittest.main()` block at the bottom is the simplest approach. For larger projects, separating tests into a dedicated folder is standard practice.

What does a passing test output look like versus a failing one?

A passing test shows a dot for each test that ran, followed by OK and a summary line like `Ran 3 tests in 0.001s`. A failing test shows F instead of a dot, followed by a detailed report that includes the test name, the line that failed, the expected value, and the actual value.

Conclusion

Your first unit test is a small milestone that opens a larger practice. Once you have written one test and watched it pass, you have the basic pattern you need for every test you will write from here forward. Add a test every time you write a function, and over time your project will carry its own safety net.