модульное тестирование и статические методы


Читаю и разбираюсь в модульном тестировании, пытаясь разобраться в следующем посте, в котором объясняются трудности вызовов статических функций.

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

Init::loadConfig('settings.php');
Init::setErrorHandler(APP_MODE); 
Init::loggingMode(APP_MODE);

// start loading app related objects ..
$app = new App();

// Прочитав этот пост, я теперь стремлюсь к этому вместо этого...

$init = new Init();
$init->loadConfig('settings.php');
$init->loggingMode(APP_MODE);
 // etc ...

Но, немногие дюжина тестов, которые я написал для этого класса, одинаковы. Я ничего не изменил, и они все равно все проходят. Я делаю что-то не так?

Автор поста заявляет следующее:

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

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

Я буду избегать статических методов, но мне бы хотелось иметь представление о том, КОГДА статические методы полезны, если вообще полезны. Кажется, из этого постстатические методы являются примерно таким же злом, как и глобальные переменные, и их следует избегать, насколько это возможно.

Мы будем весьма признательны за любую дополнительную информацию или ссылки по этому вопросу.

Author: hakre, 2011-05-11

3 answers

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

public function findUser($id) {
    Assert::validIdentifier($id);
    Log::debug("Looking for user $id");  // writes to a file
    Database::connect();                 // needs user, password, database info and a database
    return Database::query(...);         // needs a user table with data
}

Что вы могли бы захотеть протестировать с помощью этого метода?

  • Передача чего-либо, кроме положительного целого числа, выбрасывает InvalidIdentifierException.
  • Database::query() получает правильный идентификатор.
  • Соответствующий пользователь возвращается, когда найден, null когда нет.

Эти требования просты, но вы также должны настроить ведение журнала, подключиться к базе данных, загрузить ее данными и т.д. Класс Database должен нести единоличную ответственность за проверку того, что он может подключаться и запрашивать. Класс Log должен делать то же самое для ведения журнала. findUser() не должен иметь дела ни с чем из этого, но должен, потому что это зависит от них.

Если вместо этого метод, описанный выше, вызывал экземпляр методы в экземплярах Database и Log тест может проходить в макетных объектах с запрограммированными возвращаемыми значениями, специфичными для данного теста.

function testFindUserReturnsNullWhenNotFound() {
    $log = $this->getMock('Log');  // ignore all logging calls
    $database = $this->getMock('Database', array('connect', 'query');
    $database->expects($this->once())->method('connect');
    $database->expects($this->once())->method('query')
             ->with('<query string>', 5)
             ->will($this->returnValue(null));
    $dao = new UserDao($log, $database);
    self::assertNull($dao->findUser(5));
}

Вышеуказанный тест завершится неудачей, если findUser() не вызовет connect(), передаст неверное значение для $id (5 выше), или возвращает что-либо, кроме null. Прелесть в том, что база данных не задействована, что делает тест быстрым и надежным, а это означает, что он не завершится неудачей по причинам, не связанным с тестом, таким как сбой сети или плохие данные образца. Это позволяет вам сосредоточиться о том, что действительно важно: функциональность, содержащаяся в findUser().

 51
Author: David Harkness, 2011-05-11 08:28:44

Себастьян Бергманн согласен с Миско Хевери и часто цитирует его:

Модульное тестирование требует швов, швы - это то, где мы предотвращаем выполнение обычного пути кода и как мы достигаем изоляции тестируемого класса. Швы работают через полиморфизм, мы переопределяем/реализуем класс/интерфейс, а затем подключаем тестируемый класс по-другому, чтобы взять под контроль поток выполнения. Со статическим методы, которые нечего переопределять. Да, статические методы легко вызывать, но если статический метод вызывает другой статический метод, невозможно переопределить зависимость вызываемого метода.

Основная проблема со статическими методами заключается в том, что они вводят связь, обычно путем жесткого кодирования зависимости в ваш потребляющий код, что затрудняет их замену заглушками или насмешками в ваших модульных тестах. Это нарушает принцип Открытого/закрытого и зависимость Принцип инверсии, два из ТВЕРДЫХ принципов.

Вы абсолютно правы в том, что статика считается вредной. Избегайте их.

Пожалуйста, проверьте ссылки для получения дополнительной информации.

Обновление: обратите внимание, что, хотя статика по-прежнему считается вредной, возможность заглушать и имитировать статические методы была удалена с PHPUnit 4.0

 18
Author: Gordon, 2014-07-22 21:44:41

Я не вижу никаких проблем при тестировании статических методов (по крайней мере, таких, которых не существует в нестатических методах).

  • Фиктивные объекты передаются тестируемым классам с использованием внедрения зависимостей.
  • Имитационные статические методы могут быть переданы тестируемым классам с помощью подходящего загрузчика или манипулирования include_path.
  • Поздняя статическая привязка имеет дело с методами, вызывающими статические методы в том же классе.
 1
Author: Oswald, 2011-05-11 08:59:32