11# KloudKIT TestShed
22
3- > Meet ** KloudKIT TestShed** , a tidy home for your integration-testing power tools.
4- >
5- > It snap-fits into ` pytest ` , auto-provisions Docker, runs Playwright, and cleans up after itself
6- > so you can focus on building sharp tests.
3+ A pytest plugin for integration testing with Docker and Playwright.
4+ It handles container lifecycle, browser provisioning, and cleanup automatically.
75
86## Features
97
10- - ** Automated Docker management:** Spin up and control containers from tests.
11- - ** Playwright integration:** Run browser tests in isolated Docker environments.
12- - ** Configurable via markers & CLI:** Tune environments per test or suite.
13- - ** Automatic resource cleanup:** Ensures a clean state after tests.
8+ - ** Docker management:** provision and control containers from tests.
9+ - ** Playwright integration:** browser testing in isolated Docker environments.
10+ - ** Configurable via markers & CLI:** tune environments per test or suite.
11+ - ** Automatic cleanup:** containers and volumes are removed after tests.
1412
1513## Installation
1614
@@ -40,9 +38,44 @@ from kloudkit.testshed.fixtures.playwright import playwright_browser
4038
4139TestShed provides fixtures to manage containers inside your tests.
4240
43- #### High-level ` shed ` fixtures
41+ #### Configure containers with decorators
42+
43+ Configure containers using ` pytest ` markers:
4444
45- Use the ` shed ` fixture for smart container management with configurable defaults:
45+ - ** ` @shed_config(**kwargs) ` :** pass arguments to the container factory (e.g. ` publish ` , ` networks ` ).
46+ - ** ` @shed_env(**envs) ` :** set environment variables.
47+ - ** ` @shed_volumes(*mounts) ` :** mount volumes as ` (source, dest) ` tuples or ` BaseVolume ` instances.
48+ - ** ` @shed_mutable() ` :** force a dedicated container for tests that mutate state (bypasses the shared default).
49+
50+ ``` python
51+ from kloudkit.testshed.docker import InlineVolume, RemoteVolume
52+
53+ @shed_config (publish = [(8080 , 80 )])
54+ @shed_env (MY_ENV_VAR = " hello" )
55+ @shed_volumes (
56+ (" /path/to/host/data" , " /app/data" ),
57+ InlineVolume(" /app/config.txt" , " any content you want" , mode = 0o 644 ),
58+ RemoteVolume(" /app/remote-config.json" , " https://api.example.com/config.json" , mode = 0o 644 ),
59+ )
60+ def test_configured_docker_app (shed ):
61+ # ... test logic ...
62+ ```
63+
64+ Use ` @shed_mutable() ` when your test writes data, installs packages, or otherwise changes the container.
65+
66+ This ensures it gets its own instance instead of reusing the shared default:
67+
68+ ``` python
69+ @shed_mutable ()
70+ def test_install_package (shed ):
71+ shed.execute(" apt-get install -y curl" )
72+
73+ assert " curl" in shed.execute(" which curl" )
74+ ```
75+
76+ #### High-level ` shed ` fixture
77+
78+ Use the ` shed ` fixture for container management with configurable defaults:
4679
4780``` python
4881import pytest
@@ -73,55 +106,55 @@ def test_my_app_with_debug(shed):
73106 assert shed.execute(" echo $APP_PORT" ) == " 3000"
74107```
75108
76- You can also use the factory directly:
109+ #### Deferred deployment with ` shed_deferred `
110+
111+ Use ` shed_deferred ` when you need to control * when* the container starts, for pre-deployment
112+ setup, runtime parameterization, or spinning up multiple containers in a single test:
77113
78114``` python
79- def test_custom_setup (shed_factory ):
80- container = shed_factory(envs = {" CUSTOM_VAR" : " value" })
81- # ... test logic ...
115+ @shed_env (APP_PORT = " 3000" )
116+ def test_deferred_deployment (shed_deferred ):
117+ # Container is NOT running yet — do setup here
118+ # ...
119+
120+ # Deploy with optional call-time overrides
121+ container = shed_deferred(envs = {" DEBUG" : " true" })
122+ # envs are merged: APP_PORT=3000 + DEBUG=true
123+
124+ assert container.execute(" echo $DEBUG" ) == " true"
125+ assert container.execute(" echo $APP_PORT" ) == " 3000"
126+
127+
128+ def test_multiple_containers (shed_deferred ):
129+ primary = shed_deferred(envs = {" ROLE" : " primary" })
130+ replica = shed_deferred(envs = {" ROLE" : " replica" })
131+ # Each call spins up a new container
82132```
83133
134+ Call-time parameters merge with decorator config:
135+
136+ - ** ` envs ` :** dict merge * (call-time values override decorator values)* .
137+ - ** ` volumes ` :** concatenated * (call-time volumes added after decorator volumes)* .
138+ - ** ` **kwargs ` :** passed as config args * (override decorator ` @shed_config ` values)* .
139+
84140#### Basic Docker container
85141
86- For a lower-level API, use the ` docker_sidecar ` fixture to create containers :
142+ For a lower-level API, use the ` docker_sidecar ` fixture:
87143
88144``` python
89- import pytest
90-
91145def test_my_docker_app (docker_sidecar ):
92- # Launch a simple Nginx container
146+ # Launch a container
93147 nginx = docker_sidecar(" nginx:latest" , publish = [(8080 , 80 )])
94148
95149 # Execute a command inside the container
96- assert " nginx version" in nginx.execute([" nginx" , " -v" ])
150+ hostname = nginx.execute(" cat /etc/hostname" )
151+ assert len (hostname) > 0
97152
98153 # Access the container's IP
99- print (f " Nginx container IP: { nginx.ip()} " )
154+ print (f " Container IP: { nginx.ip()} " )
100155
101156 # Interact with the file system
102- assert " /usr/share/nginx/html" in nginx.fs.ls(" /usr/share/nginx" )
103- ```
104-
105- #### Configure containers with decorators
106-
107- Configure containers using ` pytest ` markers/decorators:
108-
109- - ** ` @shed_config(**kwargs) ` :** Generic container args.
110- - ** ` @shed_env(**envs) ` :** Environment variables.
111- - ** ` @shed_volumes(*mounts) ` :** Volume mounts as ` (source, dest) ` or ` BaseVolume ` .
112- - ** ` @shed_mutable() ` :** Force non-default shed for tests that perform mutable operations.
113-
114- ``` python
115- from kloudkit.testshed.docker import InlineVolume, RemoteVolume
116-
117- @shed_env (MY_ENV_VAR = " hello" )
118- @shed_volumes (
119- (" /path/to/host/data" , " /app/data" ),
120- InlineVolume(" /app/config.txt" , " any content you want" , mode = 0o 644 ),
121- RemoteVolume(" /app/remote-config.json" , " https://api.example.com/config.json" , mode = 0o 644 ),
122- )
123- def test_configured_docker_app (shed ):
124- # ... test logic ...
157+ assert " html" in nginx.fs.ls(" /usr/share/nginx" )
125158```
126159
127160### Playwright browser testing
@@ -140,13 +173,13 @@ def test_example_website(playwright_browser):
140173
141174TestShed extends ` pytest ` with options to control the Docker environment:
142175
143- - ** ` --shed ` :** Enable TestShed for the current test suite * (default: disabled)* .
144- - ** ` --shed-image IMAGE ` :** Base image * (e.g., ` ghcr.io/acme/app ` )* .
145- - ** ` --shed-tag TAG|SHA ` :** Image tag or digest * (default: ` tests ` )* .
176+ - ** ` --shed ` :** enable TestShed for the current test suite * (default: disabled)* .
177+ - ** ` --shed-image IMAGE ` :** base image * (e.g., ` ghcr.io/acme/app ` )* .
178+ - ** ` --shed-tag TAG|SHA ` :** image tag or digest * (default: ` tests ` )* .
146179- ** ` --shed-build-context DIR ` :** Docker build context * (default: ` pytest.ini ` directory)* .
147- - ** ` --shed-image-policy POLICY ` :** Image acquisition policy for building or pulling * (default: ` pull ` )* .
148- - ** ` --shed-skip-bootstrap ` :** Skip Docker bootstrapping * (useful for unit tests)* .
149- - ** ` --shed-container-logs ` :** Print container logs on failure * (default: disabled)* .
180+ - ** ` --shed-image-policy POLICY ` :** image acquisition policy * (default: ` pull ` )* .
181+ - ** ` --shed-skip-bootstrap ` :** skip Docker bootstrapping * (useful for unit tests)* .
182+ - ** ` --shed-container-logs ` :** print container logs on failure * (default: disabled)* .
150183
151184> [ !NOTE]
152185> When TestShed is installed globally, you must explicitly enable it per suite with
@@ -157,10 +190,10 @@ TestShed extends `pytest` with options to control the Docker environment:
157190
158191The ` --shed-image-policy ` option controls how TestShed acquires Docker images:
159192
160- - ** ` pull ` ** * (default)*** : ** Pull image if not found locally, build as fallback.
161- - ** ` build ` :** Build only if image doesn't exist locally.
162- - ** ` require ` :** Require existing local image * (fails if not found)* .
163- - ** ` rebuild ` :** Always rebuild the image.
193+ - ** ` pull ` ** * (default)* : pull image if not found locally, build as fallback.
194+ - ** ` build ` :** build only if image doesn't exist locally.
195+ - ** ` require ` :** require existing local image * (fails if not found)* .
196+ - ** ` rebuild ` :** always rebuild the image.
164197
165198#### Examples
166199
0 commit comments