テスト ツールのコンテナー化: Dockerfile とメイクファイルの作成

アプリケーションをコンテナ化することの利点についてはよく耳にしますが、システム全体をテストすることを目的としたアプリケーションやアプリケーションの テストをコンテナ化することの利点についてはあまり耳 にしません。 コンテナ内でアプリケーションのテストを実行すると、アプリケーションが期待どおりに実行されていることをより確実に感じることができます。 さらに、テストを Docker イメージとしてパッケージ化することで、複数のマシン間でテストをすばやく実行し、一貫性のある環境を確保できます。 この投稿では、テストツーリングアプリケーションのコンテナ化と、ツールの単体テストと統合テストのコンテナ化について説明し、これらの利点を示します。

ツーリング

コンテナー化されるテスト ツールは、構成の異なる 5 つのオペレーティング システムの現在のマトリックス全体で、Docker エンジン、ユニバーサル コントロール ウィンドウ、および Docker Trusted レジストリを含む Docker EE スタックのストレス テストに使用される一連のツールです。 ツール自体には、いくつかの重要なプロパティがあります。

  1. 独自の仮想 Python 環境で Python 3 で記述されています
  2. 2つの外部クライアントバイナリを使用
  3. システムテストを実行するための外部構成ファイルへの3つの依存関係があります
  4. 独自の単体テストと統合テストに合格する
  5. すべてのシステムテストのログを収集します

これらのプロパティは、ツールがコンテナー化されているかどうかに関係なく存在しますが、このツールをコンテナー化することで、開発、コード管理、コラボレーションがどのように容易になるかを見ていきます。この投稿には多くの「テスト」用語があるので、共通のベースから始めるために、それぞれのタイプのテストが私たちの目的のために何を表しているのかを説明します。

システムツールボックス: 実際のアプリケーション/ツール。 これは、コンテナ化する完全なアプリケーションと考えることができます。

単体テスト: これらは、アプリケーションの単体テストです。 外部依存関係はすべてモックされます。コンテナ内で単体テストを実行します。

統合テスト:これらは、ライブシステムに依存する統合テストです。 これらのテストは、コンテナー内でも実行されます。

ドッカーファイルの作成

私たちのドッカーファイル

1 パイソンから:3.7.1
2
3 コピー ./client-binaries/Linux/<binary_1> /usr/local/bin/<binary_1>
4 COPY ./client-binaries/Linux/docker-18.09.4 /usr/bin/docker
5
6 ワークディレクトリ/system_toolbox
7 コピー ./pip-requirements/requirements.txt ./proj/requirements.txt
8 RUN pip install -r ./proj/requirements.txt
9
10 コピー ./統合テスト ./統合テスト11 コピー ./テストケース ./テストケース12
13 COPY ./sut-configs ./sut-configs14 コピー ./<binary_1>-configs ./<binary_1>-configs15
16 コピー ./ツールボックス 。/ツールボックス17
18 コピー ./メイクファイル 。/メイクファイル19 コピー ./README.md ./README.md
20 コピー ./setup.py ./setup.py
21 RUN make install-dev
22

コンテナー内のアプリケーションを実行するには、イメージに基づいている必要があります。 現在作業しているプロジェクトはPython 3で書かれているので、ディレクティブを使用して FROM python:3.7.1 公式に維持されているPythonイメージからビルドします。 これは私たちの親であり、ベースイメージと呼ばれることもあります。 私たちは 3.7.1 バージョンのイメージを使っており、Debian Linux (stretch とも呼ばれます) OS を提供しています。 これは、ツールの一部が Alpine Linux バージョンには含まれていない SSH クライアントに依存しているためであり、Python のパッケージマネージャー pip を介して Python SSH モジュールである Paramiko をインストールできる便利さを求めているためです。

