Выполнение модульного тестирования с вложенными зависимостями и Factory классами

Я новичок в модульном тестировании и PHPUnit, но в последнее время я много читаю о шаблонах проектирования и изолированных тестах, и я решил реорганизовать приложение, над которым я работаю, чтобы избавиться от статических классов, синглетов, жестко закодированных зависимостей и всего остального, определенного в глобальном масштабе, надеясь сделать его "проверяемым", а не боли в заднице, чтобы работать в будущем, поскольку он предназначен для долгосрочного проекта.

До сих пор я считаю, что я понимаю теорию модульного тестирования, но мне было интересно, в сценарии, когда один делегат обрабатывает вложенные зависимости объектов к Factory, как следует идти об модульном тестировании, указанном Factory, или это просто лишний, чтобы проверить его? И что лучше всего подходит для проверки того, что "цепочка" зависимостей хорошо работает в синхронизации?

Позвольте мне проиллюстрировать вопросы. Предположим, у вас есть следующий "устаревший" код:

class House {
    protected $material;
    protected $door;
    protected $knob;

    public function __construct() {
        $this->door = new Door();
        $this->knob = $this->door->getKnob();
        $this->material = "stone";

        echo "House material: ".$this->material . PHP_EOL . "<br/>";
        echo "Door material: ".$this->door->getMaterial() . PHP_EOL . "<br/>";
        echo "Knob material: ".$this->knob->getMaterial() . PHP_EOL . "<br/>";
    }
}

class Door {
    protected $material;
    protected $knob;

    public function __construct() {
        $this->knob = new Knob();
        $this->material = "wood";
    }

    public function getKnob() {
        return $this->knob;
    }

    public function getMaterial () {
        return $this->material;
    }

}

class Knob {
    protected $material;

    public function __construct() {
        $this->material = "metal";
    }

    public function getMaterial () {
        return $this->material;
    }
}

$house = new House();

Это (насколько я понимаю) плохо для модульного тестирования, поэтому мы заменяем закодированные зависимости с помощью DI + a Factory class:

class House {
    protected $material;
    protected $door;
    protected $knob;

    public function __construct($door) {
        $this->door = $door;
        $this->knob = $this->door->getKnob();
        $this->material = "stone";

        echo "House material: ".$this->material . PHP_EOL . "<br/>";
        echo "Door material: ".$this->door->getMaterial() . PHP_EOL . "<br/>";
        echo "Knob material: ".$this->knob->getMaterial() . PHP_EOL . "<br/>";
    }
}

class Door {
    protected $material;
    protected $knob;

    public function __construct($knob) {
        $this->knob = $knob;
        $this->material = "wood";
    }

    public function getKnob() {
        return $this->knob;
    }

    public function getMaterial () {
        return $this->material;
    }

}

class Knob {
    protected $material;

    public function __construct() {
        $this->material = "metal";
    }

    public function getMaterial () {
        return $this->material;
    }
}

class HouseFactory {
    public function create() {
        $knob = new Knob();
        $door = new Door($knob);
        $house = new House($door);

        return $house;
    }
}

$houseFactory = new HouseFactory();
$house = $houseFactory->create();

Теперь (и опять же, насколько я понимаю) House, Door and Knob могут быть проверены с помощью смехотворных зависимостей. Но:

1) Что происходит с HouseFactory сейчас?

Если нужно просто:

  • Не тестируйте его, так как он еще не тестировал какую-либо прикладную логику, и на самом деле фабрики остаются такими. Предположим, что если независимые тесты для House, Door и Knob проходят, Factory должен быть в порядке.
  • Рефакторируйте Factory каким-то образом, то есть используя функции внутри класса, чтобы получить каждый экземпляр таким образом, чтобы можно было переопределить эти функции через PHPUnit, чтобы возвращать макетные объекты, на всякий случай, если в классе есть какая-то дополнительная логика может использовать некоторые испытания в будущем.

2) Возможно ли настроить тесты, которые полагаются на несколько (не издевающихся) зависимостей сразу? Я понимаю, что это технически не модульное тестирование (возможно, интеграционное тестирование?), Но я думаю, что он все еще отлично справляется с использованием PHPUnit? Учитывая приведенный выше пример, я хотел бы иметь возможность настроить тест, который не только изолирует House, Door, Knob и HouseFactory, но и результаты взаимодействия реальных объектов друг с другом, возможно, с некоторыми из них функции издеваются, например те, которые относятся к данным. Является ли PHPUnit плохим выбором для такого рода тестов?

Заранее благодарим за ваше время. Я понимаю, что некоторые из допущений, которые я делаю, могут быть неверными, поскольку я, очевидно, не специалист по этому вопросу; исправления приветствуются и оцениваются.

Ответ 1

factory похож на ключевое слово new. Вы проверяете ключевое слово new? Нет, вы проверяете, можете ли вы построить класс. Но это независимо от самого factory и части устройства, которое уже является частью ваших модульных тестов.

2) называется интеграционным тестированием. И вы можете сделать это с помощью PHPUnit.


Изменить. Поскольку в комментариях было некоторое обсуждение:

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

В этом нет ничего плохого, однако обычно это не требуется, поскольку конструкторы возвращаемого типа (ов) уже находятся в модульных тестах, и хорошо, что тест действительно тривиальный и просто проверка данных, которая пахнет интеграционным тестированием. Кроме того, те типы, которые имеют этот тип из factory в качестве зависимости (и которые также относятся к модульному тесту), сделают компиляцию/выполнение неудачными, если зависимость не может быть предоставлена. Итак, все, что есть для factory, уже проверено даже с обеих сторон. И если factory не потребляется, тогда вам не нужно его тестировать.

Я предлагаю вам создать один раз factory чисто TDD-стиль, поэтому предварительно сформулируйте его, и тогда у вас возникнет чувство этого. Возможно, вам захочется проверить другие аспекты вашего класса factory, но, вероятно, это больше связано с интеграцией, чем с модульным тестированием.

И я не хочу создавать впечатление, что другие ваши устройства должны иметь жестко запрограммированные вызовы метода factory create, вместо того, чтобы вводить вложенную зависимость. Поскольку вы не должны использовать new внутри своих устройств, вы также не должны использовать Factory::create. Подобно new, имя класса (Factory) жестко закодировано, а не вводится. Тогда это скрытая зависимость. Но зависимости не следует скрывать; но сделал видимым.

Ответ 2

Вы можете проверить его с наследованием.

Просто дополните House с помощью FakeHouse для тестирования, а затем проверьте $material, $door и $knob и т.д., изменились ли они или нет после тестирования.