W

Ward
GitHub logo
216 stars, 18 forks

Ward

A testing framework for Python 3.6 and beyond.

Descriptive testing

Describe your tests with strings, not long_and_unreadable_function_names.

@test("the eggs are green")
async def _():
    eggs = await get_food("eggs")
    assert eggs.colour == "green"
PASS test_food:7:
the eggs are green
FAIL test_users:12:
get_user(id=1) returns user 1
SKIP test_todos:19:
mark_done(todo_id=3) returns todo 3 [WIP]

Readable output

Understand failures quickly with colourful unified diff output.

Comparison: LHS vs RHS shown below

['apples', 'bananas', 'kiwis']
['apples', 'bananas', 'oranges']

Powerful test tagging and selection

Tag your tests and use tag expressions to select precisely which tests to run.

$
ward --tags "(unit or integration) and slow"

For example, tag tests with an issue tracker number to easily run tests associated with an issue:

$
ward --tags "FEATURE-1234 or FEATURE-4567"

Modular test dependencies

Manage test setup and teardown using fixtures that rely on Python's import system, not name matching.

1. Define a unit of test data anywhere, and tell Ward how long to cache it for.
user_fixtures.py
# Will run at most 1x per module
@fixture(scope=Scope.Module)
def user():
    u = User(id=1, name="sam")
    return u
2. Import it and bind it as a default argument. Ward will inject the resolved fixture into your test.
test_users.py
from user_fixtures import user

@test("get_user returns the correct user")
async def _(expected=user):
    found = await get_user(id=expected.id)
    assert found == expected

Expressive parameterised testing

Parameterise tests to have Ward call your test multiple times with different arguments.

@test("truncate('{text}', num_chars={num_chars}) returns '{expected}'")
def _(
    text=s,
    num_chars=each(20, 11, 10, 5),
    expected=each(s, s, "hello w...", "he..."),
):
    result = truncate(text, num_chars)
    assert result == expected

Ward will expand this into 4 distinct tests, each with their own description:

PASS test_util:47 [1/4]:
truncate('hello world', num_chars=20) returns 'hello world'
FAIL test_util:47 [2/4]:
truncate('hello world', num_chars=11) returns 'hello world'
PASS test_util:47 [3/4]:
truncate('hello world', num_chars=10) returns 'hello w...'
PASS test_util:47 [4/4]:
truncate('hello world', num_chars=5) returns 'he...'

More features

ASYNC SUPPORT
Write async tests and fixtures.
CROSS PLATFORM
Tested on Windows, Mac OS, and Linux systems.
ZERO CONFIG
Sensible defaults make configuration optional.
TEST SEARCH
Loose querying of test code for quick development.
LOW OVERHEAD
Roughly half the framework overhead of Pytest.
WORKS WITH HYPOTHESIS
Works with Hypothesis out of the box, with deeper support planned.

Interested in helping push Ward forward?

Ward is currently in development on GitHub. Contributions of any kind are welcomed!

It's available on PyPI, and can be installed using pip:

$
pip install ward