前述のように、このプロジェクトは 2 つの外部クライアント バイナリに依存しています。 ディレクティブを使用して COPY <src> <dst> 、これらを Dockerfile に含めます。 このコマンドは、指定された宛先パスにあるイメージのファイルシステムにバイナリをコピーします。 使用 COPY とその代替コマンドADDの違いについてのここでのメモ。 Dockerのベストプラクティスアプローチを使用しますがADD、DockerCOPYイメージ内のディレクトリとして自動的に解凍されるソースパスからtarアーカイブを移動するなど、いくつかの追加機能があります。したがって、ファイルやディレクトリをコピーし、Dockerfileを過剰にエンジニアリングしないという目的のために、 COPY 適切なコマンドです。

クライアントバイナリの追加以外のすべてのコマンドは内部 /system_toolboxにあるため、 WORKDIR ディレクティブを /system_toolboxに設定します。 これにより、コンテナ /system_toolbox 内の現在の作業ディレクトリが設定され、Dockerfile内の追加のテキストをクリーンアップできます。 たとえば、Dockerfile の 6 行目から 16 行目を次のように記述できます。

6 コピー ./統合テスト /system_toolbox/統合テスト
7 コピー ./テストケース /system_toolbox/テストケース
8
9 COPY ./sut-configs /system_toolbox/sut-configs
10 コピー ./<binary_1>-設定 /system_toolbox/<binary_1>-configs
11
12 コピー ./ツールボックス /system_toolbox/ツールボックス
13

これはうまくいくでしょう。ただし、読みにくく、親ディレクトリ構造に変更がある場合は、すべての行でこのパスを変更する必要があります。 代わりに、ディレクティブを使用して、 WORKDIR 次のように各ステートメントを短縮できます。

6 ワークディレクトリ/system_toolbox
7 コピー ./pip-requirements/requirements.txt ./proj/requirements.txt
8 RUN pip install -r ./proj/requirements.txt
9
10 コピー ./統合テスト ./統合テスト11 コピー ./テストケース ./テストケース12
13 COPY ./sut-configs ./sut-configs14 コピー ./<binary_1>-configs ./<binary_1>-configs15
16 コピー ./ツールボックス 。/ツールボックス17

作業ディレクトリが設定されると、作業ディレクトリが変更されない限り、次のコマンドはすべてそのディレクトリから実行されます。 ドッカーファイルの次の 2 行:

7 コピー ./pip-requirements/requirements.txt ./proj/requirements.txt
8 RUN pip install -r ./proj/requirements.txt

Python 要件ファイルをコピーし、ディレクティブを使用してこれらの要件をすべてインストールします RUN 。 この方法で RUN を呼び出すと、次のコマンド RUN がシェルで実行され、Linux コンテナーでは既定で実行されます /bin/sh -c

次の Dockerfile 行は、テスト、ライブラリ コード、および必要な構成ファイルを設定するためにツールによって使用されます。

# ツールのコード精度をテストするための統合テスト
コピー ./統合テスト ./統合テスト# ツールが実行するシステムテストケース
コピー ./テストケース ./テストケース# ツールの設定ファイル
COPY ./sut-configs ./sut-configs# ツールの設定ファイル
コピー ./<binary_1>-configs ./<binary_1>-configs# ツーリングライブラリ
コピー ./ツールボックス 。/ツールボックス

そして最後に、ドッカーファイルの最後の2行:

20 コピー ./setup.py ./setup.py
21 RUN make install-dev

Python セットアップ スクリプトをコピーしてツールをパッケージ化して実行します make install-devが、詳細については Makefile セクションで説明します。

メイクファイルの作成

メイクファイルの作成は、多くの場合、冒険です。 このセクションでは、ツールの実行を可能にする主要なコンポーネントのいくつかについて説明します。 また、コンテナ内のツールをテストする単体テストと統合テストについても見ていきます。 Makefile のこの部分は、インストール ターゲット、Docker ターゲット、テスト ターゲット、ユーティリティの 4 つのセクションに分かれています。 以下の Makefile の抜粋の後には、ユーティリティターゲットがバージョンチェックを行うため、最初の 3 つのセクションの説明が続きます。

