Writing your first tests
In this tutorial, we'll write two tests using Ward. We'll define reusable test data in a fixture, and pass that data into our tests. Finally, we'll look at how we can cache that test data to improve performance.
Ward is available on PyPI, so it can be installed using
When you run
ward with no arguments, it will recursively look for tests starting from your current directory.
Ward will look for tests in any Python file with a name that starts with
We're going to write tests for a function called
def contains(list_of_items, target_item)
This function should return
True if the
target_item is contained within
list_of_items. Otherwise it should return
Our first test
Tests in ward are just Python functions decorated with
Functions with this decorator can be named
_. We'll tell readers
what the test does using a plain English description rather than the function name.
Our test is contained within a file called
from contains import containsfrom ward import test@test("contains returns True when item is in list")def _():list_of_ints = list(range(100000))result = contains(list_of_ints, 5)assert result
In this file, we've defined a single test function called
_. It's been
@test, and has a helpful description. We don't have to read the code inside the test to understand its purpose.
The description can be queried when running a subset of tests. You may decide to use your own conventions inside the description in order to make your tests highly queryable.
Now we can run
ward in our terminal:
Ward will find and run the test, and confirm that the test PASSED with a message like the one below.
Extracting common setup code
Lets add another test.
@test("contains returns False when item is not in list")def _():list_of_ints = list(range(100000))result = contains(list_of_ints, -1)assert not result
This test begins by instantiating the same list of 10 integers as the first test. This duplicated setup code can be extracted out into a fixture so that we don't have to repeat ourselves at the start of every test.
@fixture decorator lets us define a fixture, which is a unit of test setup code. It can optionally contain some additional code to clean up any resources
the it used (e.g. cleaning up a test database).
Lets define a fixture immediately above the tests we just wrote.
from ward import fixture@fixturedef list_of_ints():return list(range(100000))
We can now rewrite our tests to make use of this fixture. Here's how we'd rewrite the second test.
@test("contains returns False when item is not in list")def _(l=list_of_ints):result = contains(l, -1)assert not result
By binding the name of the fixture as a default argument to the test, Ward will resolve it before the test runs, and inject it into the test.
By default, a fixture is executed immediately before being injected into
a test. In the case of
list_of_ints, that could be problematic if lots of tests
depend on it. Do we really want to instantiate a list of 100000 integers before
each of those tests? Probably not.
Improving performance with fixture scoping
To avoid this repeated expensive test setup, you can tell Ward what the scope of a fixture is. The scope of a fixture defines how long it should be cached for.
Ward supports 3 scopes:
testscoped fixture will be evaluated at most once per test.
modulescoped fixture will be evaluated at most once per test module.
globalscoped fixture will be evaluated at most once per invocation of
If a fixture is never injected into a test or another fixture, it will never be evaluated.
We can safely say that we only need to generate our
list_of_ints once, and we can reuse its value in every test that depends on it. So lets give it a
from ward import fixture, Scope@fixture(scope=Scope.Global) # or scope="global"def list_of_ints():return list(range(100000))
With this change, our fixture will now only be evaluated once, regardless of how many tests depend on it. Careful management of fixture scope can drastically reduce the time and resources required to run a suite of tests.
As a general rule of thumb, if the value returned by a fixture is immutable, or we know that no test will mutate it, then we can make it
Warning: You should never mutate a
module scoped fixture. Doing so breaks the isolated nature of tests, and introduces hidden dependencies between them. Ward will warn you if it detects a
module scoped fixture has been mutated inside a test (coming in v1.0).
In this tutorial, you've learned how to write your first tests with Ward. We covered how to write a test, inject a fixture into it, and cache the fixture for performance.