Laavel 테스트~복원 편으로

17684 단어 PHPLaraveltesting

이 보도에 관하여


Laravel #2 Advent Calendar 2018 - Qiita 24일째 보도다.
팩스 편이긴 하지만 팩스가 거의 없고'휴대폰으로 의존관계를 교환한다'는 내용이니 크리스마스이브니까 용서해 주세요.

개시하다


개요


이 글은 단원 테스트라고 하지만 단원 테스트가 없는 곳이 있기 때문에 단원 테스트로 변경해야 한다.
Laavel 테스트~단원 테스트 편 - Qita

컨디션

  • Laravel: 5.7.15
  • PHP: 7.1.19
  • PHPUnit: 7.4.4
  • 이루어지다


    구조기로 받아들일 의존 대상 변경


    만일을 대비해서 먼저 원래의 코드를 놓아라.
    <?php
    
    namespace App\Models\Services\Calculators;
    
    use App\Models\PeriodOfUse;
    use App\Models\Specifications\DiscountSpecification;
    
    class DiscountCalculator
    {
        public function run(PeriodOfUse $period, int $baseCharge): int
        {
            $specification = new DiscountSpecification();
            if (!$specification->satisfied($period)) {
                return 0;
            }
            return (int)floor($baseCharge / 10);
        }
    }
    
    new DiscountSpecification 대신 로컬 변수를 대입하고 인스턴스를 구조기로 받아 속성에 들어갑니다.
    <?php
    
    namespace App\Models\Services\Calculators;
    
    use App\Models\PeriodOfUse;
    use App\Models\Specifications\DiscountSpecification;
    
    class DiscountCalculator
    {
        private $specification;
    
        public function __construct(DiscountSpecification $specification)
        {
            $this->specification = $specification;
        }
    
        public function run(PeriodOfUse $period, int $baseCharge): int
        {
            if (!$this->specification->satisfied($period)) {
                return 0;
            }
            return (int)floor($baseCharge / 10);
        }
    }
    
    Specification은interface를 사용할 수 있지만 이번에는 구체적인 반이 하나밖에 없어서 그냥 놔뒀다.

    대상을 모듈화하는 몇 가지 방법


    다음은 DiscountCalculatorTest::testRun()의 수정이다.$specification 생성된 곳은 다음과 같다.
    // Mockery
    $specification = Mockery::mock(Context::class)
        ->shouldReceive('satisfied')
        ->once()
        ->andReturn($satisfied)
        ->getMock();
    
    // or
    
    // PHPUnit MockObject
    $specification = $this->createConfiguredMock(
        Context::class,
        ['satisfied' => $satisfied]
    );
    
    $calculator = new DiscountCalculator($specification);
    $discount = $calculator->run($period, $baseCharge);
    
    Laavel에 Mockery가 미리 설치되어 있음Mockery::mock()도 가능하며 PHPUnit에 내장된 모듈 클래스$this->createMock()$this->createConfiguredMock()도 사용할 수 있습니다.
    또한 Mockery와 MockObject 대신 PHP7에서 가져온 무명 클래스를 사용할 수도 있습니다.
    
    $specification = new class extends DiscountSpecification {
        public $satisfied;
        public function satisfied(): bool {
            return $this->satisfied;
        }
    };
    $specification->satisfied = $satisfied;
    
    모처럼 묶여서 모커리도 괜찮을 것 같은데 무명반을 쓴 걸 알았더라면 손해 볼 일은 없었을 텐데.
    satisfied의 행동은 단지 두 가지(진짜인지 가짜인지)일 뿐이기 때문에 DataProvider로부터 테스트 모델로 당신에게 맡깁니다.
    - public function testRun(int $baseCharge, int $days, int $expected)
    + public function testRun(int $baseCharge, bool $satisfied, int $expected)
    
    일수는 더 이상 DiscountCalculator에 의존하지 않으니 적당히 넣으세요.
    이렇게 되면 Discount Calculator만 테스트할 수 있어 매우 좋은 단원 테스트입니다.

    잡담


    DI 컨테이너를 사용하는 경우도 다음과 같다.
    DiscountCalculatorTest.php
    $specification = Mockery::mock(DiscountSpecification::class)
        ->shouldReceive('satisfied')
        ->once()
        ->andReturn($satisfied)
        ->getMock();
    
    $this->app->bind(DiscountSpecification::class, function () use ($specification) {
        return $specification;
    });
    
    $this->app->bind('calculator', DiscountCalculator::class);
    $calculator = app('calculator');
    
    $discount = $calculator->run($period, $baseCharge);
    $this->assertSame($expected, $discount);
    
    제품 코드에 DI 용기가 사용되지 않더라도 테스트 시 테스트 대상과 모델 대상은 모두 용기를 이용해 초기화하면 의존 대상(위의 예에서 DiscountSpecification)도 자동으로 모듈로 교체돼 편리하다.

    최종 테스트 코드


    일수가 의존에서 삭제되었기 때문에 테스트 모드에서도 제외됩니다.
    DiscountCalculatorTest.php
    <?php
    
    namespace Tests\Unit;
    
    use App\Models\PeriodOfUse;
    use App\Models\Services\Calculators\DiscountCalculator;
    use App\Models\Specifications\DiscountSpecification;
    use App\Models\Specifications\Specification;
    use Carbon\Carbon;
    use Tests\TestCase;
    use Mockery;
    
    class DiscountCalculatorTest extends TestCase
    {
        /**
         * @param int $baseCharge
         * @param bool $satisfied
         * @param int $expected
         * @dataProvider dataRun
         */
        public function testRun(int $baseCharge, bool $satisfied, int $expected)
        {
            $period = new PeriodOfUse(Carbon::today(), Carbon::today()->addDays(1));
            $specification = Mockery::mock(DiscountSpecification::class)
                ->shouldReceive('satisfied')
                ->once()
                ->andReturn($satisfied)
                ->getMock();
    
            $calculator = new DiscountCalculator($specification);
            $discount = $calculator->run($period, $baseCharge);
    
            $this->assertSame($expected, $discount);
        }
    
        public function dataRun()
        {
            return [
                '値引きあり,端数なし' => [
                    'baseCharge' => 5000,
                    'satisfied'  => true,
                    'expected'   => 500,
                ],
                '値引きあり,端数あり' => [
                    'baseCharge' => 4999,
                    'satisfied'  => true,
                    'expected'   => 499,
                ],
                '値引きなし' => [
                    'baseCharge' => 5000,
                    'satisfied'  => false,
                    'expected'   => 0,
                ],
            ];
        }
    }
    

    끝말


    각양각색의 모듈화 방법이 있지만 기본적으로 모커리 사용을 추천하고 상황에 따라 무명반을 사용하는 것이 편할 수 있다(그렇게 말하지만 잠시 생각이 나지 않으니 아이디어가 있는 사람이 평론을 해주면 좋겠다.
    Happy testing!
    내일이 마지막 날, @jiyuujin의 "LARAVEL을 사용하여 \12294, 검증 및 페이지 관리"기대해주세요!

    좋은 웹페이지 즐겨찾기