1メジャー?=0
2マイナー?=1
3
4 バージョン=$(メジャー).$(マイナー)
5
6 APP_NAME = "システムツールボックス"
7
8#私たちのドッカーハブアカウント名
9#HUB_NAMESPACE = "<hub_name>"
10
11 #ドッカーファイルの場所
12 DOCKER_FILE_DIR = "ドッカーファイル"
13 DOCKERFILE = "${DOCKER_FILE_DIR}/Dockerfile"
14
15 IMAGE_NAME = "${APP_NAME}"
16 CUR_DIR = $(シェル エコー "${PWD}")
17
18#Pythonフォーマットチェッカー用。 デフォルトは 78 です
19 PEP8_MAX_LINE_LENGTH = 99
20
21#ユニットテストオプション
22 NOSETEST_OPTS = --冗長性=2 --include='.*_test.py' --詳細エラー --どこ=ツールボックス
23 COVERAGE_OPTS = --カバレッジあり --カバーパッケージ=ツールボックス --カバー-html --カバーを含む --カバーテスト --カバー消去 \
24 --cover-html-dir=../テスト結果/単体テストコードカバレッジ
25
26 #################################
27#インストールターゲット
28 #################################
29 .PHONY: install-dev
31 インストール開発:
32 @echo "+ $@"
33
34 @pip -e をインストールします。35
36 .偽物:ピップフリーズ
37 @echo "+ $@"
38 @pipフリーズ|grep -v system_toolbox > pip-requirements/requirements.txt
39
40
41 #################################
42#ドッカーターゲット
43 #################################
44 .偽物:クリーンイメージ
45 クリーンイメージ: バージョンチェック
46 @echo "+ $@"
47 @docker rmi ${HUB_NAMESPACE}/${IMAGE_NAME}:latest ||真
48 @docker rmi ${HUB_NAMESPACE}/${IMAGE_NAME}:${VERSION} ||真
49
50 .偽物:画像
51 画像: バージョン チェック
52 @echo "+ $@"
53 @docker build -t ${HUB_NAMESPACE}/${IMAGE_NAME}:${VERSION} -f ./${DOCKERFILE} .54 @docker tag ${HUB_NAMESPACE}/${IMAGE_NAME}:${VERSION} ${HUB_NAMESPACE}/${IMAGE_NAME}:latest
55 @echo 「完了」。
56 @docker画像 --フォーマット '{{.Repository}}:{{.Tag}}\t\t Built: {{.CreatedSince}}\t\tSize: {{.サイズ}}' |\
57 grep ${IMAGE_NAME}:${VERSION}
58
59 .偽物:プッシュ
60プッシュ:クリーン画像
61 @echo "+ $@"
62 @docker push ${HUB_NAMESPACE}/${IMAGE_NAME}:${VERSION}
63 @docker push ${HUB_NAMESPACE}/${IMAGE_NAME}:latest
64
65 #################################
66#テストターゲット
67 #################################
68 .偽物:テストユニット
69 テストユニット:
70 @echo "+ $@"
71 鼻テスト ${NOSETEST_OPTS}
72
73 .フォニー:チェックFMT
74 #check-FMT: 画像
75 チェックFMT:
76 @echo "+ $@"
77 pycodestyle --ファイル名='*.py' --exclude='*.sh,*.md,*.txt,Makefile,*.swp' --max-line-length=${PEP8_MAX_LINE_LENGTH} *
78
79 .フォニー:テストスタティック
80テスト静的:
81 @echo "+ $@"
82 パイリント-d重複コードテストケース
83 パイリントツールボックス
84 パイリント統合テスト
85
86 .偽物:すべてテストします
87テストオール:チェックFMTテスト静的テストユニット
88
89 .偽物:テストコンテナ
90テストコンテナ:画像
91 @echo "+ $@"
92 @docker run --rm --name toolbox-unit-tests ${HUB_NAMESPACE}/${IMAGE_NAME}:latest make test-all
93 @docker run --rm --name toolbox-int --volume ${CUR_DIR}/results:/root/logs -e REGISTRY_USERNAME=foo -e REGISTRY_PASSWORD=bar \
94 ${HUB_NAMESPACE}/${IMAGE_NAME}:latest python ./integration-tests/testbed_validation.py
95
96 .フォニー:統合-静的
97統合-静的:画像
98 @echo "+ $@"
99 @docker run --rm --name toolbox-int --volume ${CUR_DIR}/results:/root/logs -e REGISTRY_USERNAME=foo -e REGISTRY_PASSWORD=bar \
100 ${HUB_NAMESPACE}/${IMAGE_NAME}:latest python ./integration-tests/testbed_validation.py
101 #@python ./integration-tests/testbed_validation.py
102
103 .PHONY:統合-テストベッド-調査
104統合テストベッド調査:
105 @echo "+ $@"
106 @python ./integration-tests/testbed_validation.py --topology-filter=poc_ --sut-filter=${TESTBED_SURVEY_SUT} \
107 --create-system ${INTEGRATION_PERSONA} ${DRY_RUN}
108
109
110 #################################
111#ユーティリティ
112 #################################
113
114 .PHONY: バージョン チェック115バージョンチェック:
116 @echo "+ $@"
117 if [ -z "${VERSION}" ];そうしたら\
118エコー「バージョンが設定されていません」;\
119 誤り ;\
120 その他 \
121 echo "VERSION is ${VERSION}";\
122 fi
123

