PowerShell(Pester 5.3)によるテストの雛形

### はじめに...

調査した内容を時系列にまとめた資料なので、その点ご了承ください。

## 目次

1. 実行環境
1. 実現したいこと
1. 作業の流れ
1. 例題
1. (i) フォルダを作成する
1. (ii) テスト対象のコードを準備する
1. (iii) テストコードを作成する
1. (iv) テスト実行コードを作成する
1. (v) テスト
1. まとめ
1. 参考リンク

--------

### 1. 実行環境

|  項目  |  情報 |
| ---- | ---- |
|  OS  |  macOS 11.6  |
|  HW  |  MacBook Air (11-inch, Mid 2013)   |
|  pwsh  |  7.2.0  |
|  pester | 5.3.3  |
|  MDM環境  |  microsoft intune  |

### 2. 実現したいこと

- powershell でユニットテストを実施したい

### 3. 作業の流れ

以下の流れを元に例題を定めて実践する

i. フォルダ構成を検討する
ii. テスト対象のコードを準備する
- テスト項目毎にfunctionを分けてコードを作る
iii. テストコードを作成する
iv. テスト実行コードを作成する
v. テスト

### 4. 例題

割り算を行うfunctionのテストを行う

### 5. (i) フォルダを作成する

今回はテストを実行するにあたり、テスト対象のスクリプトとテストコードを分離して管理する。
分離するためにフォルダは以下の4つで構成した。
また、命名規則を定めることで分離した際の負荷を軽減する。

|#|フォルダ名|ファイル名|役割|
| ---- | ---- |---- |---- |
|1|src|<コード名>.ps1|テスト対象コード|
|2|test-src|<コード名>.ps1|テストコード|
|3|test-exec|<コード名>.Test-Exec.ps1|テスト実行コード|
|4|test-result|<コード名>.Test-Exec.ps1|テスト結果(4の結果出力される)|

<details><summary>テスト対象コード-Division.ps1</summary><div>

* 今回の構成

|#|フォルダ名|ファイル名|役割|
|---- |---- |---- |---- |
|1|src|Division.ps1|テスト対象コード|
|2|test-src|Division.test.ps1|テストコード|
|3|test-exec|Division.test-exec.ps1|テスト実行コード|
|4|test-result|Division.test-result.xml|テスト結果(4の結果出力される)|

</div>
</details>

### 6. (ii) テスト対象のコードを準備する

テスト対象のコードは以下の通り、定数1000を引数で割る関数。

```console
Division.ps1
function SampleFunction001{
    param([int]$i)
        1000/$i
}
```


### 7. (iii) テストコードを作成する

テストコードの構成は以下の通り、Itが実際のテスト内容を記述する箇所となる。
テスト内容を分類するためにDescribe(説明) > Context(環境)という順序で分類する。

```console
## ContextをまとめるDescribe
Describe "<Describeの説明>" {
    ## 全てのContextが実行される前に1回だけ実行されるBeforeAll
    BeforeAll {
    }

    ## テスト(It)をまとめる Context
    Context "<Contextの説明>"{
        # 実際のテスト内容(It)
        It "<Itの説明>"{
            # 実際のテスト内容
    
        It "<Itの説明>"{
            # 実際のテスト内容
        }
    }

    ## 全てのContextが実行された後1回だけ実行されるBeforeAll
    AfterAll {
    }
}
```

<details><summary>テストコード-Division.Test.ps1</summary><div>
```console
Describe "Function-001" {
    BeforeAll {
        # テスト開始時の処理を記載する
        # テスト対象のコードをロードする
        # $PSCommandPathを用いてテスト対象コードの名前を指定
        Write-Verbose "BeforeAll";
        $BasePath = Join-Path $(Split-Path $(Split-Path $PSCommandPath  -Parent) -Parent) "src";
        $TargetCode = Join-Path "${BasePath}" $(Split-Path -Leaf $PSCommandPath).Replace(".Test","");
        . $TargetCode;
        Write-Verbose "LoadFunction ${TargetCode}"

    }

    # 正常系のテスト
    Context "正常系"{
        # 1000 を 100で割ったら10
        It "[SampleFunction001]-N--[001]" {
            SampleFunction001 -i 100| Should -Be 10
        }
    
        # 1000 を 200で割ったら5
        It "[SampleFunction001]-N--[002]" {
            SampleFunction001 -i 200| Should -Be 5
        }
    
    }

    # 異常系のテスト
    Context "異常"{
        It "[SampleFunction001]-AN-[001]" {
            # 1000 を 0で割ったら Throw ※{}で囲む
            {SampleFunction001 -i 0}| Should -Throw
        }

        It "[SampleFunction001]-AN-[002]" {
            # 引数の指定が無かったら Throw
            {SampleFunction001 }| Should -Throw
        }
    }

    AfterAll{
        # テスト終了時の処理を記載する
        Write-Verbose "AfterAll";
    }

}
```
</div>
</detials>

