CubeSoft の各種プロジェクトでは、継続的インテグレーション (CI: Continuous Integration) 用サービスとして AppVeyor を利用していますが、諸々の事情を考慮して Azure Pipelines でも同等の CI を実行できるように環境の整備を進めています。この記事では、Azure Pipelines での CI、特に OpenCover/NUnit を用いてユニットテストを実行し、結果を Codecov に送信するまでの手順について記述します。
概要
前提として、何らかの csproj (多くの場合、ユニットテスト用のプロジェクト)に対して下記の PackageReference が記述されている事とします。
<ItemGroup> <PackageReference Include="NUnit" Version="3.11.0" /> <PackageReference Include="NUnit.ConsoleRunner" Version="3.10.0" /> <PackageReference Include="OpenCover" Version="4.7.922" /> </ItemGroup>
この状態で、下記のタスクを実行するように YAML ファイルを編集していきます。
- NuGet Resotre
- Build
- Run OpenCover with NUnit
- Send to Codecov
- Publish test results
- NuGet Pack
- Publish pipline artifacts
ここでは、ユニットテストに関係のある 3. ~ 5. の記述を抜粋します。下記を含む全ての記述内容は AzurePipelines.yml を参照下さい。*1
- script: > "$(TEST_TOOL)" -log:Error -register:user -target:"$(TEST_CORETOOL)" -targetargs:"$(PROJECT_NAME).Tests.dll" -targetdir:"Tests\$(PROJECT_BIN)" -returntargetcode -hideskipped:All -output:"$(TEST_COVERAGE)" -filter:"$(TEST_FILTERS)" displayName: 'Run tests via OpenCover and NUnit' - script: | pip install codecov codecov -f "$(TEST_COVERAGE)" -t $(CODECOV_TOKEN) displayName: 'Send coverage results to Codecov' - task: PublishTestResults@2 inputs: testResultsFormat: 'NUnit' testResultsFiles: '**\$(TEST_RESULT)' displayName: 'Publish test results'
最初の task (script) で OpenCover を実行し、次の task で Codecov に結果を送信します。尚、この記事の執筆時点では、Azure Pipelines から Codecov に結果を送信するためにはトークンを指定する必要があります。そのため、How to integrate codecov.io in an Azure Build Pipeline を参考にして、あらかじめ CODECOV_TOKEN と言う環境変数を作成し、必要な値を Secret 設定で追加しておいて下さい。
最後の task で Azure Pipelines 上にテスト結果を送信します。PublishTestResults タスクを実行すると、各ビルドの Tests タブに結果が表示されるようになります。
備考
これ以降は、今回 Azure Pipelines 上で CI 環境を整えるにおいて、嵌まったポイント等いくつかの関連事項を記述します。
NuGet task を利用した Restore
NuGet に関連するコマンドは NuGet task が用意されており、Restore に関しても通常はこの task を利用します。
- task: NuGetCommand@2 inputs: command: 'restore' restoreSolution: '$(PROJECT_NAME).sln' #feedsToUse: 'config' #nugetConfigPath: 'NuGet.config' displayName: 'Restore NuGet packages'
しかし、NuGetCommand@2 経由で実行した場合、初期設定ではカレントディレクトリに存在する NuGet.config は無視されます。config ファイルを反映させるには feedsToUse および nugetConfigPath 引数を利用するようなのですが、実際に試した所、今度は api.nuget.org が Source として認識しないような挙動を示しました。恐らく、明示的に config を指定した場合、この辺りも含めて設定する必要があるものと予想されます。
- script: | nuget restore "$(PROJECT_NAME).sln" displayName: 'Restore NuGet packages'
以上を考慮すると、NuGet.config を用意している場合、現時点では上記のように script で実行する方が楽なようです。
GitHub Releases からダウンロード
依存するライブラリが全て NuGet パッケージとして取得できれば良いのですが、必ずしもそうではない場合もあります。例えば、CubeICE は 7z.dll と言うライブラリに依存しています。ここでは、このライブラリを Releases - cube-soft/7z から取得する事を試みます。
- task: DownloadGitHubRelease@0 inputs: connection: 'cube-soft-ci' userRepository: 'cube-soft/7z' itemPattern: '7z-*-x64.zip' downloadPath: '$(Build.SourcesDirectory)' displayName: 'Download 7-Zip modules'
GitHub Releases からのダウンロードには Download GitHub Release task を利用します。userRepository に対象となるリポジトリの名前、itemPattern にダウンロードするファイルを表す文字列、downloadPath に保存ディレクトリのパスを指定します。
connection には、Service connections と呼ばれる機能で作成した文字列を記述します。作成手順は、まず Creating a personal access token for the command line を参考に、GitHub 上でトークンを生成します(生成時に指定するスコープに関しては、repo, user, admin:repo_hook が推奨されているようです)。次に、Azure Pipelines の左下にある Project settings から Service connections を選択し、New service connection で GitHub を選択します。
新規作成画面で Personal access token を選択し、Connection Name には適当な名前、Token には GitHub で取得したトークンを入力します。最後に、ここで設定した名前を DownloadGitHubRelease@0 の connection 引数に記述すると完了です。
Pipeline Artifacts の設定
Azure Pipelines には Artifacts と言う項目が存在します。これは、Azure DevOps の Artifacts(左側にメニューとして表示されている項目)とは別物で、各ビルド結果の右上に表示されるリンクから辿る事ができます。
この Artifacts に成果物を表示するには、Publish Pipeline Artifact task を利用します。
- task: PublishPipelineArtifact@0 inputs: artifactName: '$(PROJECT_NAME)' targetPath: '$(Build.ArtifactStagingDirectory)' displayName: 'Publish pipline artifacts'
NuGet Pack など多くの task において、実行結果は $(Build.ArtifactStagingDirectory) に保存されます。そのため、targetPath 引数にはこの変数を指定しておくと良いようです。
*1:Cube プロジェクトでは、取得した各種 NuGet パッケージを "../packages" に配置するように設定しています(参考:Cube のプロジェクト構成およびビルド&テスト方法)。初期設定では、これらの NuGet パッケージは "$(UserProfile)/.nuget/packages" に配置されるようなので、もしリンク先の YAML ファイルを利用する場合には、適当に置き換えて下さい。