インストール ターゲット

インストール ターゲットには 2 つのコンポーネントがあります。 最初の install-devは Dockerfile から実行されます。 RUN make install-devがドッカーファイルで実行されると、実行されますpip install -e . コンテナ内。 これは楽しいコマンドであり、詳細を実際に把握するには少し掘り下げる必要がありました。

このコマンドの利便性を把握するために、ここではPython仮想環境について少し脇に置きます。 Python アプリケーションを作成する場合は、Python パッケージと拡張機能を配布するメカニズムである Distutils で使用できるアプリケーションの説明を提供するファイルを用意 setup.py するのが一般的です。 より具体的には、Pythonのドキュメントから「セットアップスクリプトは、Distutilsを使用してモジュールをビルド、配布、およびインストールするすべてのアクティビティの中心です。 セットアップスクリプトの主な目的は、モジュールを操作するさまざまなコマンドが正しいことをするように、Distutilsにモジュールの配布を記述することです。」

ただし、Pythonを使用する場合は、仮想環境で実行するようにすべてを設定することがよくあります。 コードを変更するたびに作業ディレクトリを仮想環境にリンクするために繰り返すpython setup.py install必要があるのは少し面倒な場合があるため、存在するディレクトリsetup.pyで実行できますpip install -e .。これにより、変更を加えるファイルと仮想環境のパッケージの間にsudoシンボリックリンクが作成されるため、最新バージョンのPythonアプリケーションを実行できます。

インストール ターゲット pip-freezeの 2 番目の部分 は楽しく、少しわかりやすくなっています。 コンテナ内でテストを実行する必要はありませんが、Pythonで開発するときに知っておくと便利なコマンドです。 アプリケーションが適切な数のパッケージを蓄積している場合、 で requirements.txtすべてのバージョンを自分で管理するのが難しい場合があります。 このコマンドは、アプリケーションと互換性のあるすべてのパッケージバージョンを取得し、アプリケーションのを requirements.txt 自動的に更新するための優れた方法です。

ドッカーターゲット

Docker ターゲットでは、イメージの作成と保守について説明します。 最初の clean-image ものは前提条件として実行 version-check され、次に最新バージョンと選択したバージョンの両方を削除します。 イメージを削除しようとしたときにイメージが存在しないというエラーが表示されても、テストツールがここでシャットダウンすることはありません。したがって、構文を使用します command  || trueブール関数は常にtrueを返し、スクリプトが不必要に中止するのを防ぎます。

