Azure PipelinesのYAMLでXamarin.Forms製アプリのCI環境を構築する方法(xUnit)

「Azure PipelinesのYAMLでXamarin.Forms製アプリのCI/CD環境を構築する」は3部構成です。
記事を順番に読み進めると、Azure PipelinesでXamarin.Forms製アプリのCI/CD環境が構築できるようになります。

第1部: CI環境の構築　__←イマココ__
第2部: [App Center配布パイプラインの構築](https://qiita.com/uhooi/items/a4bb5282eed708dadb40)
第3部: [ストア配布パイプラインの構築](https://qiita.com/uhooi/items/31f0630aa47e7980fbc3)

## はじめに

本記事は [Xamarin / MAUI Advent Calendar 2021](https://qiita.com/advent-calendar/2021/xamarin) の1日目の記事です。

Azure Pipelinesを使い、Xamarin.Forms製アプリの単体テストを行うCIを構築します。

## 本記事で説明しないこと

- Azure Pipelinesの概要や基本的な操作方法  
[私が以前書いた記事](https://qiita.com/uhooi/items/3e1bd3e64f339f6c0d0f) が参考になると思います

## 設定ファイルの構成

[Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法](https://qiita.com/uhooi/items/3e1bd3e64f339f6c0d0f#%E8%A8%AD%E5%AE%9A%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E6%A7%8B%E6%88%90) と同様なので省略します。

### name

[Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法](https://qiita.com/uhooi/items/3e1bd3e64f339f6c0d0f#name) と同様なので説明は省略します。

### trigger

[Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法](https://qiita.com/uhooi/items/3e1bd3e64f339f6c0d0f#trigger) と同様なので説明は省略します。

設定のみ紹介します。

```yaml:ci.yml
trigger:
  batch: true
  branches:
    include:
    - main
    - develop
  paths:
    exclude:
    - Docs
    - README.md
    - LICENSE
```

### schedules

[Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法](https://qiita.com/uhooi/items/3e1bd3e64f339f6c0d0f#schedules) と同様なので省略します。

設定のみ紹介します。

```yaml:ci.yml
schedules:
- cron: "0 15 * * *"
  displayName: Daily midnight build
  branches:
    include:
    - main
    - develop
  always: true
```

### variables

今回は使っていないので省略します。

### jobs

ジョブを1つのみ用意しています。

#### test

単体テストを実行するジョブです。

`dotnet test` コマンドで単体テストを実行し、Azure DevOps上にテスト結果とコードカバレッジを表示します。
xUnitを使っているので、 `testResultsFormat` に `xUnit` を指定しています。

```yaml:ci.yml
  steps:
  # テスト
  - task: DotNetCoreCLI@2
    displayName: 'Dotnet test'
    inputs:
      command: test
      projects: '$(System.DefaultWorkingDirectory)/{単体テストのプロジェクト}.csproj'
      arguments: '--collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=json,cobertura,lcov,teamcity,opencover'

  # テスト結果のアップロード
  - task: PublishTestResults@2
    displayName: 'Publish test results'
    inputs:
      testResultsFormat: 'xUnit'
      testResultsFiles: '*.trx'
      searchFolder: '$(Agent.TempDirectory)'
      mergeTestResults: true

  # コードカバレッジのアップロード
  - task: PublishCodeCoverageResults@1
    displayName: 'Publish code coverage report'
    inputs:
      codeCoverageTool: 'Cobertura'
      summaryFileLocation: '$(Agent.TempDirectory)\*\coverage.cobertura.xml'
      reportDirectory: '$(Build.SourcesDirectory)\TestResults\Coverage\Reports'
      failIfCoverageEmpty: true
```

## 設定ファイルの全体図

最後に設定ファイルの全体図を載せます。

```yaml:ci.yml
name: $(SourceBranchName)_$(Date:yyyyMMdd)$(Rev:.r)

trigger:
  batch: true
  branches:
    include:
    - main
    - develop
  paths:
    exclude:
    - Docs
    - README.md
    - LICENSE

schedules:
- cron: "0 15 * * *"
  displayName: Daily midnight build
  branches:
    include:
    - main
    - develop
  always: true

jobs:
- job: test
  pool:
    vmImage: 'windows-2019'

  steps:
  # テスト
  - task: DotNetCoreCLI@2
    displayName: 'Dotnet test'
    inputs:
      command: test
      projects: '$(System.DefaultWorkingDirectory)/{単体テストのプロジェクト}.csproj'
      arguments: '--collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=json,cobertura,lcov,teamcity,opencover'

  # テスト結果のアップロード
  - task: PublishTestResults@2
    displayName: 'Publish test results'
    inputs:
      testResultsFormat: 'xUnit'
      testResultsFiles: '*.trx'
      searchFolder: '$(Agent.TempDirectory)'
      mergeTestResults: true

  # コードカバレッジのアップロード
  - task: PublishCodeCoverageResults@1
    displayName: 'Publish code coverage report'
    inputs:
      codeCoverageTool: 'Cobertura'
      summaryFileLocation: '$(Agent.TempDirectory)\*\coverage.cobertura.xml'
      reportDirectory: '$(Build.SourcesDirectory)\TestResults\Coverage\Reports'
      failIfCoverageEmpty: true
```

シンプルなYAMLファイルなので、慣れれば読みやすいと思います。

## おまけ: PR時にCIを回す

[Azure PipelinesのYAMLでiOSアプリのCI環境を構築する方法](https://qiita.com/uhooi/items/3e1bd3e64f339f6c0d0f#%E3%81%8A%E3%81%BE%E3%81%91-pr%E6%99%82%E3%81%ABci%E3%82%92%E5%9B%9E%E3%81%99) と同様なので省略します。

## おわりに

Azure PipelinesのYAMLで基本的なXamarin.Forms製アプリのCIを回すことができました！
本記事の内容はXamarin.Forms固有の技術を使っておらず、xUnitを使っているプロジェクトなら適用できます。

逆にいうとXamarin.*を使ったクラスは単体テストが書きづらいです。
詳細は私が昨年のAdvent Calendarで書いた記事をご参照ください。

https://qiita.com/uhooi/items/7f7945cdc44aa901114e

以上 [Xamarin / MAUI Advent Calendar 2021](https://qiita.com/advent-calendar/2021/xamarin) の1日目の記事でした。
明日は @ytabuchi さんの記事です。

## 参考リンク

- [.NET Core CLI task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/build/dotnet-core-cli?view=azure-devops)
- [Publish Test Results task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/test/publish-test-results?view=azure-devops&tabs=trx%2Cyaml)
- [Publish Code Coverage Results task - Azure Pipelines | Microsoft Docs](https://docs.microsoft.com/en-us/azure/devops/pipelines/tasks/test/publish-code-coverage-results?view=azure-devops)