Containers
The shed fixture gives a test a running container of the shed image. Tests that don't change anything share one container; markers give a test its own.
At a Glance
- TestShed enabled with
--shedand an image (see Getting Started). - Markers are imported from
kloudkit.testshed.docker.decorators.
Shared and Dedicated Containers
By default, shed returns one container shared by the whole session (shed_default), so it starts once and tests stay fast.
A test with any TestShed marker gets its own container instead, removed when the test ends. Use @shed_mutable() for a test that changes the container (writes files, installs packages) but needs no other setting:
from kloudkit.testshed.docker.decorators import shed_mutable
@shed_mutable()
def test_install_package(shed):
shed.execute("apt-get install -y curl")
assert "curl" in shed.execute("which curl")Configure With Markers
| Marker | Effect |
|---|---|
@shed_env(**envs) | Set environment variables. |
@shed_volumes(*mounts) | Mount files: (source, dest) tuples or volume objects. |
@shed_config(**kwargs) | Pass any other argument to the container (publish, command, probe, …). |
@shed_mutable() | Give the test its own container, without other settings. |
from kloudkit.testshed.docker.decorators import shed_config, shed_env
@shed_config(publish=[(8080, 80)])
@shed_env(MY_ENV_VAR="hello")
def test_configured_app(shed):
assert shed.execute("echo $MY_ENV_VAR") == "hello"@shed_config arguments go to the container's run call, so anything docker run takes works there (publish, command, entrypoint, user, networks, …).
Markers work on a class or a module too (pytestmark). The one closest to the test wins: a marker on a test replaces the same marker on its class. Each marker applies once per test, so two @shed_env on one test keep only one, and @shed_mutable() counts as an @shed_env.
Volumes
Every mount is read-only. A relative source resolves against the stubs directory (tests/stubs, or --shed-stubs-dir):
from kloudkit.testshed.docker import InlineVolume, RemoteVolume
from kloudkit.testshed.docker.decorators import shed_volumes
@shed_volumes(
("nginx.conf", "/etc/nginx/nginx.conf"),
InlineVolume("/app/config.txt", "any content you want", mode=0o644),
RemoteVolume("/app/config.json", "https://api.example.com/config.json"),
)
def test_with_files(shed):
...InlineVolume(path, content, mode=0o644): mounts a file with the given text or bytes.RemoteVolume(path, url, mode=0o644): downloads the URL before the container starts, and mounts the result. It also takesmethod,allow_redirects,raise_for_statusandrequest_options(passed torequests).
Suite-Wide Defaults
Override shed_container_defaults in your conftest.py to set what every shed container starts with:
import pytest
from kloudkit.testshed.docker import Container, HttpProbe
class AppContainer(Container):
DEFAULT_USER = "app"
@pytest.fixture(scope="session")
def shed_container_defaults():
return {
"container_class": AppContainer,
"probe": HttpProbe(port=3000, endpoint="/health"),
}Markers are applied on top of the defaults, key by key. envs and volumes are the exception: a dedicated container takes them from its markers alone, so default envs and volumes reach only the shared container. container_class picks the class every container is wrapped in (see Inside a Container).
Start on Demand With shed_deferred
shed_deferred is a function that starts the container when you call it, for setup that has to happen first, or for several containers in one test:
from kloudkit.testshed.docker.decorators import shed_env
@shed_env(APP_PORT="3000")
def test_deferred(shed_deferred):
# Nothing is running yet.
container = shed_deferred(envs={"DEBUG": "true"})
assert container.execute("echo $DEBUG") == "true"
assert container.execute("echo $APP_PORT") == "3000"
def test_replicas(shed_deferred):
primary = shed_deferred(envs={"ROLE": "primary"})
replica = shed_deferred(envs={"ROLE": "replica"})Call arguments merge with the markers:
envs: merged, call values win.volumes: added after the marker's volumes.- Anything else: replaces the
@shed_configvalue of the same name.
Attach a Running Container
shed_attach brings in a container you started yourself, such as a database from a compose file:
def test_against_running_db(shed, shed_attach):
db = shed_attach("my-postgres")
assert db.execute("pg_isready")
assert shed.execute(["getent", "hosts", db.name])The container joins the session's network for the test, so shed containers reach it by name, and leaves it when the test ends. TestShed never stops or removes it. It must already be running, and can't be used with --shed-skip-bootstrap.
Other Images With docker_sidecar
docker_sidecar starts a container from any image, on the session's network:
def test_with_nginx(docker_sidecar):
nginx = docker_sidecar("nginx:latest", publish=[(8080, 80)])
assert nginx.ip()It takes the same arguments as @shed_config except timeout, and ignores shed_container_defaults. The container is removed when the test ends; docker_module_sidecar and docker_session_sidecar keep it for the module or the session. With detach=False, the container runs to completion and the call returns its output.
Next Steps
- Inside a Container: what you can do with the container you get.
- Readiness Probes: wait until it's ready.
- Fixtures and Markers: every fixture and its scope.