Зависимости в модульном тестировании - PullRequest
2 голосов
/ 18 января 2012

В настоящее время я пытаюсь применить модульное тестирование к проекту впервые.Возникли два вопроса:

  1. Это плохая практика, если несколько тестов зависят друг от друга? В приведенном ниже коде для нескольких тестов требуется, чтобы результаты других тестов былиположительный, является ли это общей лучшей практикой?

  2. Как далеко вы идете с насмешливыми объектами, от которых зависит ТУ? В приведенном ниже коде «Маршрутизатор» зависит от«Маршрут», который зависит от «RouteParameter».Чтобы издеваться или не издеваться?

Приведенный ниже код предназначен для проверки моего объекта «Маршрутизатор», который принимает маршруты через Router::addRoute($route) и маршрутизирует URL через Router::route($url).

class RouterTest extends PHPUnit_Framework_TestCase {
    protected function createSimpleRoute() {
        $route = new \TNT\Core\Models\Route();
        $route->alias = 'alias';
        $route->route = 'route';
        $route->parameters = array();

        return $route;
    }

    protected function createAlphanumericRoute() {
        $route = new \TNT\Core\Models\Route();
        $route->alias = 'alias';
        $route->route = 'test/[id]-[name]';

        $parameterId = new \TNT\Core\Models\RouteParameter();
        $parameterId->alias = 'id';
        $parameterId->expression = '[0-9]+';

        $parameterName = new \TNT\Core\Models\RouteParameter();
        $parameterName->alias = 'name';
        $parameterName->expression = '[a-zA-Z0-9-]+';

        $route->parameters = array($parameterId, $parameterName);

        return $route;
    }

    public function testFilledAfterAdd() {
        $router = new \TNT\Core\Helpers\Router();

        $router->addRoute($this->createSimpleRoute());

        $routes = $router->getAllRoutes();

        $this->assertEquals(count($routes), 1);

        $this->assertEquals($routes[0], $this->createSimpleRoute());

        return $router;
    }

    /**
    * @depends testFilledAfterAdd
    */
    public function testOverwriteExistingRoute($router) {
        $router->addRoute(clone $this->createSimpleRoute());

        $this->assertEquals(count($router->getAllRoutes()), 1);
    }

    /**
    * @depends testFilledAfterAdd
    */
    public function testSimpleRouting($router) {
        $this->assertEquals($router->route('route'), $this->createSimpleRoute());
    }

    /**
    * @depends testFilledAfterAdd
    */
    public function testAlphanumericRouting($router) {
        $router->addRoute($this->createAlphanumericRoute());

        $found = $router->route('test/123-Blaat-and-Blaat');

        $data = array('id' => 123, 'name' => 'Blaat-and-Blaat');

        $this->assertEquals($found->data, $data);
    }

    /**
    * @expectedException TNT\Core\Exceptions\RouteNotFoundException
    */
    public function testNonExistingRoute() {
        $router = new \TNT\Core\Helpers\Router();

        $router->route('not_a_route');
    }
}

Ответы [ 2 ]

5 голосов
/ 18 января 2012

1) Да, это определенно плохая практика, если тесты зависят друг от друга.

Модульный тест должен быть построен таким образом, чтобы в случае неудачи он сразу указывал на определенную область в вашем коде.Хорошие юнит-тесты уменьшат время, затрачиваемое на отладку.Если тесты зависят друг от друга, вы потеряете это преимущество, потому что вы не можете определить, какая ошибка в вашем коде сделала тест неудачным.Кроме того, это кошмар обслуживания.Что если что-то изменится в вашем «общем тесте», то вам придется изменить все зависимые тесты.

Здесь вы можете найти несколько хороших советов о том, как решать проблемы взаимодействующих тестов (вся книга шаблонов тестов xUnit обязательна к прочтению!)

2) Модульное тестирование - это тестирование наименьшего возможного.

Допустим, у вас есть будильник (код C #):

public class AlarmClock
{
    public AlarmClock()
    {
        SatelliteSyncService = new SatelliteSyncService();
        HardwareClient = new HardwareClient();
    }

    public void Execute()
    {
        HardwareClient.DisplayTime = SatelliteSyncService.GetTime();

        // Check for active alarms 
        // ....
    }
}

Это не проверяется.Вам понадобится реальное спутниковое соединение и аппаратный клиент, чтобы проверить, установлено ли правильное время.

Следующее, однако, позволит вам издеваться над hardwareClient и satelliteSyncService.

public AlarmClock(IHardwareClient hardwareClient, ISatelliteSyncService satelliteSyncService)
{
    SatelliteSyncService = satelliteSyncService;
    HardwareClient = hardwareClient;
}

Однако вы никогда не должны высмеивать объект, который вы на самом деле тестируете (звучит логично, но иногда я вижу, что это происходит).

Итак, как далеко вы должны идти с насмешкой.Вы должны издеваться над всем, от чего зависит ваш класс тестирования.Таким образом, вы можете проверить свой класс в полной изоляции.И вы можете контролировать результат ваших зависимостей, чтобы вы могли убедиться, что ваша SUT будет проходить через все пути кода.

Например, пусть SatelliteSyncService сгенерирует исключение, пусть вернет недопустимое время и выключитКонечно, пусть он вернет правильное время, а затем в определенные моменты, чтобы вы могли проверить, активирован ли ваш будильник в нужный момент.

Для создания ваших тестовых данных маршрута.Подумайте об использовании Builder Pattern. Это поможет вам установить только то, что требуется для успешного прохождения теста.Это сделает ваши тесты более выразительными и их будет легче читать другим.Это также снизит уровень обслуживания вашего теста, поскольку у вас меньше зависимостей.

Я написал сообщение в блоге о модульном тестировании, которое расширяет идеи, упомянутые здесь.Для объяснения понятий используется C #, но он применим ко всем языкам.

0 голосов
/ 18 января 2012

В вашем примере показаны не тесты, зависящие друг от друга, а отдельный тест для класса Router, в котором используются связанные классы Route и RouteParameter.Поскольку эти другие являются держателями данных, у меня не было бы проблем с их использованием в тестах Router.

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

...