A testing framework that extends unittest with plugins and enhanced discovery capabilities
—
Utilities for writing parameterized tests, BDD-style specifications, and enhanced test setup/teardown functionality. These tools extend unittest capabilities with modern testing patterns.
Decorators for creating parameterized tests that run with multiple input values.
def params(*paramList):
"""
Make a test function or method parameterized by parameters.
Parameters may be defined as simple values or tuples. To pass a tuple
as a simple value, wrap it in another tuple.
Parameters:
- paramList: Variable arguments, each becomes a test parameter set
Returns:
Decorator function that adds paramList attribute to test function
Example:
@params(1, 2, 3)
def test_nums(num):
assert num < 4
@params((1, 2), (2, 3), (4, 5))
def test_less_than(a, b):
assert a < b
"""
def cartesian_params(*paramList):
"""
Make a test function or method parameterized by cartesian product of parameters.
Parameters in the list must be defined as iterable objects (tuple or list).
Parameters:
- paramList: Variable arguments of iterables for cartesian product
Returns:
Decorator function that adds paramList attribute to test function
Example:
@cartesian_params((1, 2, 3), ('a', 'b'))
def test_nums(num, char):
assert num < ord(char)
"""Decorators for adding setup and teardown methods to function-based tests.
def with_setup(setup):
"""
Decorator that sets the setup method to be executed before the test.
Currently works only for function test cases.
Parameters:
- setup: The method to be executed before the test
Returns:
Decorator function that adds setup attribute to test function
Example:
def my_setup():
# setup code
pass
@with_setup(my_setup)
def test_something():
# test code
pass
"""
def with_teardown(teardown):
"""
Decorator that sets the teardown method to be executed after the test.
Currently works only for function test cases.
Parameters:
- teardown: The method to be executed after the test
Returns:
Decorator function that adds tearDownFunc attribute to test function
Example:
def my_teardown():
# teardown code
pass
@with_teardown(my_teardown)
def test_something():
# test code
pass
"""Domain-specific language for writing behavior-driven tests with hierarchical organization and fixtures.
def A(description):
"""
Test scenario context manager.
Returns a Scenario instance, conventionally bound to 'it'.
Parameters:
- description: Description of the test scenario
Returns:
Context manager yielding Scenario instance
Example:
with such.A('calculator functionality') as it:
# tests and fixtures
pass
"""
class Scenario:
"""
A test scenario that defines a set of fixtures and tests.
"""
def having(self, description):
"""
Define a new group under the current group.
Context manager for creating nested test groups. Fixtures and tests
defined within the block belong to the new group.
Parameters:
- description: Description of the test group
Returns:
Context manager yielding self
Example:
with it.having('valid input data'):
# nested tests and fixtures
pass
"""
def uses(self, layer):
"""
Add a layer as mixin to this group.
Parameters:
- layer: Layer class to add as mixin
"""
def has_setup(self, func):
"""
Add a setup method to this group.
The setup method runs once before any tests in the containing group.
A group may define multiple setup functions - they execute in order.
Parameters:
- func: Setup function to add
Returns:
The original function (decorator pattern)
Example:
@it.has_setup
def setup():
it.data = load_test_data()
"""
def has_teardown(self, func):
"""
Add a teardown method to this group.
The teardown method runs once after all tests in the containing group.
A group may define multiple teardown functions - they execute in order.
Parameters:
- func: Teardown function to add
Returns:
The original function (decorator pattern)
Example:
@it.has_teardown
def teardown():
cleanup_test_data()
"""
def has_test_setup(self, func):
"""
Add a test case setup method to this group.
The setup method runs before each test in the containing group.
Functions may optionally take one argument (the TestCase instance).
Parameters:
- func: Test setup function to add
Example:
@it.has_test_setup
def setup(case):
case.client = TestClient()
"""
def has_test_teardown(self, func):
"""
Add a test case teardown method to this group.
The teardown method runs after each test in the containing group.
Functions may optionally take one argument (the TestCase instance).
Parameters:
- func: Test teardown function to add
Example:
@it.has_test_teardown
def teardown(case):
case.client.close()
"""
def should(self, desc):
"""
Define a test case.
Each function marked with this decorator becomes a test case in the
current group. Test functions may optionally take one argument
(the TestCase instance) for executing assert methods.
Parameters:
- desc: Description of what the test should do, or the test function
if used without arguments
Returns:
Decorator function or Case instance if desc is a function
Example:
@it.should('calculate sum correctly')
def test_sum(case):
result = calculator.add(2, 3)
case.assertEqual(result, 5)
@it.should
def test_subtraction():
\"\"\"subtract numbers correctly\"\"\"
assert calculator.subtract(5, 3) == 2
"""
def createTests(self, mod):
"""
Generate test cases for this scenario.
You must call this with globals() to generate tests from the scenario.
If you don't call this, no tests will be created.
Parameters:
- mod: Module globals dictionary (usually globals())
Example:
it.createTests(globals())
"""
class Group:
"""A group of tests with common fixtures and description."""
def __init__(self, description, indent=0, parent=None, base_layer=None):
"""
Initialize test group.
Parameters:
- description: Group description
- indent: Indentation level
- parent: Parent group
- base_layer: Base layer class
"""
def addCase(self, case):
"""Add a test case to this group."""
def addSetup(self, func):
"""Add a setup function to this group."""
def addTeardown(self, func):
"""Add a teardown function to this group."""
def child(self, description, base_layer=None):
"""Create a child group."""
class Case:
"""Information about a test case."""
def __init__(self, group, func, description):
"""
Initialize test case.
Parameters:
- group: Parent group
- func: Test function
- description: Test description
"""
def __call__(self, testcase, *args):
"""Execute the test case with given TestCase instance."""from nose2.tools import params, cartesian_params
# Simple parameter list
@params(1, 2, 3, 4, 5)
def test_positive_numbers(num):
assert num > 0
# Tuple parameters
@params((1, 2, 3), (4, 5, 9), (6, 7, 13))
def test_sum(a, b, expected):
assert a + b == expected
# Cartesian product parameters
@cartesian_params([1, 2], ['a', 'b'], [True, False])
def test_combinations(num, char, flag):
# Test runs with all combinations: 8 total tests
assert isinstance(num, int)
assert isinstance(char, str)
assert isinstance(flag, bool)
# Class method parameterization
class TestMath(unittest.TestCase):
@params(0, 1, 5, 10)
def test_square_root(self, num):
result = math.sqrt(num * num)
self.assertEqual(result, num)from nose2.tools.decorators import with_setup, with_teardown
# Global test data
test_data = {}
def setup_data():
test_data['value'] = 42
test_data['list'] = [1, 2, 3]
def cleanup_data():
test_data.clear()
@with_setup(setup_data)
@with_teardown(cleanup_data)
def test_data_access():
assert test_data['value'] == 42
assert len(test_data['list']) == 3from nose2.tools import such
with such.A('web application') as it:
@it.has_setup
def setup():
it.app = create_test_app()
it.client = it.app.test_client()
@it.has_teardown
def teardown():
it.app.cleanup()
with it.having('user authentication'):
@it.has_test_setup
def setup_auth(case):
case.user = create_test_user()
@it.should('allow login with valid credentials')
def test_valid_login(case):
response = it.client.post('/login', data={
'username': case.user.username,
'password': 'password'
})
case.assertEqual(response.status_code, 200)
@it.should('reject invalid credentials')
def test_invalid_login(case):
response = it.client.post('/login', data={
'username': 'wrong',
'password': 'wrong'
})
case.assertEqual(response.status_code, 401)
with it.having('API endpoints'):
@it.should('return JSON responses')
def test_json_response(case):
response = it.client.get('/api/status')
case.assertEqual(response.content_type, 'application/json')
# Generate test cases - this is required!
it.createTests(globals())import unittest
from nose2.tools import params, such
from nose2.tools.decorators import with_setup
# Traditional unittest class
class TestBasicMath(unittest.TestCase):
@params(1, 2, 3, 5, 8)
def test_fibonacci_numbers(self, num):
self.assertIn(num, [1, 1, 2, 3, 5, 8, 13, 21])
# Function-based tests with setup
def setup_calculator():
global calc
calc = Calculator()
@with_setup(setup_calculator)
@params((2, 3, 5), (10, 5, 15), (0, 100, 100))
def test_addition(a, b, expected):
result = calc.add(a, b)
assert result == expected
# BDD-style specification
with such.A('calculator application') as it:
@it.has_setup
def setup():
it.calc = Calculator()
with it.having('basic arithmetic operations'):
@it.should('add positive numbers')
def test_positive_addition(case):
result = it.calc.add(2, 3)
case.assertEqual(result, 5)
@it.should('handle zero values')
def test_zero_addition(case):
case.assertEqual(it.calc.add(0, 5), 5)
case.assertEqual(it.calc.add(5, 0), 5)
it.createTests(globals())Install with Tessl CLI
npx tessl i tessl/pypi-nose2