🗂

テストデータビルダーでテスト用データを生成する

2022/05/15に公開

テストデータビルダーを使用する とは

ビルダーパターンを使ってテスト対象オブジェクトのインスタンスを生成すること

ユニットテスト用データを用意する方法の一つ
ビルダーはテスト対象オブジェクトのコンストラクタの各パラメータに対応するフィールドを持っており、フィールドは安全な値で初期化されている
ビルダーのフィールドをメソッドを通して上書きし、build()メソッドを最後に呼んだ時、ビルダーは最新のフィールドの値で対象オブジェクトの新しいインスタンスを生成する

こんな時に使える

テストデータの可読性アップしたい

コンストラクタ引数が多いと、実際にテストで確認したいデータ以外にダミーの引数を大量に渡すことになる
その場合、データ生成部分のコードが読みづらくなり、テストデータの意図も伝わりづらい
ビルダーを使用すると可読性が上がり、テストデータの意図も伝わりやすくなる

テスト対象オブジェクトの構造変更があった時、変更コストを下げたい

各テストで対象オブジェクトのインタンスを生成している場合、全てを変更しないといけないが、ビルダーであれば変更箇所が最小限で済む
(たとえばコンストラクタ引数が追加になった時、変更が必要なのはビルダーとその引数に関連するテストのみになる)

before ~テストデータビルダーを使用しない~

例:Orderの割引率(DiscountRate)が違うデータでテストしたい時、テストデータを生成する

    @Test
    fun hogeTest() {
        val defaultOrder = Order(
          Customer("tanaka", Address("Taito-ku", "Tokyo", PostCode("110", "0000"))),
          listOf(OrderLine("hat", 1), OrderLine("cape", 1)),
          DiscountRate(BigDecimal.ZERO)
        )
        val discountedOrder = Order(
          Customer("tanaka", Address("Taito-ku", "Tokyo", PostCode("110", "0000"))),
          listOf(OrderLine("hat", 1), OrderLine("cape", 1)),
          DiscountRate(BigDecimal.TEN)
        )
  
        // ....テスト処理
    }

after ~テストデータビルダーを使用する~

ビルダーを使用して上記と同じテストデータを生成

    @Test
    fun hogeTest() {
        val defaultOrder = anOrder().build()
        val discountedOrder = anOrder().withDiscountRate(DisCountRate(BigDecimal.TEN)).build()
        
        // ....テスト処理
    }

テストデータビルダー

private data class OrderBuilder(
  private val customer: Customer = Customer("tanaka", Address("Taito-ku", "Tokyo", PostCode("110", "0000"))),
  private val lines: List<OrderLine> = listOf(OrderLine("hat", 1), OrderLine("cape", 1)),
  private val discountRate: DiscountRate = DiscountRate(BigDecimal.ZERO),
) {

  companion object {
    fun anOrder(): OrderBuilder = OrderBuilder()
  }
  fun withCustomer(customer: Customer): OrderBuilder = this.copy(customer = customer)
  fun withOrderLines(lines: List<OrderLine>): OrderBuilder = this.copy(lines = lines)
  fun withDiscountRate(discountRate: DiscountRate): OrderBuilder = this.copy(discountRate = discountRate)

  fun build(): Order = Order(customer, lines, discountRate)
}

note

各メソッド(with○○○)で直接builderのフィールドを上書きすることもできるが、副作用を避けるためbuilder自体のコピーを返すようにした

参考

実践テスト駆動開発 - 第22章 複雑なテストデータの構築

GitHubで編集を提案

Discussion