Как проводить модульное тестирование приложения, использующего ORM?

StackOverflow https://stackoverflow.com/questions/673518

Вопрос

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

У меня есть несколько классов PHP, которые содержат функции, которые выглядят следующим образом:

    static function _setSuspended($Suspended, $UserID)
    {
        try {
            $con = Propel::getConnection();

            $c1 = new Criteria();
            $c1->add(DomainsPeer::USERID,$UserID);

            $update = new Criteria();
            $update->add(DomainsPeer::SUSPENDED,$Suspended);

            BasePeer::doUpdate($c1, $update, $con);

            return true;
        } catch(PropelException $e) {
            return $e->getMessage();
        }
    }

Я использую Propel в качестве ORM.Я прочитал различные темы модульного тестирования, в которых говорится о создании «Mocks» и «Stubs», а также о том, что нет, но я не смог найти ничего, что конкретно говорило бы вам, как тестировать функцию, подобную приведенной выше.

Мои мысли звучат примерно так:Мне нужно протестировать функцию выше, чтобы я хотел ее вызвать.Но если я назову это, он использует Propel в качестве ORM, и в соответствии с принципами модульного тестирования я должен изолировать каждую функцию отдельно.

Я просто не вижу способа сделать это.Что мне здесь не хватает?

Это было полезно?

Решение

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

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

Другие советы

Это общий ответ: я вообще не знаком с Propel и лишь немного лучше знаком с PHP.Основной ответ заключается в том, что вы используете внедрение зависимостей.Вместо того, чтобы ссылаться непосредственно на ваш ORM, вы создаете вокруг него оболочку, а затем внедряете ее в свой класс/функцию для фактического использования.Чтобы выполнить модульное тестирование, вы создаете макет или поддельную версию оболочки, которая не взаимодействует с ORM, но вместо этого позволяет вам настраивать ответы оболочки на вызовы ваших методов.Это позволяет вам исключить ORM при модульном тестировании ваших функций.

Я пытался решить ту же проблему при создании Плагин PHPUnit для Symfony.В итоге я подошел к этому так же, как Тестовая среда Django — используйте отдельную базу данных/соединение, уничтожайте и перестраивайте ее перед каждым тестом.

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

я читал Блог Миско Хевери о тестировании много в последнее время.Он охватывает эту ситуацию;вам нужно будет использовать DI (внедрение зависимостей).

Я тоже немного борюсь с этим и тоже использую propel.

Во-первых, вы можете переместить метод «suspend» в класс «Object», а не в одноранговый узел.В любом случае для этой конкретной функции вам не нужно использовать статические методы для достижения этой цели.Ваш API может выглядеть так:

MyObjectPeer::retrieveByPK(1)->suspend();

Это можно будет протестировать с помощью обычных методов модульного тестирования.

Если действительно необходимо протестировать базу данных, то, AFAIK, вам действительно нужно, чтобы БД участвовала в тестировании.Я часто использую ltree и postgis в своем текущем проекте и не могу придумать другого способа запуска модульных тестов для логики модели, которая зависит от БД, кроме включения ее в мои тесты.

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

Мы могли бы провести тестирование с подключением к другой базе данных, но тогда это уже не модульный тест, а интеграционный тест.

Лучшая альтернатива, о которой я думаю, — это иметь класс QueryFactory, который будет обертывать все необходимые вам методы, и тогда вы сможете его издеваться.

Сначала я создаю интерфейс

interface iQueryFactory
{
    function firstFunction($argument);
    function secondFunction($argument, $argument2);
}

QueryFactory со всем вашим запросом ORM, который нам нужен.

class QueryFactory implements iQueryFactory
{
    function firstFunction($argument) 
    {
        // ORM thing
    }

    function secondFunction($argument, $argument2)
    {
        // ORM stuff
    }
}

Есть бизнес-логика с внедрением фабрики запросов.

class BusinessLogic 
{
    protected $queryFactory;

    function __construct($queryFactoryInjection) 
    {
        $this->queryFactory= $queryFactoryInjection;
    }

    function yourFunctionInYourBusinessLogique($argument, $argument2) 
    {
        // business logique

        try {
            $this->queryFactory->secondFunction($argument, $argument2);
        } catch (\Exception $e) {
            // log
            // return thing
        }

        // return stuff
    }
}

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

class QueryFactoryMock implements iQueryFactory
{
    function firstFunction($argument) 
    {
        if (is_null($argument)) 
        {
            throw new \Exception("");
        } 
        else 
        {
            return "succes";
        }
    }

    function firstFunction($argument, $argument2) 
    { 
        // sutff  
    }
}

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

class BusinessLogicTest extends PHPUnit_Framework_TestCase 
{
    public function setUp() 
    {
        require_once "BusinessLogic.php";
    }

    public function testFirstFunction_WhenInsertGoodName() 
    {
        $queryMockup = new QueryFactoryMock();
        $businessLogicObject = new BusinessLogic($queryMockup);
        $response = $businessLogicObject ->firstFunction("fabien");

        $this->assertEquals($response, "succes");
    }

    public function testFirstFunction_WhenInsetNull() 
    {
        $queryMockup = new QueryFactoryMock();
        $businessLogicObject = new BusinessLogic($queryMockup);
        $response = $businessLogicObject->firstFunction(null);

        $this->assertEquals($response, "fail");
    }
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top