テストを科学する

2013/12/24

PesterのMock機能をもう少し詳しく

この記事はPowerShell Advent Calendar 2013の24日目のエントリーです。

クリスマス・イブですが普通に仕事をして普通に家で大好きなシェルとSQLと戯れる@oota_kenです。

去年のPowerShell Advent Calendar 2012の17日目のエントリーで解説されているPowerShell用のBDDフレークワークPesterのMock機能に関してもう少し詳しく解説したいと思います。

下記のような時間帯毎に挨拶メッセージを出してくれる関数Out-Greetingのユニットテストを書く場合どのようにすればよいでしょうか?
# Out-Greetingはシェルの起動時に$profileで読み込んで画面表示をするといった方法で使用するイメージです。

function Out-Greeting([string] $name) {  
    $date = Get-Date
    $hour = $date.Hour
    if (6 -le $hour -and $hour -le 11) {
        $greeting = "Good morning "
    } elseif (12 -le $hour -and $hour -le 17) {
        $greeting = "Good afternoon "
    } else {
        $greeting = "Good night "
    }
    
    $greeting + $name | Out-Host
}

Get-Dateで現在日時を取得し、Out-Hostでコンソール出力してしまっているので、テスト結果が実行する日時に依存してしまって、そのままでは単に呼び出しができたかどうかの検証しかできませんね。

そのような場合にPesterのMock機能が活用できます。

Describe "Out-Greeting" {
    Context "Morning" {
        Mock Get-Date { return [DateTime] "2013/12/24 06:00:00" }
        Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good morning ootaken" }
        It "at 6" {
            Out-Greeting "ootaken" 
            Assert-VerifiableMocks
        }
    }
}

PesterのMockは任意のコマンドレットや関数を上書きすることができます。また、同時に”-verifiable”オプションとAssert-VrifiableMocksを組み合わせてモックの呼び出しの検証を、”-parameterFilter”オプションによって呼び出し時の引数に基づくモックの呼び分けができます。

Get-Dateをモックで置き換えることにより、検証したい任意の時間で、実行結果の期待値を一意に定めて再現性あるテストを記述できるようになります。

また、Out-Hostをモックに置き換え、”-parameterfilter”オプションで引数をチェックすることにより、コンソールに出力するOut-Hostのような通常では検証が難しいコマンドレットを使った場合でも呼びだしと出力の検証ができるようになります。

このようにPesterのモック機能を使えば、テスト対象が現在日時や乱数、コンソールに依存したものであってもそれらをモックに置換し検証することが簡単にできます。

下記のように、モックのスコープはスクリプトブロック毎であり、並列してコンテキストでは別々のモックを用意し、各日時とそれに対応した挨拶の文言を検証することができます。

$here = Split-Path -Parent $MyInvocation.MyCommand.Path
    $sut = (Split-Path -Leaf $MyInvocation.MyCommand.Path).Replace(".Tests.", ".")
    . "$here¥$sut"

    Describe "Out-Greeting" {
        Context "Morning" {
            Mock Get-Date { return [DateTime] "2013/12/24 06:00:00" }
            Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good morning ootaken" }
            It "at 6" {
                Out-Greeting "ootaken" 
                Assert-VerifiableMocks
            }
        }

        Context "Afternoon" {
            Mock Get-Date { return [DateTime] "2013/12/24 12:00:00" }
            Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good afternoon ootaken" }
            It "at 12" {
                Out-Greeting "ootaken" 
                Assert-VerifiableMocks
            }
        }

        Context "Night" {
            Mock Get-Date { return [DateTime] "2013/12/24 18:00:00" }
            Mock Out-Host -verifiable -parameterFilter { $_ -eq "Good night ootaken" }
            It "at 18" {
                Out-Greeting "ootaken" 
                Assert-VerifiableMocks
            }
        }
    }

このように便利なPesterのモック機能ですが、そもそもモックを多用しなければテスト出来ない関数やスクリプトブロックというのはテスト容易性が低く、外部依存が強い、良くないコードです。

Out-Greetingの時刻によって挨拶を返す部分を切り出した下記のような関数Get-Greetingを作れば、モックの必要が無く、少ないコードで分岐のロジックを検証することができます。

function Get-Greeting([datetime] $date) {
    $hour = $date.Hour
    if (6 -le $hour -and $hour -le 11) {
        return "Good morning"
    } elseif (12 -le $hour -and $hour -le 17) {
        return "Good afternoon"
    } else {
        return "Good night"
    }
}

Describe "Out-Greeting" {
    Context "Get-Greeting" {
        It "at 6" {
           $result = Get-Greeting ([DateTime]  "2013/12/24 06:00:00")
           $result | Should Be "Good morning"
       }

       It "at 12" {
           $result = Get-Greeting ([DateTime]  "2013/12/24 12:00:00")
           $result | Should Be "Good afternoon"
       }

       It "at 18" {
           $result = Get-Greeting ([DateTime]  "2013/12/24 18:00:00")
           $result | Should Be "Good night"
       }
   }
}

以上のようにPesterのモック機能を使えば、テスト対象が現在日時や乱数、コンソールに依存したものであってもそれらをモックに置換し検証することが簡単にできます。

しかし、モックを多用したテストコードを書く前にそもそも対象の関数やスクリプトブロックは果たして適切なのだろうか、よりテストしやすい形にリファクタリングする余地は残されていないだろうか?と自答するのが真のエンジニアであり、PowerShellerです。ツールが豊富な機能を用意してくれているからといってそれにおぼれず常に最適な方法を自分の頭で考えましょう。

次の最終日はAkira SugiuraさんでPowerShell勉強会のフォローアップとのことです。楽しみですね。

ソフトウェアテストに関するお悩みなど、まずはお気軽にお問い合わせください。

  • お問い合わせフォーム【お問い合わせはコチラ】
  • 電話でのお問い合わせ【0120-142-117】