yusuke921's blog

労働はオワコン

【Laravel】dataProviderを使って単体テストの境界値テストをスマートに書く

はじめに

この記事はLaravel Advent Calendar 2018 - Qiitaの13日目の記事です。

qiita.com

昨日は@nyamucoroさんの記事で 「初心者も出来た!Laravelの中身を読んでコントリビュートしよう 」でした!

今回はdataProviderを使って単体テストの境界値テストをスマートに書いてみます。

テスト対象クラス

<?php

namespace App\Services;

class FizzBuzzService
{
    public function fizzBuzz($num)
    {
        if ( ($num % 15) === 0 ) {
            return 'FizzBuzz';
        } elseif ( ($num % 3) === 0 ) {
            return 'Fizz';
        } elseif ( ($num % 5) === 0 ) {
            return 'Buzz';
        } else {
            return $num;
        }
    }
}

有名なあれです

愚直にテストクラスを書いてみる

<?php

namespace Tests\Unit\App\Services;

use App\Services\FizzBuzzService;
use Tests\TestCase;

class FizzBuzzServiceTest extends TestCase
{
    public function testFizzBuzz_3でも5でも割り切れる場合()
    {
        $fizz_buzz_service = new FizzBuzzService();
        $num = 15;
        $actual = $fizz_buzz_service->fizzBuzz($num);
        $expected = 'FizzBuzz';

        $this->assertEquals($expected, $actual);
    }

    public function testFizzBuzz_3で割り切れる場合()
    {
        $fizz_buzz_service = new FizzBuzzService();
        $num = 3;
        $actual = $fizz_buzz_service->fizzBuzz($num);
        $expected = 'Fizz';

        $this->assertEquals($expected, $actual);
    }

    public function testFizzBuzz_5で割り切れる場合()
    {
        $fizz_buzz_service = new FizzBuzzService();
        $num = 5;
        $actual = $fizz_buzz_service->fizzBuzz($num);
        $expected = 'Buzz';

        $this->assertEquals($expected, $actual);
    }

    public function testFizzBuzz_3でも5でも割り切れない場合()
    {
        $fizz_buzz_service = new FizzBuzzService();
        $num = 11;
        $actual = $fizz_buzz_service->fizzBuzz($num);
        $expected = 11;

        $this->assertEquals($expected, $actual);
    }
}

dataProviderを使ってスマートに書く

<?php

namespace Tests\Unit\App\Services;

use App\Services\FizzBuzzService;
use Tests\TestCase;

class FizzBuzzServiceTest extends TestCase
{
    /**
     * @dataProvider testData
     */
    public function testFizzBuzz($expected, $num)
    {
        $fizz_buzz_service = new FizzBuzzService();
        $actual = $fizz_buzz_service->fizzBuzz($num);

        $this->assertEquals($expected, $actual);
    }

    public function testData()
    {
        return [
            '3でも5でも割り切れる場合'   => ['FizzBuzz', 15],
            '3で割り切れる場合'         => ['Fizz', 3],
            '5で割り切れる場合'         => ['Buzz', 5],
            '3でも5でも割り切れない場合' => [11, 11],
        ];
    }
}

だいぶスッキリしたと思います。

テスト対象のメソッドの実装によってはdataProviderがバシッとハマらないケースも多々ありますが 今回のような境界値の振る舞いをテストしたい場合にはオススメなので使ってみてください。

明日は@kkznchさんの記事で 「LaravelのPivotモデルを使い中間テーブルから関係を取得する」です!

レガシーコード改善ガイド 1〜3章まとめ

 

レガシーコード改善ガイド

レガシーコード改善ガイド

 

 

 とりあえずレガシーコード改善ガイドの1〜3章を読んでみた。

書いてあった部分で印象的な箇所がいくつかあったのでまとめていきたいと思う。

 

はじめに

レガシーコードの定義について書かれていた。

テストのないコードは悪いコードである。どれだけうまく書かれているかは関係ない。どれだけ美しいか、オブジェクト指向か、きちんとカプセル化されているかは関係ない。テストがあれば、検証しながらコードの動きを素早く変更することができる。テストがなければ、コードが良くなっているのか悪くなっているのかが本当にはわからない。 

なかなか印象的な言葉だ。

 

レガシーコードとは複雑で絡まり合ったコード、構造がわかりにくくて理解することが難しいコード…

ではない!

 

テストコードがないならそれはレガシーコードだよ!!!

 

伝えたいことは要するに、安全に仕様の変更、リファクタリングをするためにはテストないと無理ゲーってことだと思う。

 

 

ソフトウェアの変更は困難

 毎日のようにコードを変更し、仕様変更、追加機能を実装しているけど変更されない既存の機能に比べると変更量はごくわずかである。

 

