CodePipelineにテストユニットを設定する
CodePipelineにテストユニットを追加することができます。
テストユニットはCodeBuildで作成できます。
今回はDockerイメージを作成し、ECRリポジトリにプッシュするパイプラインをCodePipelineで構築します。
その過程にテストユニットを追加することを目的とします。
構築する環境
CodePipelineを構成し、3つのリソースを連携させます。
1つ目はCodeCommitです。
CodeCommitはCodePipelineのソースステージを担当します。
Gitリポジトリとして使用します。
2つ目はCodeBuildです。
こちらのCodeBuildはCodePipelineのテストステージを担当します。
今回のテスト対象はPythonスクリプトとし、テストで使用するツールはpytestとします。
3つ目もCodeBuildです。
こちらのCodeBuildはCodePipelineのビルドステージを担当します。
CodeCommitにプッシュされたコードから、Dockerイメージをビルドします。
ビルドしたイメージをECRにプッシュします。
SSMパラメータストアにDockerHubアカウント情報を保存します。
DockerBuildでイメージを生成する際に、DockerHubにサインインした上でベースイメージをプルするために、これらを使用します。
CodePipelineが開始されるきっかけですが、CodeCommitへのプッシュを条件とします。
具体的には、EventBridgeに上記を満たすルールを用意します。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/081
テンプレートファイルのポイント解説
本ページはCodePipelineで作成したパイプライン内に、テストユニットを追加することを目的とします。
CodePipelineに関する基本的な事項については、以下のページをご確認ください。
CloudFormationカスタムリソースを使用して、CloudFormationスタック削除時に、自動的にS3バケット内のオブジェクトと、ECRリポジトリ内のイメージを削除します。
詳細につきましては、以下のページをご確認ください。
ReportGroup
Resources:
CodeBuildReportGroup:
Type: AWS::CodeBuild::ReportGroup
Properties:
DeleteReports: true
ExportConfig:
ExportConfigType: NO_EXPORT
Name: !Sub "${CodeBuildProject1}-${ReportName}"
Type: TEST
Code language: YAML (yaml)
CodeBuildでテストユニットを実行するためには、レポートグループを作成します。
Typeプロパティでレポートグループのタイプを設定します。
「CODE_COVERAGE」を指定すると、コードカバレッジ、つまりコード網羅率のレポートを作成します。
「TEST」を指定すると、通常のテストレポートを作成します。
今回は後者を選択します。
DeleteReportsプロパティはレポートの削除に関するパラメータです。
これを有効化すると、グループ内部にレポートが残っていたとしても、グループとレポートをまとめて削除できるようになります。
ExportConfigはレポートグループのエクスポートに関するパラメータです。
生のレポートデータをS3バケットに出力することができます。
今回はエクスポートしません。
CodeBuild
Resources:
CodeBuildProject1:
Type: AWS::CodeBuild::Project
Properties:
Artifacts:
Type: CODEPIPELINE
Cache:
Type: NO_CACHE
Environment:
ComputeType: !Ref ProjectEnvironmentComputeType
EnvironmentVariables:
- Name: REPORT_NAME
Type: PLAINTEXT
Value: !Ref ReportName
Image: !Ref ProjectEnvironmentImage
ImagePullCredentialsType: CODEBUILD
Type: !Ref ProjectEnvironmentType
PrivilegedMode: true
LogsConfig:
CloudWatchLogs:
GroupName: !Ref LogGroup
Status: ENABLED
S3Logs:
Status: DISABLED
Name: !Sub "${Prefix}-project-01"
ServiceRole: !GetAtt CodeBuildRole.Arn
Source:
Type: CODEPIPELINE
BuildSpec: !Sub |
version: 0.2
phases:
install:
runtime-versions:
python: 3.7
commands:
- pip3 install pytest
- pip3 install bottle
build:
commands:
- python -m pytest --junitxml=reports/pytest_reports.xml
reports:
$REPORT_NAME:
files:
- pytest_reports.xml
base-directory: reports
file-format: JUNITXML
Visibility: PRIVATE
Code language: YAML (yaml)
Environmentプロパティでテストを実行する環境を設定します。
ComputeTypeプロパティでビルド環境のスペックを指定します。
今回は最小構成(メモリ4GB、2vCPU、ストレージ」50GB)の「BUILD_GENERAL1_SMALL」を指定します。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-compute-types.html
EnvironmentVariablesプロパティでビルド環境で使用できる環境変数を設定できます。
今回は先述のレポートグループの名前を設定します。
Imageプロパティはビルド環境を作成するためのDockerイメージを指定します。
今回はARM版のAmazon Linux 2である「aws/codebuild/amazonlinux2-aarch64-standard:2.0」を指定します。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html
ImagePullCredentialsTypeプロパティはイメージをプルするための権限を設定します。
「CODEBUILD」を設定すると、独自のクリデンシャルを使用することになります。
Typeプロパティでビルド環境のタイプを設定します。
今回はARM環境を意味する「ARM_CONTAINER」を設定します。
PrivilegedModeプロパティはDockerデーモンの実行に関するパラメータです。
以下の引用の通り、今回は有効化します。
Enables running the Docker daemon inside a Docker container. Set to true only if the build project is used to build Docker images. Otherwise, a build that attempts to interact with the Docker daemon fails.
AWS::CodeBuild::Project Environment
Sourceプロパティでbuildspec.ymlの内容を定義します。
pytestを使用して、Pythonスクリプトをテストします。
今回は以下のページを参考に設定を行いました。
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/test-report-pytest.html
以下がCodeBuild用のIAMロールです。
Resources:
CodeBuildRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action:
- sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser
Policies:
- PolicyName: PipelineExecutionPolicy
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- ssm:GetParameters
Resource:
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubPassword}"
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubUsername}"
- Effect: Allow
Action:
- s3:PutObject
- s3:GetObject
- s3:GetObjectVersion
- s3:GetBucketAcl
- s3:GetBucketLocation
Resource:
- !Sub "arn:aws:s3:::${BucketName}"
- !Sub "arn:aws:s3:::${BucketName}/*"
- Effect: Allow
Action:
- codebuild:CreateReportGroup
- codebuild:CreateReport
- codebuild:UpdateReport
- codebuild:BatchPutTestCases
- codebuild:BatchPutCodeCoverages
Resource:
- !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/*"
- Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
Resource:
- !GetAtt LogGroup.Arn
- !Sub
- "${LogGroupArn}:log-stream:*"
- LogGroupArn: !GetAtt LogGroup.Arn
Code language: YAML (yaml)
レポートグループに関する権限を付与します。
CodePipeline
Resources:
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
ArtifactStore:
Location: !Ref BucketName
Type: S3
Name: !Ref Prefix
RoleArn: !GetAtt CodePipelineRole.Arn
Stages:
- Actions:
- ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: 1
Configuration:
BranchName: !Ref BranchName
OutputArtifactFormat: CODE_ZIP
PollForSourceChanges: false
RepositoryName: !GetAtt CodeCommitRepository.Name
Name: Source
OutputArtifacts:
- Name: !Ref PipelineSourceArtifact
Region: !Ref AWS::Region
RunOrder: 1
Name: Source
- Actions:
- ActionTypeId:
Category: Test
Owner: AWS
Provider: CodeBuild
Version: 1
Configuration:
ProjectName: !Ref CodeBuildProject1
InputArtifacts:
- Name: !Ref PipelineSourceArtifact
Name: Test
OutputArtifacts: []
Region: !Ref AWS::Region
RunOrder: 1
Name: Test
- Actions:
- ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: 1
Configuration:
ProjectName: !Ref CodeBuildProject2
InputArtifacts:
- Name: !Ref PipelineSourceArtifact
Name: Build
OutputArtifacts:
- Name: !Ref PipelineBuildArtifact
Region: !Ref AWS::Region
RunOrder: 1
Name: Build
Code language: YAML (yaml)
Stagesプロパティにテストステージを定義します。
Configurationプロパティ内で、テストステージの詳細設定を行います。
ProjectNameプロパティで、先述のCodeBuildを指定します。
InputArtifactsプロパティで、テスト対象のスクリプト等を指定します。今回はSourceステージのアーティファクトを指定します。
(参考)アプリコンテナ用ファイル
Dockerfile
FROM amazonlinux
RUN yum update -y && yum install python3 python3-pip -y
RUN pip3 install bottle
COPY main.py ./
CMD ["python3", "main.py"]
EXPOSE 8080
Code language: Dockerfile (dockerfile)
アプリコンテナ用のイメージは、Amazon Linux 2をベースとして作成します。
Python製WebフレームワークのBottleを使用します。
ですからPythonおよびpipをインストール後、これをインストールします。
アプリロジックを記載したPythonスクリプト(main.py)をコピーし、これを実行するように設定します。
先述の通り、アプリは8080/tcpでHTTPリクエストを待ち受けますので、このポートを公開します。
main.py
from bottle import route, run
@route('/')
def hello():
return 'Hello CodePipeline.'
if __name__ == '__main__':
run(host='0.0.0.0', port=8080)
Code language: Python (python)
Bottleを使用して、簡易的なWebサーバを構築します。
8080/tcpでHTTPリクエストを待ち受け、「Hello CodePipeline.」を返すという単純な構成です。
test_main.py
import pytest
from main import hello
@pytest.mark.parametrize(('expected',), [
('Hello CodePipeline.',),
])
def test_hello(expected):
assert hello() == expected
Code language: Python (python)
pytestでmain.py内に定義したhello関数をテストするためのスクリプトです。
hello関数を実行し、「Hello CodePipeline.」の文字列が返ってきたら成功という内容です。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- ECR:fa-081
- CodeCommit:fa-081
- CodeBuild1:fa-081-project-01
- CodeBuild2:fa-081-project-02
- CodePipeline:fa-081
AWS Management Consoleから各リソースを確認します。
CodePipelineを確認します。
パイプラインの実行に失敗しています。
これはCloudFormationスタック作成時に、CodeCommitが作成されたことがきっかけとして、パイプラインが実行されたためです。
現時点ではCodeCommitにコードをプッシュしていないため、パイプライン実行の過程でエラーが発生しました。
作成されているステージに注目します。
Testという名前で、テストユニット用のステージが作成されています。
CodeCommitにコードがプッシュされると、テストユニットが実行され、正常な動作が確認できれば、Dockerイメージをビルドし、ECRリポジトリにプッシュするという流れになります。
動作確認
テスト成功時
準備が整いましたので、CodeCommitにコードをプッシュします。
まずCodeCommitをプルします。
$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/fa-081
Cloning into 'fa-081'...
warning: You appear to have cloned an empty repository.
Code language: Bash (bash)
空のリポジトリがプルされました。
リポジトリに3つのファイル(Dockerfile、main.py、test_main.py)を加えます。
$ ls -al
total 12
drwxrwxr-x 3 ec2-user ec2-user 71 Aug 21 11:24 .
drwxrwxr-x 6 ec2-user ec2-user 125 Aug 21 11:24 ..
-rw-rw-r-- 1 ec2-user ec2-user 187 Aug 12 11:01 Dockerfile
drwxrwxr-x 7 ec2-user ec2-user 119 Aug 21 11:24 .git
-rw-rw-r-- 1 ec2-user ec2-user 681 Aug 21 08:07 main.py
-rw-rw-r-- 1 ec2-user ec2-user 233 Aug 21 08:34 test_main.py
Code language: Bash (bash)
3ファイルをCodeCommitにプッシュします。
$ git add .
$ git commit -m 'first commit'
...
3 files changed, 51 insertions(+)
create mode 100644 Dockerfile
create mode 100644 main.py
create mode 100644 test_main.py
$ git push
...
* [new branch] master -> master
Code language: Bash (bash)
正常にプッシュできました。
しばらく待機した後、改めてCodePipelineを確認します。
パイプラインが開始されました。
Sourceステージは正常に完了し、テスト(Test)ステージに到達しました。
現在は「In progress」とあり、テストユニットを実行中であることがわかります。
しばらく待機します。
テストステージが正常に完了後、ビルドステージも正常に完了しました。
テストのレポートグループを確認します。
レポートグループが作成されています。
テストユニット実行結果の概要が確認できます。
今回実行したテストの詳細を確認します。
グラフや表でテスト結果が表示できます。
テスト時のフェーズ移行の過程も確認できます。
今回は全て正常に完了しました。
テスト実行時の詳細なログも確認することができます。
ECRリポジトリを確認します。
イメージがプッシュされています。テストが正常に完了しましたので、ビルドステージに移行し、Dockerイメージが生成されて、リポジトリにプッシュされたということです。
テスト失敗時
参考にテスト失敗時の挙動を確認します。
関数が出力する文字列を修正し、敢えてテストに失敗させます。
CodePipeline上で、テストに失敗したことがわかります。
レポートグループを確認すると、失敗したテストの概要がわかります。
続いて詳細を確認します。
ビルドフェーズで失敗していることがわかります。
ログを見ると、失敗した原因が確認できます。
返ってくる文字列が期待通りのものではなかったという内容です。
テストケースの詳細ページからも、同様の内容が確認できます。
まとめ
CodePipeline上にテストステージを追加する方法をご紹介しました。