TDD: лучшие практики насмешливых стеков объектов - PullRequest
1 голос
/ 07 апреля 2020

Я пытаюсь ознакомиться с модульным тестированием в PHP с небольшим API в Lumen. Написание первых нескольких тестов было довольно приятно с помощью некоторых учебных пособий, но теперь я столкнулся с моментом, когда мне нужно смоделировать / заглушить зависимость.

Мой контроллер зависит от указанного c пользовательского типа интерфейса, на который намекают конструктор.
Конечно, я определил привязку интерфейса / реализации в ServiceProvider.

    public function __construct(CustomValidatorContract $validator)
    {
        // App\Contracts\CustomValidatorContract
        $this->validator = $validator;
    }

    public function resize(Request $request)
    {
        // Illuminate\Contracts\Validation\Validator
        $validation = $this->validator->validate($request->all());

        if ($validation->fails()) {
            $response = array_merge(
                $validation
                ->errors() // Illuminate\Support\MessageBag
                ->toArray(), 
                ['error' => 'Invalid request data.']
            );

            // response is global helper
            return response()->json($response, 400, ['Content-Type' => 'application/json']);
        }
    }

Как видите, у моего CustomValidatorContract есть метод validate(), который возвращает экземпляр Illuminate\Contracts\Validation\Validator (результат проверки). Это в свою очередь возвращает экземпляр Illuminate\Support\MessageBag при вызове errors(). MessageBag тогда имеет toArray() -метод.

Теперь я хочу проверить поведение моего контроллера в случае сбоя проверки.

    /** @test */
    public function failing_validation_returns_400()
    {
        $EmptyErrorMessageBag = $this->createMock(MessageBag::class);
        $EmptyErrorMessageBag
            ->expects($this->any())
            ->method('toArray')
            ->willReturn(array());

        /** @var ValidationResult&\PHPUnit\Framework\MockObject\MockObject $AlwaysFailsTrueValidationResult */
        $AlwaysFailsTrueValidationResult = $this->createStub(ValidationResult::class);
        $AlwaysFailsTrueValidationResult
            ->expects($this->atLeastOnce())
            ->method('fails')
            ->willReturn(true);
        $AlwaysFailsTrueValidationResult
            ->expects($this->atLeastOnce())
            ->method('errors')
            ->willReturn($EmptyErrorMessageBag);

        /** @var Validator&\PHPUnit\Framework\MockObject\MockObject $CustomValidatorAlwaysFailsTrue */
        $CustomValidatorAlwaysFailsTrue = $this->createStub(Validator::class);
        $CustomValidatorAlwaysFailsTrue
            ->expects($this->once())
            ->method('validate')
            ->willReturn($AlwaysFailsTrueValidationResult);

        $controller = new ImageResizeController($CustomValidatorAlwaysFailsTrue);
        $response = $controller->resize(new Request);

        $this->assertEquals(400, $response->status());
        $this->assertEquals(
            'application/json',
            $response->headers->get('Content-Type')
        );
        $this->assertJson($response->getContent());
        $response = json_decode($response->getContent(), true);
        $this->assertArrayHasKey('error', $response);
    }

Это тест, который работает нормально - но может кто-нибудь сказать мне, если есть лучший способ написать это? Это не правильно. Нужен ли этот большой стек mo c -объектов из-за того, что я использую фреймворк в фоновом режиме? Или что-то не так с моей архитектурой, так что это кажется слишком "сверхмощным"?

Спасибо

Ответы [ 2 ]

1 голос
/ 08 апреля 2020

То, что вы делаете, не является модульным тестированием, потому что вы не тестируете ни одного модуля вашего приложения. Это интеграционное тестирование, выполненное с помощью модульного тестирования, и по этой причине оно выглядит неправильно.

Модульное тестирование и интеграционное тестирование проводятся в разное время, в разных местах и ​​требуют разных подходы и инструменты - первый тестирует каждый отдельный класс и функцию вашего кода, в то время как второй не заботится о них, они просто запрашивают API и проверяют ответы. Кроме того, ИТ не подразумевает насмешек, потому что их целью является проверка того, насколько хорошо ваши устройства интегрируются друг с другом.

Вам будет сложно поддерживать подобные тесты, потому что каждый раз, когда вы меняете CustomValidatorContract вас ' Я должен исправить все тесты, связанные с этим. Вот как UT улучшает дизайн кода, требуя, чтобы он был как можно более слабосвязанным (чтобы вы могли выбрать один модуль и использовать его без необходимости загрузки всего приложения), соблюдая SRP & OCP , et c.

Вам не нужно тестировать сторонний код, вместо этого выберите уже протестированный. Вам также не нужно тестировать побочные эффекты, потому что среда похожа на стороннюю службу, ее следует тестировать отдельно (return response() - это побочный эффект). Также это серьезно замедляет тестирование.

Все это приводит к мысли, что вы хотите тестировать CustomValidatorContract только в изоляции. Вам даже не нужно что-то там издеваться, просто создайте экземпляр валидатора, дайте ему несколько наборов входных данных и проверьте, как он работает.

0 голосов
/ 07 апреля 2020

Это тест, который работает нормально - но может кто-нибудь сказать мне, если есть лучший способ написать это? Это не правильно. Нужен ли этот большой стек mo c -объектов из-за того, что я использую фреймворк в фоновом режиме? Или что-то не так с моей архитектурой, что это выглядит слишком «сверхмощным»?

Большой стек фиктивных объектов указывает на то, что ваш испытуемый тесно связан со многими разными вещами.

Если вы хотите поддерживать более простые тесты, то вам нужно упростить дизайн.

Другими словами, вместо Controller.resize - это одна огромная монолитная c вещь, которая знает все детали обо всем, Подумайте о дизайне, в котором изменение размера знает только поверхность вещей и как делегировать работу другим (более легко проверяемым) частям.

Это нормально, в том смысле, что TDD много о выборе проекты, которые поддерживают лучшее тестирование.

...