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:
importpytest@pytest.fixturedefubuntu(spawnu):# Get `spawnu` attached to ubuntu container with installed python and# where bash ranproc=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')returnprocdeftest_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 - 1assertubuntu.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])defubuntu(request,spawnu):python_version=request.param# Get `spawnu` attached to ubuntu container with installed python and# where bash randockerfile=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))returnproc
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:
importpytest@pytest.fixture(params=[2,3])defubuntu(request,spawnu,run_without_docker):python_version=request.param# Get `spawnu` attached to ubuntu container with installed python and# where bash randockerfile=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:ifnotrun_without_docker:# Your source root is available in `/src`proc.sendline(u'pip{} install /src'.format(python_version))returnproc@pytest.mark.once_without_dockerdeftest_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 - 1assertubuntu.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_dockerdeftest_broke_config(ubuntu,TIMEOUT):ubuntu.sendline(u'{invalid} > ~/.app/config.json')ubuntu.sendline(u'app')assertubuntu.expect([TIMEOUT,u'Config was broken!'])