一部の変更によって振る舞いが変わる箇所はあるが今まで通り振る舞いが保たれる部分の方が多い。

 

しかし、既存の振る舞いを保ちつついくつかの変更を加えることは困難!

 

理由としては以下を意識しなければならないからだ。

  • 変更が正しく行われたかどうか?
  • 既存の機能を何も壊していないか?

 

コードを書けば書くほどバグを埋め込む可能性が高くなるもんね。。。

 

 

振る舞いを確認するための単体テスト

単体テストはしないけど結合テストはやってるよーって現場は結構多いと思う。

でも結合テストにはいくつか問題点がある。

 

  • エラー箇所の特定
  • テスト実行時間

 

エラー箇所の特定

結合テストでは最終的なアウトプットは確認できても一つ一つの振る舞いはわからない。

例えば本来"hoge"と出力が期待されるところが"huga"となっていた場合、バグは確認できたけど出力に到るまでの一連の処理の中でどこが問題なのかを割り出すことができない。

 

テスト実行時間

テストがダル過ぎて積極的にやりたくないw

ブラウザからポチポチするのは時間がかかり過ぎる。。。

(最終的にはやらないといけないと思うけど) 

 

よっていいテストの条件は

  • フィードバックがすぐに得られる(実行時間が短い)
  • すぐに問題の箇所が特定できる

 

レガシーコード改善ガイドを読んでいこうと思う

 

レガシーコード改善ガイド

レガシーコード改善ガイド

 

 

毎週チーム内でレガシーコード改善ガイドを題材に読書会をすることになった。

 

もともと周りのプログラマーからの評判がよかったのでいつか読みたいと思っていたが、本格的に読んだほうが良さそうな状況になったのでこれから読み進めていく。

 

みなさんテストしてますか?

ここでいうテストとはUnitTestのことだ。

自分はテストコードを書くようにしている。テストがないと不安でコミットできない。コミットするソースコードに対してはテストを書いて実行し、振る舞いが期待通りであることを確認してからコミットすべきだと思っている。(プライベートで開発しているどうでもいいプロダクトは別として)

テストが通っていないということは期待通りの振る舞いをする保証がないということだ。全てのテストが通っているからといってバグが発生しないわけではないが単体レベルでの振る舞いは保証される。

 

しかし実際の現場でテストが存在しないプロダクトは意外と多い気がする。Excelで書かれた結合テストのシナリオはあったりするけど

 

自分が書いたコードに絶対の自信があるからだろうか?そもそもテストの導入の仕方がわからないからなのか?

ちなみにテストを書く人ほどわかりやすくて人に優しいコードを書く傾向にあると思う。(自分はまだまだだけど。。。)

 

理由はどうあれテストはあった方がいいよという話。

(あった方がというよりないとダメ)

 

レガシーコード改善ガイドを読もうと思ったきっかけ

自分は最近新しいチームにジョインし、既存システムの開発をすることになった。

チームメンバーは話しやすく積極的にコミュニケーションを取れそうな感じですごくいい!

 

チームにジョインしてすぐに開発に入ることになった。

最初は簡単な改修や機能追加だ。

 

ディレクターさんに仕様を共有してもらいながら実装のイメージを膨らませていく。

 

仕様の共有が終わり、開発に入るためまずは既存のコードを読んでいく。。。

。。。

 

テストがない!!!

 

しかも

 

Viewに書かれた大量の業務ロジック!

条件文に現れる何かを意味する大量のマジックナンバー!!

 

Controllerにも大量の業務ロジックが!!!しかも数千行はありそうだ。。。

条件文やループのネストもなかなかに深い。。。

 

改修、機能追加をしたいがエンバグが怖い。。。

 

「ここを修正したいけど他に影響は出ないか?そもそも影響範囲は?」

振る舞いがどのように変わってしまったかを確認する術がない。。。

テストがないからだ

 

しかし既存の機能を壊すようなことだけはしたくない。。。

 

というわけでテストを導入したい^ ^

 

レガシーコード改善ガイドを読んでいくことでチーム内に浸透させたいことはざっとこんな感じ

  • そもそもUnitTestとは?
  • なぜUnitTestを書かなければならないのか?
  • 具体的にどうやってUnitTestを書けばいいのか?

 

レガシーコード改善ガイドを読んだ後で実際のプロダクトにテストコードを書いていければいいなーと思っている。

 

既存のコードをテストに入れるのは簡単ではないが、今あるプロダクトをよりよくしていきたいという思いはあるので頑張りたいと思う!

 

 

今後は読み進めた部分に対してまとめ、備忘録的な感じで記事を書いていければと思う。

 

よし、頑張ろう!