### 8.  (iv) テスト実行コードを作成する

テスト実行コードの構成は以下の通り。
実際には、Invoke-Pesterコマンドだけで実行可能だが、今回はテストコードのファイル名を$PSCommandPathを元に生成したり、実行時の出力内容を指定しているため、長めとなっている。

<details><summary>テスト実行コード-Division.Test-Exec.ps1</summary><div>
```console
$BasePath = $(Split-Path $(Split-Path $PSCommandPath  -Parent) -Parent)

# $PSCommandPathを用いてテストコードのファイル名を指定
$TargetCode = Join-Path "${BasePath}" "test-src" $(Split-Path -Leaf $PSCommandPath).Replace("-Exec","");
# $PSCommandPathを用いてテスト結果(xml)のファイル名を指定
$Result = Join-Path "${BasePath}" "test-result" $(Split-Path -Leaf $PSCommandPath).Replace("-Result","").Replace(".ps1",".xml");

# 詳細ログを出力する場合には、Continueを指定 
$VerbosePreference = "SilentlyContinue"

# Pesterの設定値を指定
$PesterPreference = [PesterConfiguration]::Default
$PesterPreference.Output.Verbosity = 'Detailed'

# Invoke-Pesterでテストコードを実行した上で、実行結果をJUnit形式に変換し、ファイル出力
Invoke-Pester $TargetCode -PassThru | ConvertTo-JUnitReport -AsString |Out-File $Result
```
</div>
</details>


### 9. (v) テスト

テストは、Division.Test-Exec.ps1 を実行する。
実行結果は、コンソール出力と同時にtest-resultフォルダにxmlで出力される

テスト実行結果(Console)
```console
Pester v5.3.3

Starting discovery in 1 files.
Discovery found 4 tests in 19ms.
Running tests.

Running tests from '/Users/<username>/Documents/powershell/pester/test-src/Division.Test.ps1'
Describing Function-001
 Context 正常系
   [+] [SampleFunction001]-N--[001] 16ms (5ms|11ms)
   [+] [SampleFunction001]-N--[002] 18ms (9ms|9ms)
 Context 異常
   [+] [SampleFunction001]-AN-[001] 26ms (24ms|2ms)
   [+] [SampleFunction001]-AN-[002] 42ms (22ms|20ms)
Tests completed in 228ms
Tests Passed: 4, Failed: 0, Skipped: 0 NotRun: 0
```

<details><summary>テスト実行結果-Division.Test-Exec.xml</summary><div>

```console
<?xml version="1.0" encoding="utf-16" standalone="no"?>
<testsuites xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="junit_schema_4.xsd" name="Pester" tests="4" errors="0" failures="0" disabled="0" time="0.228">
  <testsuite name="/Users/<username>/Documents/powershell/pester/test-src/Division.Test.ps1" tests="4" errors="0" failures="0" hostname="<machie-name>" id="0" skipped="0" disabled="0" package="/Users/<username>/Documents/powershell/pester/test-src/Division.Test.ps1" time="0.228">
    <properties>
      <property name="machine-name" value="<machie-name>" />
      <property name="user" value="tatt" />
      <property name="user-domain" value="" />
      <property name="cwd" value="/Users/<username>/Documents/powershell/pester/test-exec" />
      <property name="os-version" value="20.6.0" />
      <property name="junit-version" value="4" />
      <property name="platform" value="Darwin" />
      <property name="clr-version" value="Unknown" />
    </properties>
    <testcase name="Function-001.正常系.[SampleFunction001]-N--[001]" status="Passed" classname="/Users/<username>/Documents/powershell/pester/test-src/Division.Test.ps1" assertions="0" time="0.020" />
    <testcase name="Function-001.正常系.[SampleFunction001]-N--[002]" status="Passed" classname="/Users/<username>/Documents/powershell/pester/test-src/Division.Test.ps1" assertions="0" time="0.021" />
    <testcase name="Function-001.異常.[SampleFunction001]-AN-[001]" status="Passed" classname="/Users/<username>/Documents/powershell/pester/test-src/Division.Test.ps1" assertions="0" time="0.032" />
    <testcase name="Function-001.異常.[SampleFunction001]-AN-[002]" status="Passed" classname="/Users/<username>/Documents/powershell/pester/test-src/Division.Test.ps1" assertions="0" time="0.063" />
  </testsuite>
</testsuites>
```

</div>
</details>

### 10.  まとめ
- pesterを使ってテストコードの作成からテスト実行までの流れを記載できた
- 今後、Describe内で利用できる関数やShouldで指定できる内容を記載したい

### 11. 参考リンク

https://pester-docs.netlify.app/docs/quick-start