📝

バックエンドのテスト方針について

2024/10/01に公開

はじめに

弊社ではバックエンドのテスト方針をADRにまとめています。
(テストの方針はArchitecture Decisionなのか?という疑問もありますが、弊社では広く技術に関する意思決定をADRに残しています。)

ADR作成前も大まかな慣習やお作法はありましたが、詳細については開発者の判断に委ねられていました。
テストの方針を改めて明文化することにより、常に一定の品質を保つことを狙いとしています。

この記事では、テスト方針をまとめたADRを公開します。
テストに関心がある方や、テストの方針に悩んでいる方の参考になれば幸いです。

前提

  • 弊社のバックエンドはGo言語で実装されています
  • DDD (ドメイン駆動設計) とクリーンアーキテクチャを採用しており、以下の依存関係になっています
    adapterusecasedomain

ADRでは弊社のテスト方針をadapter, usecase, domainの各レイヤーに分け、それぞれのテスト方針と基準を定めています。
ここからは、そのADRの内容をご紹介します。

バックエンドのテスト方針

Context

この決定がなされた背景を記載

  • 現状ではテストの方針が統一されておらず、開発者の判断に委ねられている
  • テストの方針を明文化することにより、テストの品質を一定に保ちたい
  • 開発チームの拡大を見据え、新規開発者が容易にテスト方針を理解できるようにしたい

Decision

決定事項を記載

  • 記載すること
    • レイヤー毎の実装の判断基準と方針
    • テスト形式、使用するツールなどの全体方針
    • 決定の根拠となる理由
  • 記載しないこと
    • テストデータの管理手順やモックの具体的な実装方法

domain

  • 概要
    • 値オブジェクト、エンティティ、ドメインサービスの振る舞いを検証する
  • 実装の判断基準
    • ドメインの重要な制約やルールを含んでいる
    • 複雑な条件分岐や計算を含んでいる
    • 取るに足らないコードは省略してもよい
  • 方針
    • テストケースは正常系と、より多くの異常系を網羅する
    • パッケージ名はxx_testとして、パブリックメソッドの振る舞いを検証する
      • プライベートメソッドは直接テストするのではなく、パブリックメソッドを通して間接的にテストする
      • プライベートメソッドが過度に複雑などの理由で間接的にテストすることが難しい場合、export_test.goを利用する
    • トップレベル、サブテストの両方にt.Parallel()を指定する
    • モックは使用しない

usecase

  • 概要
    • ユースケース(アプリケーションのルール)が正しく実装されているかを検証する
    • ドメインオブジェクトの生成や変更、リポジトリを使用したデータの取得や永続化など
  • 実装の判断基準
    • 原則として実装する
    • ただし、以下の条件を全て満たす場合は省略してもよい
      • ドメインの重要な制約やルール、複雑な条件分岐や計算を含んでいない
      • 外部サービスやサードパーティライブラリなどの外部依存を含んでいない
      • 異常系が標準的なもの以外を含んでいない (例:認可チェック、リポジトリのエラーなど)
    • usecaseのテストは実行時間がかかるため、実装や保守コストに対して得られる価値を考慮する
  • 方針
    • テストケースは最長の正常系と、domainのテストで網羅できない異常系を検証する
      • 正常系は場合によって複数あっても構わない
      • 異常系は認可チェック、ドメインオブジェクトの存在チェックなど
    • パッケージ名はテスト対象パッケージと同名とする
      • usecaseはadapterから呼ばれることを前提としており、汎用的な利用を想定していないため
      • 非公開の変数や定数、関数にアクセスできないことによるデメリットのほうが大きいと考えられるため
    • トップレベルのみt.Parallel()を指定する
      • 本ADR記載時点で、サブテストにt.Parallel()は指定していない
      • サブテスト単位の並列実行はデータ競合の可能性が高まり、テストの複雑性や実装コストの増加が懸念されるため
    • モックは外部サービスやサードパーティライブラリなどの外部依存を含む場合のみ使用する
    • 検証フェーズはrepositoryで取得した値を検証対象とする

adapter

  • 概要
    • adapter, usecase, domainの統合的な挙動を検証する
    • 技術的詳細を検証する (/implement配下など)
  • 実装の判断基準
    • GraphQLの各mutation, queryに対応する.httpの作成は必須とする
    • 技術的詳細は原則として実装しない
    • ただし、以下の条件のいずれかを満たす場合は実装を検討する
      • domainやusecaseのテストで網羅できない複雑な処理を含んでいる
      • セキュリティなど、技術的詳細が特に重要な場合
  • 方針
    • .httpは正常系のみ
    • 技術的詳細は原則実装しないため省略する

その他全体方針

  • テストの形式は原則としてテーブルドリブンテストとする
    • テーブルドリブンテストが適さない場合、無理にこだわる必要はない
  • 検証フェーズはgo-cmp, testify/assertのいずれかを使用する
    • 判断基準は以下
      • go-cmp
        • 構造体やmapなど、複雑なデータ構造を比較する場合
        • カスタムの比較ロジックが必要な場合 (例:特定フィールドの除外など)
      • testify/assert
        • 文字列や数値など、基本的なデータ型を比較する場合
        • テストコードの簡潔さと可読性を優先する場合
  • 現在日時の取り扱いは以下の優先順位とする
    • 現在日時を外部から注入する (例:関数の引数として渡すなど)
    • 外部から注入することが困難な場合、testtimeを使用して現在日時を固定する

Consequences

この決定による影響を記載

  • 期待される効果
    • 各レイヤーの特性に応じた適切なテストが実装される
    • テスト実装の判断基準が明確になり、意思決定が容易になる
    • テスト方針が明文化されることにより、開発チームの変動に関わらずテストの品質が維持される
  • 想定される課題
    • テスト実装の判断に個人の解釈が介入する余地が残る
    • 必要に応じた方針の修正や、レビューを通じた品質の担保が求められる

おわりに

以上が弊社のバックエンドのテスト方針をまとめたADRの内容です。

個人的にはこれまでなんとなくで書いていたテストが、明確な基準として言語化されたことで頭の中が整理されました。
チームとしてもADRをベースに会話できるようになったことで、新規メンバーとのコミュニケーションが取りやすくなったのは明確なメリットだと感じています。

各レイヤーごとのより詳細な方針決定の背景や、実装例なども改めてご紹介できればと思います。

参考

Fivot Tech Blog

Discussion