要約
ドキュメントを見て試して、Pipelinesのジョブの中でPostgreSQLのコンテナを立ち上げてDBを使ったテストを動かしたという話です。
手順
Azure Pipelinesではジョブ単位でコンテナを立ち上げておき、その後タスクを実行できます。
これを利用して.Net coreなプロジェクトのテストプロジェクトで実際のDBを使ってみました。
- project.sln
- Fukasawah.Sample1
- Fukasawah.Sample1.csproj
- Fukasawah.Sample1.Tests
- Fukasawah.Sample1.Tests.csproj
という感じのファイル構成をしているとき、次のようなパイプラインを書きます。
trigger:
- master
variables:
DotnetVersion: 3.1.x
BuildConfiguration: Release
ProjectName: Fukasawah.Sample1 # 適宜書き換え
TestDbName: testdb
TestDbUser: testuser
TestDbPassword: testpass
# ジョブで使うコンテナを定義して名前を付ける
resources:
containers:
- container: 'postgres'
image: 'postgres:11-bullseye'
ports:
- 5432
env:
POSTGRES_DB: $(TestDbName)
POSTGRES_USER: $(TestDbUser)
POSTGRES_PASSWORD: $(TestDbPassword)
# ヘルスチェックはコマンドに応じて
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
stages:
- stage: Build_and_Publish
jobs:
- job: Build
pool:
vmImage: "ubuntu-latest"
services:
# `サービス名: コンテナ名` で書く(複数同じサービスがあることはあまりないので、コンテナ名と一緒にすると紛れがないと思う)
postgres: postgres
variables:
# テストプロジェクトで使う接続文字列を環境変数CONNECTION_STRINGで渡す。ポートだけは変わるので変数から埋め込む
CONNECTION_STRING: "Host=localhost;Port=$(agent.services.postgres.ports.5432);Database=$(TestDbName);Username=$(TestDbUser);Password=$(TestDbPassword)"
steps:
- task: UseDotNet@2
inputs:
packageType: "sdk"
version: $(DotnetVersion)
displayName: "Install .NET Core SDK $(DotnetVersion)"
- task: DotNetCoreCLI@2
inputs:
command: "build"
projects: "**/$(ProjectName).csproj"
arguments: "-c $(BuildConfiguration)"
displayName: "dotnet build"
# TODO: DBの作成を行うジョブを書く場合はこのあたり。
# 接続文字列を読み取ってデータを流すスクリプトを用意したりする(dotnet ef migration && dotnet ef database updateとか。)
# 今回は、テストコード中で「dbContext.Database.EnsureCreated()」を呼び出して作成するので、やっていない。
- task: DotNetCoreCLI@2
inputs:
command: "test"
projects: "**/$(ProjectName).Tests.csproj"
arguments: '-c $(BuildConfiguration) --collect:"XPlat Code Coverage"'
displayName: "dotnet test"
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: "Cobertura"
summaryFileLocation: "$(Agent.TempDirectory)/**/coverage.cobertura.xml"
- task: DotNetCoreCLI@2
inputs:
# publish時にzipまで行うには一工夫
# ref: https://devadjust.exblog.jp/27915866/
command: "publish"
publishWebProjects: false
projects: "**/$(ProjectName).csproj"
arguments: "-c $(BuildConfiguration) -o $(Build.SourcesDirectory)/publish --no-restore --no-build -p:DebugType=None -p:DebugSymbols=false"
displayName: "dotnet publish"
- task: CopyFiles@2
inputs:
contents: '$(ProjectName)/$(ProjectName)*.zip'
targetFolder: $(Build.ArtifactStagingDirectory)
displayName: "Copy zip to artifacts staging directory"
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: $(Build.ArtifactStagingDirectory)
artifactName: BuildOutputs
displayName: "Publish build artifacts"
ポイントは以下です。ドキュメントに書いてある通りです。
-
resources.containers[]:
に利用するコンテナの定義を書く -
stages[].jobs[].services
にresourcesで書いたコンテナを参照する -
$(agent.services.サービス名.ports.ポート番号)
でポート番号を変数から参照して埋め込む- コンテナの定義のポート番号はこの変数に使われるもので繋ぐためには使えない。うっかり重複してエラーになっても面倒なので変数で取るほうが良いと思う
- ただ、dockerコマンドで起動しているだけなので、ポートマッピングしている場合はそのまま行けるかもしれない(
5432
ではなく5432:5432
と書いたり)
docker-compose.ymlをazure-pipelines.ymlで書くようなイメージですね。
これを実行すると、こんな感じに実行されます。
ジョブの最初の方のInitialize containersタスクでイメージのPullが行われるので、若干時間がかかっています。
コンテナが動く状態になるまで待つ(healthcheck)
コンテナの起動はしたものの、プロセスが立ち上がりきらない場合もあり、その時にテストが走るとダメです。
ビルド時間的に間に合うでしょうと高をくくっても良いのですが、optionsでDockerのHealthcheckオプションを指定できます。
PostgreSQLではpg_isreadyというユーティリティコマンドがあり、これを使うのが一般的なようです。(その分、次のタスクを実行するまでの待ち時間は増えます。)
resources:
containers:
- container: 'postgres'
image: 'postgres:11-bullseye'
ports:
- 5432
env:
POSTGRES_DB: $(TestDbName)
POSTGRES_USER: $(TestDbUser)
POSTGRES_PASSWORD: $(TestDbPassword)
options: --health-cmd pg_isready --health-interval 3s --health-timeout 3s --health-retries 10