Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
「YUMEMI.swift #5 ~Free Talk~」のLTで使用した資料です。

パラメタライズドテストのメリット解説

2019/11/29(金)
YUMEMI.swift #5 〜Free Talk〜
@the_uhooi

自己紹介

  _______ _    _ ______      
 |__   __| |  | |  ____|     
    | |  | |__| | |__        
    | |  |  __  |  __|       
    | |  | |  | | |____      
  _ |_|_ |_|  |_|______|   _ 
 | |  | | |               (_)
 | |  | | |__   ___   ___  _ 
 | |  | | '_ \ / _ \ / _ \| |
 | |__| | | | | (_) | (_) | |
  \____/|_| |_|\___/ \___/|_|

I am a very weak vimmer.
私はよわよわVimmerです。

メソッドをテストする

テストケースは何パターン必要?

func canWork(motivation: Int, hasRedBull: Bool) -> Bool {
    if motivation < 100 {
        return false
    }

    if !hasRedBull {
        return false
    }

    return true
}

正解は4パターン

motivation hasRedBull return value
99 true false
99 false false
100 true true
100 false false

手動でテストする

一覧表に○×を付けるのが一般的

No motivation hasRedBull return value 判定 日付 担当
1 99 true false OK 2019/11/29 ウホーイ
2 99 false false OK 2019/11/29 ウホーイ
3 100 true true NG 2019/11/29 ウホーイ
4 100 false false OK 2019/11/29 ウホーイ

自動でテストする

func test_canWork_motivation_99_hasRedBull_true() {
    let logic = LogicSample()
    XCTAssertFalse(logic.canWork(motivation: 99, hasRedBull: true))
}

func test_canWork_motivation_99_hasRedBull_false() {
    let logic = LogicSample()
    XCTAssertFalse(logic.canWork(motivation: 99, hasRedBull: false))
}

func test_canWork_motivation_100_hasRedBull_true() {
    let logic = LogicSample()
    XCTAssertTrue(logic.canWork(motivation: 100, hasRedBull: true))
}

func test_canWork_motivation_100_hasRedBull_false() {
    let logic = LogicSample()
    XCTAssertFalse(logic.canWork(motivation: 100, hasRedBull: false))
}

何も工夫していないテストコード

  • 網羅できているかわかりづらい
  • 同じような処理で読みづらい
  • メソッド名に悩む

テストコードがわかりづらい、読みづらいと…

保守されなくなる

エクセルっぽく書きたい

motivation hasRedBull return value
99 true false
99 false false
100 true true
100 false false

エクセルっぽく書きたい

let tests: [(motivation: Int, hasRedBull: Bool, expect: Bool)] = [
    ( 99, true,  false),
    ( 99, false, false),
    (100, true,  true ),
    (100, false, false)
]

引数の配列を作り、ループしてテストすればいいんだ!

自動でテストする(工夫)

func test_canWork() {
    let tests: [(line: UInt, motivation: Int, hasRedBull: Bool, expect: Bool)] = [
        (#line,  99, true,  false),
        (#line,  99, false, false),
        (#line, 100, true,  true ),
        (#line, 100, false, false)
    ]

    for (line, motivation, hasRedBull, expect) in tests {
        let logic = LogicSample()
        let result = logic.canWork(motivation: motivation, hasRedBull: hasRedBull)
        XCTAssertEqual(result, expect, line: line)
    }
}

わかりやすい!

  • 網羅できているか、ひと目でわかる
  • 処理の重複がない
  • テストケースを追加・変更・削除しやすい

パラメタライズドテスト(parameterized test)

このように、引数のみを切り替えて実行するテストを 「 パラメタライズドテスト 」という

言語によって呼び方が異なることがある

言語 名前
Go テーブル駆動テスト
Java パラメータ化テスト

他言語では一般的なのに、なぜかSwiftでは知られていない…

Swiftでの実装詳細

@tobi462さんの資料がとても参考になります

Swift で ParameterizedTest をやってみた話/swift-parameterized-test - Speaker Deck
https://speakerdeck.com/yusukehosonuma/swift-parameterized-test

おまけ

Swiftでも、SpockのData Tablesのように書きたい!

class MathSpec extends Specification {
  def "maximum of two numbers"(int a, int b, int c) {
    expect:
    Math.max(a, b) == c

    where:
    a | b | c
    1 | 3 | 3
    7 | 4 | 7
    0 | 0 | 0
  }
}

演算子のオーバーロードとFunction Buildersで実現できないかな…

参考
http://spockframework.org/spock/docs/1.3/data_driven_testing.html#data-tables

ご清聴ありがとうございました

  ______ _   _ _____  
 |  ____| \ | |  __ \ 
 | |__  |  \| | |  | |
 |  __| | . ` | |  | |
 | |____| |\  | |__| |
 |______|_| \_|_____/ 

サンプルコード
https://github.com/uhooi/yumemi-swift-5-sample

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment