Dockerを本番環境で利用する場合、ユニットテストとイメージの関係は次のようにいくつか考えられます。
- ユニットテストをパスしたコードで本番用イメージを作成する
- テストコードを含むイメージを作成しユニットテストを実行。通ったものを本番で利用する。
- テストコードを除いたコードでイメージを作成し、そのイメージをテストをする。通ったものを本番で利用する。
今回は下記の理由から3.の方法をやってみました。
- テストをパスしたイメージをそのまま本番環境で利用したい
- テストコード自体は本番環境では不要
- イメージは小さければ小さいほどPullやPushなどの取り回しが楽になる
テスト対象のコードとテストコードの用意
テストコードを含むパッケージを用意します。
今回用意したパッケージ構成はこちらです。
$ tree
.
├── Dockerfile
└── pkg
├── __init__.py
├── main.py
└── tests
├── __init__.py
└── test_main.py
- Dockerfile
FROM python:3.6-alpine
WORKDIR /usr/src/app
COPY pkg ./
CMD ["python3", "main.py"]
- pkg/main.py
def hello(name):
return f'Hello, {name}'
def main():
print(hello('rhoboro'))
if __name__ == '__main__':
main()
- pkg/tests/test_main.py
import unittest
class MainTestCase(unittest.TestCase):
def test_hello(self):
from main import hello
expected = 'Hello, rhoboro'
actual = hello('rhoboro')
self.assertEquals(expected, actual)
Dockerイメージのビルド
まずは普通にビルドして動くことを確認します
$ docker build -t ham:0.1 .
Sending build context to Docker daemon 7.68kB
Step 1/4 : FROM python:3.6-alpine
---> 449d3495be0e
Step 2/4 : WORKDIR /usr/src/app
---> Using cache
---> 99a7c8425bb2
Step 3/4 : COPY pkg ./
---> a86f831af210
Step 4/4 : CMD ["python3", "main.py"]
---> Running in 5b578ba7c354
Removing intermediate container 5b578ba7c354
---> c8f333e587c9
Successfully built c8f333e587c9
Successfully tagged ham:0.1
# 動くことを確認
$ docker run -it --rm ham:0.1
Hello, rhoboro
続いて、 .dockerignore
を用意して、テストコードをイメージから除外します
pkg/tests
test_libs # あとで必要になるので先に書いておく
再度ビルドしてみると、2行目でDockerデーモンに送られるデータ量が 7.67kB -> 5.12kB に減っていることがわかります。
$ docker build -t ham:0.1 .
Sending build context to Docker daemon 5.12kB
Step 1/4 : FROM python:3.6-alpine
---> 449d3495be0e
Step 2/4 : WORKDIR /usr/src/app
---> Using cache
---> 99a7c8425bb2
Step 3/4 : COPY pkg ./
---> 42326153a6ce
Step 4/4 : CMD ["python3", "main.py"]
---> Running in 6d4895773904
Removing intermediate container 6d4895773904
---> 9ac09ac79374
Successfully built 9ac09ac79374
Successfully tagged ham:0.1
テストコードを除外したイメージのテストを実行する
ここからが本題です。このテストコードを除外してビルドしたイメージをテストします。
まずカレントディレクトリにテストでのみ利用するライブラリ(ここでは pytest )をインストールします。
pipの-t
オプションで先ほど.dockerignore
に記載したtest_libs
を指定します。
ここで、あえてpython:3.6-alpineのpip
を利用しているのはこれらのライブラリが利用される環境がイメージの内部だからです。(DockerfileのFROM句で指定したもの)
# ホストの ./test_libs にインストールされる
$ docker run -it --rm -v `pwd`:/usr/src/app \
-w /usr/src/app \
python:3.6-alpine pip install -t test_libs pytest
~ 省略 ~
このtest_libs/
とテストコードをマウントし、テストを実行してみます。
test_libs/
はPYTHONPATH
に追加しないと認識されません。
# ./test_libs をPYTHONPATHに追加して pytest を実行する
$ docker run -it --rm -v `pwd`/pkg/tests:/usr/src/app/tests \
-v `pwd`/test_libs:/usr/src/test_libs \
-e PYTHONPATH=/usr/src/test_libs \
ham:0.1 python -m pytest -v
=================================================================================================== test session starts ====================================================================================================
platform linux -- Python 3.6.6, pytest-3.7.4, py-1.6.0, pluggy-0.7.1 -- /usr/local/bin/python
cachedir: .pytest_cache
rootdir: /usr/src/app, inifile:
collected 1 item
tests/test_main.py::MainTestCase::test_hello PASSED [100%]
================================================================================================= 1 passed in 0.07 seconds =================================================================================================
テストがパスしたので成功です。 これでDockerイメージに不要なコードを含めず、テストにパスしたイメージをそのまま安心して本番環境で動かすことができます。