AtrisanとPHP UnitでAPIの単体テストを自動化してみた

はじめまして。
rexcornuでは主にフロントエンドを担当しているインターンのやーくんです。

今回は、rexcornuで自社サービスとして提供しているTechMatchのサーバサイドの単体テストをArtisanとPHP Unitで自動化するにあたって実施したことや、このテスト工程を経験して感じたことをまとめます。

​まず、単体テストという言葉ですが、私が担当しているTechMatchにおいてはモジュールやコンポーネントの動作確認と位置付けています。

# 目次

- [はじめに：担当しているTechMatchについて](#はじめに：担当しているTechMatchについて)
- [なぜ単体テストを自動化したのか](#なぜ単体テストを自動化したのか)
- [テストの準備や実施の内容](#テストの準備や実施の内容)
- [意識したこと](#意識したこと効率的な優先順位を付けたテスト作成実行)
- [単体テストの実施結果](#単体テストの実施結果)
- [テスト工程を経験して感じたこと](#テスト工程を経験して感じたこと開発でもテストでも自動化は良い)
- [今後の課題](#今後の課題)
- [おわりに](#おわりに)

<h2>はじめに：担当しているTechMatchについて


お客様の声を基に誕生した、ITプロジェクトの調達支援サービスで、 もともとは弊社内の営業支援システムとして利用していました。多くのお客様で、エンジニア調達に関する共通の課題が発生している声を聞き、お客様にもその利便性を提供できればと始めたサービスです。

TechMatchは3つの特徴を持っています。

1. 15,000名のアクティブユーザーから求めるエンジニアの母集団を瞬時にピックアップ

2. 豊富な人材データに人のサポートを加えて、高精度なマッチングを実現

3. 調整業務は専任のリレーションシップマネージャーが対応し、プロジェクトの負担を軽減

https://c.tech-match.net/

TechMatchは2023年4月にサービスインしていますが、私はこのサービスの初期開発に参画し、フロントエンド開発（React.js）全般に関わりました。昨年の秋頃から、フロントエンド開発に加えてサーバサイドの開発にもチャレンジさせてもらっています！

<h2>なぜ単体テストを自動化したのか

今回、TechMatchで大規模なテストを実施することになり、単体テストもその一部ですが、こうしたことに至った背景には、開発中のサービスが様々な機能を持つようになって、コードが肥大化・複雑化したことで、機能間の関係が見えづらくなり、新規に機能追加した際に既存の機能でバグが発生するということが起きるようになってきたためです。

リリース後に発生した不具合の1つの例を紹介すると、ある機能をリリースした後に、本来消える想定ではなかったデータが消えてしまい、お客さまや社内の営業担当者に迷惑をかけてしまうといったことがありました。もしこれが単発であれば、時には開発者の考慮もれが発生することはあり得る、あくまで単発の人的エラーと考えることもできますが、このような不具合が複数続いたことがあり、プロダクト関係者のなかで、品質管理上の本質的な問題を抱えているのではないかという結論に至り、今後サービスをしっかりスケールさせていくためにも、本格的にテストを自動化できる体制を包括的に整備しようとなりました。

このことに関連して、TechMatchでシステムをリリースする際、メンテナンスモードに切り替えて一時的にサービス提供を停止しています。このため、もしリリース後に重大なバグが見つかってしまうと、そのバグにより著しくUXが低下するだけではなく、そのリカバリのために緊急リリースが必要になり、それに伴いまたサービス提供の停止が発生し、結果、サービス提供時間がどんどん短くなるといったことにもつながります。こうしたことを防ぐためにも、リリース前にデグレードを検知できる仕組みは必要です。

私はフロントエンドを担当していましたが、昨年の秋からバックエンド開発にもチャレンジし始めており、バックエンドのテスト工程に参画することでシステムの理解がより深まるということで、今回はAPIの単体テストの一部を担当することになりました。

<h2>テストの準備や実施の内容

以下のような流れでテストを実施しました。　

１. テストを実施する対象APIの整理
２. テストデータの準備（データベース側）
Seederを使ってテストのために複数のテーブルに必要なテストデータを作成しました。
```
// テストデータ作成の例
DB::beginTransaction();
$basedate = Carbon::now()->addDay(-11)->format('Y-m-d');
$personnel = new Personnel(); 
$personnel->id = 1;
$personnel->business_partner_id = '3';
$personnel->mail_id = '1234567890';
$personnel->status = '0';
···
$personnel->save();
```
３. テストコードの作成
```
// 案件に関するテストコードの例
$response = $this->withHeaders([
    'Authorization' => 'Bearer '. self::$accessToken,
])->get(
    route('api.activeMatching',
        $request,
    )
);
$json = $response->decodeResponseJson();
$array = $json->json('data');
$this->assertEquals(count($array), 1);
$project_list = $array['project_list'];
$this->assertEquals(count($project_list), 1);
$this->assertTrue(true);
```

４. テストの実行
artisanでテストを実行しました。
```
// 全テスト実行
php artisan test

// テスト箇所を選択して実行
php artisan test --filter test_hoge Test.php
```

５. 実行結果の検証、評価

今回は、ダッシュボード内の統計情報やログイン/ログアウト、案件のマッチングといった、TechMatchのお客様企業向けの6本のAPIを対象にテストしました。

ユースケースがマッチング率ごとの要員数といった統計情報やユーザ認証関連、案件と要員のマッチングだったので、それに合わせてユーザテーブルや案件のテーブルなどのテストデータをDBに登録しました。

テストはローカルPCで実施し、リクエストについては以下のキャプチャのようにartisanで作成しました。


![test (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3720028/1d19a4c2-94cc-19b1-8ed8-1c7effd3de4c.jpeg)



<h2>意識したこと

<h3>効率的な（優先順位を付けた）テスト作成/実行

現実問題として、時間が限られた中での対応だったため、テストの意味が薄いテストコードは書かないように注意しました。今回のケースでは、境界値関連でのリスクが高いと考えられていたことから、境界値関連のテストは厚めに実装して、それ以外のケースは実行回数が少なくなるように実装しました。こうして、テストコードの量やテストの実行回数（=テスト実施にかかる時間）が不必要に増えず、かけた工数と自動化による効果が最適になったと思います。

<h3>様々なテストデータを用意する

今回、境界値テスト以外は比較的ランダムで少なめのテストの実装でしたが、その中でもできるだけ多くのバリエーションでテストを通せるように、値が全く異なるデータを用意しました。

<h3>分かりやすく簡潔に書く（今回に限った話ではないですが）

テストケースはエンジニア以外も見るので、テストの手順を誰が見ても分かるよう丁寧に書きました。一方で、細かく書きすぎるとどうしても冗長になるので、バランスとりが難しいところでした。

<h2>単体テストの実施結果

この単体テストを実施したことで、10件ほどの既存バグを見つけることができました。いくつか例示すると、下の表にあるような、表示メッセージに関するものが見られました。こちらについては、テストの後改修してリリースしており、今はここに記載されているバグは改修されておりますので、お客様におかれましてはご安心いただければと思います。

![Screenshot from 2024-04-23 20-48-58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3720028/f35c3e50-e668-a2b2-6c64-80909ceff601.png)

<h2>テスト工程を経験して感じたこと

<h3>開発でも、テストでも、自動化は良い

テスト工程を通して予期していなかったバグを発見できたことは非常に価値のあることだと感じました。これまで手動で入出力が合っているかを確認していましたが、AtrisanとPHPUnitによる自動テストの仕組みを整えることができたので、今後は、特に既存バグを検知するための単体テストの確認工数が軽減され、これまで以上に開発を効率的に進めることができます。

<h3>正しくテストを実施するには開発者同様正しい仕様の理解が必要

これまでは、テスト実施の際は画面から操作していましたが、今回、自動テストを実装してみて、システムに関する適切な理解が必要だと感じました。テストデータを作成するにしても、業務の仕様やDBのテーブル構造を理解し、テストしたい項目に合う条件でデータを追加することが必要です。

<h3>テストデータの準備めっちゃ大変

データ同士の関係の理解が不十分だったため、DBにうまくデータが入らずかなり苦戦しました。しかし、データ構造をたどっていくにつれて、データ構造やサーバでの処理をより詳しく知ることができました。

<h1>今後の課題

テストコードを書く習慣をつけて、開発→テストのサイクルを自然と開発チーム全体で回せるようにしていきたいです。

<h1>おわりに

今回は私からサーバサイドのテスト工程を紹介し、近々masuyamaさんからフロント側の紹介をしていきます。お楽しみに。



rexcornuでは、これからの会社の成長を共にしてくれる仲間を募集しています。

https://www.rexcornu.com/career

https://www.wantedly.com/companies/company_9295588/projects

https://www.rexcornu.com/

https://c.tech-match.net/