新入社員向け技術研修でテストの講義・ハンズオンを行いました

## はじめに

弊社では、今年度からエンジニア職の新入社員向けの技術研修を
社員が講師となり、講義・ハンズオンを行うという取り組みがあり、
その際に私はソフトウェアテストについて、講義・ハンズオンを担当しましたので
それについて簡単にご紹介できればと思います。

https://tech.excite.co.jp/entry/2022/06/17/124710

資料とハンズオンのリポジトリは公開していますので、
興味のある方は是非ご覧ください。

資料: https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-sohutoueatesuto-jiang-yi
ハンズオン: https://github.com/keijiro-okamura/unittest-handson-2022

## 全体の流れ
当日は講義10分、ハンズオン80分くらいの配分で行いました。
今回はハンズオンでのユニットテストを触ってもらう事と、実際に実務で使えるテクニックの体験
をしてもらいたかったので、座学はかなり少なめな配分としました。

## 講義
講義では以下のようなことを紹介しました。

- ソフトウェアテストとは
- 自社でのソフトウェアテスト

ここではテストといっても色々な工程や手法がある事や、
実際どんな感じのテストが実務で行われているかが簡単に理解できればOKかなと思ったので、
かなり薄味な内容にしています。

## ハンズオン
下記のような誕生日を入れると、星座を返してくれる簡単なアプリケーションを題材に
テストケースの作成から、実装コードの見直しを行い、最後テストダブルを使ってのテストコードを作成を行っていきます。

ハンズオンではDIやテストダブルなど、ユニットテストを作成する上で必須の知識を学びつつ、
実際に手を動かして体験するということをもらいたかったので、
途中で用語の説明や概念の説明などを、講義形式で行いながら、それを踏まえてコードを修正するという方式にしています。

![スクリーンショット 2022-12-10 17.11.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/93425/30054827-ad35-08d8-b669-0b47c4610a00.png)

### 環境・環境構築
対象のアプリケーションはPHP・laravelで構成し、
テストフレームワークはPHPUnitを使用しました。
環境はdockerを作って用意しています。

```環境構築手順
git clone https://github.com/keijiro-okamura/unittest-handson-2022
git checkout init_handson
make init
```

#### テスト対象の実装コードとテストコードの雛形
- 実装コード

```src/app/Http/Controllers/HoroscopesController.php
<?php

namespace App\Http\Controllers;

use App\Modules\HoroscopesBeta;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller as BaseController;

class HoroscopesController extends BaseController
{
    use AuthorizesRequests, DispatchesJobs, ValidatesRequests;

    /**
     * @throws \GuzzleHttp\Exception\GuzzleException
     * @throws \Exception
     */
    public function index(Request $request)
    {
        // 日付を星座キーに変換
        $birthday = $request->get('birthday');
        $birthday_yyyymmdd = (new \DateTimeImmutable($birthday))->format('Ymd');

        // 外部モジュールから星座名を取得
        $horoscope_japanese_name = (new HoroscopesBeta())->getJapaneseName($birthday_yyyymmdd);

        return view('horoscopes',
            [
                'birthday' => $birthday,
                'horoscope_japanese_name' => $horoscope_japanese_name,
            ]
        );
    }
}

```


- テストコード

```src/tests/Feature/HoroscopesGetRequestTest.php
<?php

namespace Tests\Feature;

use App\Modules\HoroscopesBeta;
use App\Modules\HoroscopesInterface;
use Tests\TestCase;

class HoroscopesGetRequestTest extends TestCase
{
    public function setUp(): void
    {
        parent::setUp();

        // $this->instance(HoroscopesInterface::class, new HoroscopesBeta());
    }

    /**
     * A basic test example.
     *
     * @return void
     */
    public function test_GETリクエスト成功時は200を返却する()
    {
        $response = $this->get('/horoscopes/');
        $response->assertStatus(200);
    }

    // TODO: ブラックボックステスト技法 or ホワイトボックステスト技法を意識して、テストケースを追加しよう
    // TODO: テストダブルを作成し、外部モジュールに依存しないテストを作成しよう
}
```


### テストケースの作成
まずは、題材となるアプリケーションからテストケースを考えてもらいました。

テストケースの作成手法では、一旦手を止めて「ブラックボックステスト」「ホワイトボックステスト」を紹介し、
今回は「ホワイトボックステスト」を用いてテストケースを作りました。

https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-sohutoueatesuto-jiang-yi?slide=23

### DI
ユニットテストを作成する上で、DIは必須なテクニックだと思います。
なので、DIの説明を軽くしたあとは、DIが必要な状況を作り、実装コードの一部をDIして、ユニットテストがしやすい実装に修正してもらうということを行いました。

https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-sohutoueatesuto-jiang-yi?slide=31

https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-sohutoueatesuto-jiang-yi?slide=32

現在の星座を取得するロジックは、このアプリケーション上にある、`src/app/Modules/HoroscopesBeta.php`という、
クラスが処理を担当しています。

しかし、実際に運用開始するときには、`src/app/Modules/HoroscopesApi.php`を使い、外部のAPIから星座情報を取得するよう仕様変更を決定しました。

ただ、`src/app/Modules/HoroscopesApi.php`が呼び出しているAPIは
現在仮のAPIのホストが指定されているため、ハンズオン受講者の方からは現在疎通ができないようになっており、
ユニットテストが失敗するようになってしまいます。

なので、アプリケーションを修正し、ユニットテストを実行するとき限定で、`src/app/Modules/HoroscopesBeta.php`を利用するように改修をしてもらうようにしました。

https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-sohutoueatesuto-jiang-yi?slide=37

https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-sohutoueatesuto-jiang-yi?slide=38

### テストダブル
最後に、DIができるようになったので、テストダブルを注入し、
星占い返却モジュールが正しく呼び出されているかどうか、正しく呼び出された場合のレスポンスが想定しているかどうか
実際のAPIが呼び出せない代わりに、代替となるオブジェクトを用意して、ユニットテストが通るようになったら完成です。

https://speakerdeck.com/excitejp/2022nian-du-xin-zu-ji-shu-yan-xiu-sohutoueatesuto-jiang-yi?slide=50

テストダブルの種類について、今回は、ダミー、スタブ、モック、スパイを取り上げています。

### さいごに
ハンズオンの最終形は、リポジトリの[メインブランチ](https://github.com/keijiro-okamura/unittest-handson-2022/tree/main)がその状態になっているので、実際にハンズオンをやらなかった方でもコードの変化を見られるようにしています。

実際の講義・ハンズオン後のフィードバックでは新入社員の方からも良いコメントが得られたのでやってよかったと思いますし、
なにより、この講義・ハンズオンを作成するにあたり、自分自身が今一度勉強をし直す必要があったので、
それにより、自分が一番学びが深かったかなと思います。笑