I am trying to run a Python unit test within a docker image.
This is the Dockerfile used to create the image
FROM python:3.8-alpine
COPY lambda/ /app
WORKDIR /app
RUN pip install -r requirements.txt
RUN pip install unittest-xml-reporting
CMD []
and I try to run with
docker run -i --rm
-v ${WORK_DIR}/lambda/junit-reports:/app/junit-reports
pd-engineering-metrics:test_python38
python -m xmlrunner --output-file junit-reports/test-file.xml *_test.py
The error I get is
Running tests...
----------------------------------------------------------------------
E
======================================================================
ERROR [0.002s]: *_test (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: *_test
Traceback (most recent call last):
File "/usr/local/lib/python3.8/unittest/loader.py", line 154, in loadTestsFromName
module = __import__(module_name)
ModuleNotFoundError: No module named '*_test'
----------------------------------------------------------------------
Ran 1 test in 0.002s
FAILED (errors=1)
But, if I enter the running Image and manually trigger the test it works
docker run -it pd-engineering-metrics:test_python38 /bin/sh
/app # python -m xmlrunner --output-file junit-reports/test-file.xml *_test.py
Running tests...
Ran 15 tests in 0.024s
OK
Generating XML reports
Any idea what could be wrong here?
EDIT: I found a way to make it work, it doesn’t answer the question though.
Changing the command line to the following works
python -m xmlrunner --output-file junit-reports/test-file.xml discover -s /app -p "*_test.py"
2
Answers
TLDR; Because of the differences between exec and shell form in how CMD works.
ref:
https://docs.docker.com/engine/reference/commandline/run/
https://docs.docker.com/engine/reference/commandline/exec/
https://docs.docker.com/engine/reference/builder/#cmd
Normally if you run a command like
python -m xmlrunner *_test.py
, your local shell expands that wildcard into a list of filenames and explicitly passes them as arguments to the process. In the example you’ve shown, it looks like your test files are in alambda
subdirectory, so the wildcard doesn’t match anything. The shell passes the string*_test.py
directly as an argument to the container, and the container directly runs thepython
command without invoking another shell, which results in the error you get.The "classic" workaround to this is to wrap your command in a
sh -c
invocation, single-quoting the command stringThe approach you landed on uses the
unittest -p
option. Instead of passing the names of the test files, this passes in a pattern that matches their names. This avoids the problem of the host and container filesystems being different, and causes the lookup to happen inside the container filesystem. This will work fine too, and is probably a little easier than thesh -c
wrapper.(I’d also consider running unit tests directly on the host, in a Python virtual environment, without involving Docker at all.)