Hands-On Docker for Microservices with Python
上QQ阅读APP看书,第一时间看更新

Testing the container

We will start by creating a service to run the unit tests. Keep in mind that the tests need to run inside the container. This will standardize the execution of them and ensure that the dependencies are constant.

Note that, in the creation of our container, we include all the requirements to execute the tests. There's the option to create the running container and inherit from it to add the tests and test dependencies.

This certainly creates a smaller running container but creates a situation where the testing container is not 100% exactly the same as the one in production. If the size is critical and there's a big difference, this may be an option, but be aware of the differentiation if there's a subtle bug.

We need to define a service in the docker-compose.yaml file, in this way:

version: '3.7'

services:
# Development related
test-sqlite:
environment:
- PYTHONDONTWRITEBYTECODE=1
build:
dockerfile: docker/app/Dockerfile
context: .
entrypoint: pytest
volumes:
- ./ThoughtsBackend:/opt/code

This section defines a service called test-sqlite. The build defines the Dockerfile to use and the context, in the same way as we'd do with a docker build command. docker-compose automatically sets the name.

We can build the container with the following command:

$ docker-compose build test-sqlite
Building test-sqlite
...
Successfully built 8751a4a870d9
Successfully tagged ch3_test-sqlite:latest

entrypoint specifies the command to run, in this case, running the tests through the pytest command.

There are some differences between the command and the entrypoint, which both execute a command. The most relevant ones are that command is easier to overwrite and entrypoint appends any extra arguments at the end.

To run the container, call the run command:

$ docker-compose run test-sqlite
=================== test session starts ===================
platform linux -- Python 3.6.8, pytest-4.5.0, py-1.8.0, pluggy-0.12.0 -- /opt/venv/bin/python3
cachedir: .pytest_cache
rootdir: /opt/code, inifile: pytest.ini
plugins: flask-0.14.0
collected 17 items

tests/test_thoughts.py::test_create_me_thought PASSED [ 5%]
...
tests/test_token_validation.py::test_valid_token_header PASSED [100%]

========== 17 passed, 177 warnings in 1.25 seconds ============
$

You can append pytest arguments that will be passed over to the internal entrypoint. For example, to run tests that match the validation string, run the following command:

$ docker-compose run test-sqlite -k validation
...
===== 9 passed, 8 deselected, 13 warnings in 0.30 seconds =======
$

There are two extra details: the current code is mounted through a volume and overwrites the code in the container. See how the current code in ./ThoughtsBackend is mounted in the position of the code in the container, /opt/code. This is very handy for the development, as it will avoid having to rebuild the container each time a change is made.

This also means that any write in the mounted directory hierarchy will be saved in your local filesystem. For example, the ./ThoughtsBackend/db.sqlite3 database file allows you to use it for testing. It will also store generated pyc files.

The generation of the db.sqlite3 file can create permission problems in some operating systems. If that's the case, delete it to be regenerated and/or allow it to read and write to all users with chmod 666 ./ThoughtsBackend/db.sqlite3.

That's why we use the environment option to pass a PYTHONDONTWRITEBYTECODE=1 environment variable. This stops Python from creating pyc files.

While SQLite is good for testing, we need to create a better structure reflective of the deployment and to configure the access to the database to be able to deploy the server.