イメージの構築とタグ付けには、 image ターゲットを使用します。 これにより、前提条件のバージョン チェックも実行され、指定された HUB_NAMESPACE, IMAGE_NAMEを使用して VERSION 、前に作成した Dockerfile からイメージがビルドされます。 次に、イメージにタグが付けられ、イメージが正しくビルドされ、 docker images タグ付けされたことを確認するために実行されます。

そして最後に、私たちのイメージを clean-image プッシュするための、両方が image 前提条件です。 次に、最新バージョンと最新のタグ付きイメージの両方が Docker Hub にプッシュされます。 Docker Hubにログインしている場合は、プッシュが自動的に完了し、それ以外の場合は、この手順でログイン資格情報を入力する必要があります。

テスト対象

「テスト ターゲット」セクションでは、単体テスト、統合テスト、およびアプリケーションをローカルで実行してコンテナーで実行するためのシステム ツールのターゲットについて説明します。

最初のターゲット test-unit は、Python 用の Nose テスト パッケージを使用してシステム テスト コードに対して単体テストを実行し、Python 形式チェック パッケージ Pycodestyle を使用してツールボックス コードのスタイル規則をチェックし、システム テスト ケース、システム ツールボックス コード、 check-fmt test-static および統合テストに対して Python リンター Pylint を実行します。

最後の 2 つのターゲットは、Dockerfile で前に作成したイメージを使用してコンテナーでテストを実行するターゲットです。 ターゲット test-container は単体テストを実行し、続いて統合テストを実行します。 コマンド:

92 docker run --rm --name toolbox-unit-tests ${HUB_NAMESPACE}/${IMAGE_NAME}:latest make test-all

フラグを使用して--rmプロセスが完了したらコンテナを削除し、フラグを使用してコンテナに特定の名前を指定し、イメージ${HUB_NAMESPACE}/${IMAGE_NAMEの最新バージョンを選択します}、--name:latestおよびコマンドmake test-allを送信します。test-unitcheck-fmttest-staticdocker run

統合テストは、以下を呼び出すことによって実行されます。

93 docker run --rm --name toolbox-int --volume ${CUR_DIR}/results:/root/logs -e REGISTRY_USERNAME=foo -e REGISTRY_PASSWORD=bar \
94 ${HUB_NAMESPACE}/${IMAGE_NAME}:latest python ./integration-tests/testbed_validation.py

これにより、システム ツールボックスに対して統合テストが実行されます。 また、Dockerボリュームを作成して、アプリケーションから作成されたログファイルをローカルマシンに転送し、コンテナが削除されたときに表示します。 最後に、実行する integration-static と、コンテナー内の静的統合テストのみが実行され integration-testbed-survey 、ライブ システムで統合テストが実行されます。

コンテナー化されたテストを実行する方法

この設定の後、私たちは今、私たちの仕事の報酬を享受することができます。 コンテナー内ですべてのテストを実行する方法について説明します。ツールボックスに対していくつかのシステムテストを実行したいとしましょう。この間に誰かがたまたまツールボックスライブラリを変更した場合、パッケージのバージョンが変更されたり、パイプライン内の別のPRと無意識のうちに競合するライブラリ機能を追加したりした場合、これは常に楽しい「.... まあそれは私のマシンで動作します」という応答。

これを軽減するために、Docker Hubリポジトリにプッシュされているため、 make test-container 完全に機能していると確信しているイメージから最初に単体テストを実行できます。 次に、信頼できるイメージに対しても統合テストを実行します。 これにより、マシン間で同じ環境でテストが実行されるようになり、一般的に開発が迅速になり、コーディングエクスペリエンスがより楽しくなります。 ディレクトリ${CUR_DIR}/resultsをにリンク/root/logsするボリュームを作成したので、コンテナでの実行中にアプリケーションによって生成されたすべてのログファイルにアクセスすることもできます。

今後は、コンテナーでさまざまなテストを実行できるため、柔軟性が大幅に向上します。 異なるマシン間で一貫した環境でテストを実行し、潜在的な競合を回避できることに加えて、ソースコードとテストコードの一貫性を維持しながら、これらのテストを定期的に実行する自動Jenkinsジョブの設定に適しています。