Table of Contents
- What is Nose?
- Installation
- Getting Started with Basic Tests
- Test Discovery: How Nose Finds Tests
- Writing More Complex Tests
- Setup and Teardown
- Assertions
- Testing Exceptions
- Test Fixtures
- Running Tests with Nose: Command-Line Options
- Leveraging Nose Plugins
- Best Practices for Nose Testing
- Conclusion
- References
What is Nose?
Nose is a testing framework for Python that aims to make writing and running tests easier. It builds on the unittest module (Python’s standard library testing framework) but simplifies many of its complexities. Key features of Nose include:
- Auto-discovery: Automatically finds tests in your project without manual configuration.
- Flexible test styles: Supports
unittest-style classes, simple functions, and even doctests. - Setup/teardown hooks: Allows you to define pre-test (setup) and post-test (teardown) logic.
- Plugin ecosystem: Extends functionality with plugins for coverage reporting, HTML output, parameterized testing, and more.
- Command-line customization: Fine-tune test execution with flags for verbosity, filtering, and output formatting.
Installation
Nose is available on PyPI and can be installed via pip. Note that the original Nose (nose) is no longer maintained, but it still works for most legacy use cases. For new projects, consider alternatives like pytest or nose2 (a community-maintained fork of Nose).
To install Nose:
pip install nose
Verify the installation:
nosetests --version
You should see output like nosetests version 1.3.7 (the latest stable release as of 2024).
Getting Started with Basic Tests
Nose supports two primary test styles: function-based tests and class-based tests (similar to unittest). Let’s start with simple examples of both.
Function-Based Tests
Function-based tests are the simplest form in Nose. A test function is any function whose name starts with test_. Nose will automatically detect and run these functions.
Example: test_math.py
def test_addition():
"""Test that 2 + 2 equals 4."""
assert 2 + 2 == 4
def test_subtraction():
"""Test that 5 - 3 equals 2."""
assert 5 - 3 == 2
To run these tests, navigate to the directory containing test_math.py and run:
nosetests
Output:
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
The two dots (.) indicate that both tests passed.
Class-Based Tests (unittest Style)
Nose also runs unittest-style class tests. These are classes that inherit from unittest.TestCase, with methods named test_*.
Example: test_math_class.py
import unittest
class TestMath(unittest.TestCase):
def test_multiplication(self):
"""Test that 3 * 4 equals 12."""
self.assertEqual(3 * 4, 12)
def test_division(self):
"""Test that 10 / 2 equals 5."""
self.assertEqual(10 / 2, 5)
Run the tests with:
nosetests test_math_class.py
Output:
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
Test Discovery: How Nose Finds Tests
Nose uses auto-discovery to locate tests in your project. By default, it looks for:
- Files: Named
test_*.pyor*_test.py(e.g.,test_utils.py,math_test.py). - Functions: Named
test_*(e.g.,test_addition). - Classes: Named
Test*(e.g.,TestMath) with methods namedtest_*. - Doctests: Embedded in docstrings (enabled with the
--with-doctestflag).
Customizing Test Discovery
To run tests in a specific file or directory, pass the path to nosetests:
# Run tests in a specific file
nosetests test_math.py
# Run tests in a subdirectory
nosetests tests/ # Assumes a "tests" folder with test files
To run a specific test function or method:
# Run a specific function in a file
nosetests test_math.py:test_addition
# Run a specific method in a class
nosetests test_math_class.py:TestMath.test_multiplication
Writing More Complex Tests
Setup and Teardown
Setup and teardown logic ensures tests run in a clean environment. Nose provides hooks to define code that runs before (setup) and after (teardown) tests.
Setup/Teardown for Functions
Use the @with_setup decorator to define setup/teardown for individual functions:
from nose.tools import with_setup
def setup_function():
"""Runs before each test function."""
print("\nSetting up test...")
def teardown_function():
"""Runs after each test function."""
print("Tearing down test...")
@with_setup(setup_function, teardown_function)
def test_example():
assert True
Output with nosetests -s (to show print statements):
Setting up test...
.Tearing down test...
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Setup/Teardown for Classes (unittest Style)
For unittest-style classes, use setUp() (runs before each test method) and tearDown() (runs after each test method):
import unittest
class TestDatabase(unittest.TestCase):
def setUp(self):
"""Connect to a test database before each test."""
self.db = {"user": "test_user"}
def tearDown(self):
"""Clean up the test database after each test."""
self.db.clear()
def test_user_exists(self):
self.assertIn("user", self.db)
def test_user_value(self):
self.assertEqual(self.db["user"], "test_user")
Assertions
Nose uses the same assertion methods as unittest (via unittest.TestCase). Common assertions include:
| Assertion | Purpose |
|---|---|
assertEqual(a, b) | Check that a == b |
assertNotEqual(a, b) | Check that a != b |
assertTrue(x) | Check that x is True |
assertFalse(x) | Check that x is False |
assertIn(a, b) | Check that a is in b (e.g., a list) |
assertNotIn(a, b) | Check that a is not in b |
assertRaises(Exception, func, *args) | Check that func(*args) raises Exception |
Testing Exceptions
Use assertRaises to verify that functions raise expected exceptions.
Example: Testing division by zero
import unittest
def divide(a, b):
if b == 0:
raise ZeroDivisionError("Cannot divide by zero")
return a / b
class TestDivision(unittest.TestCase):
def test_divide_by_zero(self):
# Check that dividing by zero raises ZeroDivisionError
with self.assertRaises(ZeroDivisionError) as context:
divide(5, 0)
self.assertEqual(str(context.exception), "Cannot divide by zero")
Test Fixtures
Fixtures are reusable setup/teardown logic shared across multiple tests. Nose supports fixtures at the module, class, and function levels.
Module-Level Fixtures
Run once per module (before any tests in the module run) with setup_module and teardown_module:
def setup_module():
print("Setting up module...") # Runs once at the start
def teardown_module():
print("Tearing down module...") # Runs once at the end
def test_1():
assert True
def test_2():
assert True
Class-Level Fixtures
For unittest classes, use setUpClass (runs once before all methods) and tearDownClass (runs once after all methods):
import unittest
class TestAPI(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Initialize API client once for all tests."""
cls.client = "test_client"
@classmethod
def tearDownClass(cls):
"""Clean up API client once after all tests."""
cls.client = None
def test_client_initialized(self):
self.assertEqual(self.client, "test_client")
Running Tests with Nose: Command-Line Options
Nose’s command-line interface (CLI) offers flags to customize test runs. Here are common options:
| Flag | Purpose |
|---|---|
-v or --verbose | Show detailed test output (names and results). |
-s or --nocapture | Disable stdout capture (show print statements). |
-x or --stop | Stop testing on the first failure. |
-w <dir> | Run tests in the specified directory. |
--with-coverage | Generate coverage reports (requires coverage plugin). |
--cover-package=<pkg> | Specify packages to include in coverage. |
Examples
-
Verbose output:
nosetests -v test_math.pyOutput:
test_math.test_addition ... ok test_math.test_subtraction ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK -
Stop on first failure:
nosetests -x test_math.py -
Coverage report:
First install the coverage plugin:pip install coverage nose-covThen run:
nosetests --with-cov --cov=my_module test_math.py
Leveraging Nose Plugins
Nose’s plugin ecosystem extends its functionality. Here are essential plugins:
nose-cov: Coverage Reporting
Generates reports showing which lines of code are tested.
Installation:
pip install nose-cov
Usage:
nosetests --with-cov --cov-report=html my_module/ # HTML report
nosetests --with-cov --cov-report=term my_module/ # Terminal report
nose-html-report: HTML Output
Generates interactive HTML reports with test results.
Installation:
pip install nose-html-report
Usage:
nosetests --with-html --html-report=report.html
parameterized: Parameterized Testing
Run the same test with multiple input-output pairs.
Installation:
pip install parameterized
Example:
from parameterized import parameterized
def test_addition(a, b, expected):
assert a + b == expected
# Define test cases: (a, b, expected)
test_addition_cases = [
(2, 2, 4),
(3, 5, 8),
(-1, 1, 0),
]
# Generate tests for each case
for a, b, expected in test_addition_cases:
globals()[f"test_addition_{a}_{b}"] = lambda: test_addition(a, b, expected)
Best Practices for Nose Testing
- Name Tests Clearly: Use descriptive names like
test_user_login_successinstead oftest_login1. - Test One Behavior per Test: Each test should verify a single logic unit (e.g., separate tests for success and failure cases).
- Keep Tests Independent: Tests should not rely on shared state (use setup/teardown to isolate them).
- Run Tests Frequently: Integrate Nose with CI/CD pipelines (e.g., GitHub Actions) to run tests on every commit.
- Use Plugins Sparingly: Only add plugins that add critical value (e.g., coverage, HTML reports).
Conclusion
Nose remains a powerful tool for Python testing, especially in legacy projects. Its simplicity, auto-discovery, and plugin support make it easy to write and run tests. While newer frameworks like pytest have overtaken Nose in popularity, learning Nose provides foundational knowledge for Python testing workflows.
For new projects, consider pytest (more modern and feature-rich) or nose2 (maintained fork of Nose). For existing codebases using Nose, this tutorial equips you to write, run, and optimize tests effectively.