It’s very useful to run functional tests in a clean environment, like a fresh Docker container, and I wrote about this before, and now it was formalized in a simple py.test plugin — pytest-docker-pexpect.
It provides few useful fixtures:
spawnu
–pexpect.spawnu
object attached to a container, it can be used to interact with apps inside the container, read more;TIMEOUT
– a special object, that can be used in assertions those checks output;run_without_docker
– indicates that tests running without Docker, when py.test called with--run-without-docker
.
And some marks:
skip_without_docker
– skips test when without Docker;once_without_docker
– runs parametrized test only with a first set of params when without Docker.
It’s easier to show it in examples. So, first of all, just test some app --version
argument inside an Ubuntu container:
import pytest
@pytest.fixture
def ubuntu(spawnu):
# Get `spawnu` attached to ubuntu container with installed python and
# where bash ran
proc = spawnu(u'example/ubuntu',
u'''FROM ubuntu:latest
RUN apt-get update
RUN apt-get install python python-dev python-pip''',
u'bash')
# Sources root is available in `/src`
proc.sendline(u'pip install /src')
return proc
def test_version(ubuntu, TIMEOUT):
ubuntu.sendline(u'app --version')
# Asserts that `The App 2.9.1` came before timeout,
# when timeout came first, `expect` returns 0, when app version - 1
assert ubuntu.expect([TIMEOUT, u'The App 2.9.1'])
Looks simple. But sometimes we need to run tests in different environments, for example —
with different Python versions. It can be easily done by just changing ubuntu
fixture:
@pytest.fixture(params=[2, 3])
def ubuntu(request, spawnu):
python_version = request.param
# Get `spawnu` attached to ubuntu container with installed python and
# where bash ran
dockerfile = u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install python{version} python{version}-dev python{version}-pip
'''.format(version=python_version)
proc = spawnu(u'example/ubuntu', dockerfile, u'bash')
# Your source root is available in `/src`
proc.sendline(u'pip{} install /src'.format(python_version))
return proc
And sometimes we need to run tests in Docker-less environment, for example —
in Travis CI container-based infrastructure.
So here’s where --run-without-docker
argument comes handy. But we don’t need to
run tests for more than one environment in a single Travis CI run, and we don’t need
to make some installation steps. So there’s place for once_without_docker
mark and run_without_docker
fixture, test with them will be:
import pytest
@pytest.fixture(params=[2, 3])
def ubuntu(request, spawnu, run_without_docker):
python_version = request.param
# Get `spawnu` attached to ubuntu container with installed python and
# where bash ran
dockerfile = u'''
FROM ubuntu:latest
RUN apt-get update
RUN apt-get install python{version} python{version}-dev python{version}-pip
'''.format(version=python_version)
proc = spawnu(u'example/ubuntu', dockerfile, u'bash')
# It's already installed if we run without Docker:
if not run_without_docker:
# Your source root is available in `/src`
proc.sendline(u'pip{} install /src'.format(python_version))
return proc
@pytest.mark.once_without_docker
def test_version(ubuntu, TIMEOUT):
ubuntu.sendline(u'app --version')
# Asserts that `The App 2.9.1` came before timeout,
# when timeout came first, `expect` returns 0, when app version - 1
assert ubuntu.expect([TIMEOUT, u'The App 2.9.1'])
Another often requirement — skip some tests without docker, some destructive tests.
It can be done with skip_without_docker
mark:
@pytest.mark.skip_without_docker
def test_broke_config(ubuntu, TIMEOUT):
ubuntu.sendline(u'{invalid} > ~/.app/config.json')
ubuntu.sendline(u'app')
assert ubuntu.expect([TIMEOUT, u'Config was broken!'])