テストコードを除外したDockerイメージをテストする

Posted by rhoboro on 2018-09-07

Dockerを本番環境で利用する場合、ユニットテストとイメージの関係は次のようにいくつか考えられます。

  1. ユニットテストをパスしたコードで本番用イメージを作成する
  2. テストコードを含むイメージを作成しユニットテストを実行。通ったものを本番で利用する。
  3. テストコードを除いたコードでイメージを作成し、そのイメージをテストをする。通ったものを本番で利用する。

今回は下記の理由から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イメージに不要なコードを含めず、テストにパスしたイメージをそのまま安心して本番環境で動かすことができます。