Чтение и сбор данных на модульном тестировании, пытаясь понять следующее сообщение , объясняет трудности статических вызовов функций.
Я не совсем понимаю эту проблему. Я всегда считал, что статические функции были хорошим способом округления функций полезности в классе. Например, я часто использую вызовы статических функций для инициализации, то есть:
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 ...
Но несколько десятков тестов, которые я написал для этого класса, одинаковы. Я ничего не изменил, и они все еще проходят. Я делаю что-то неправильно?
Автор сообщения утверждает следующее:
Основная проблема со статическими методами – это процедурный код. Я понятия не имею, как модульный код с модульным тестированием. Unit-testing предполагает, что я могу создать экземпляр части приложения отдельно. Во время создания я связываю зависимости с mocks / friendlies, которые заменяют реальные зависимости. При процедурной программировании нет ничего, чтобы «пронести», поскольку нет объектов, код и данные являются отдельными.
Теперь, я понимаю из сообщения, что статические методы создают зависимости, но не понимают интуитивно, почему невозможно проверить возвращаемое значение статического метода так же легко, как обычный метод?
Я буду избегать статических методов, но мне понравилось бы иметь представление о КОГДА статические методы полезны, если вообще. Похоже, что этот пост статические методы почти так же злы, как и глобальные переменные, и их следует избегать как можно больше.
Любая дополнительная информация или ссылки на эту тему были бы весьма полезны.
Статические методы сами по себе не сложнее тестировать, чем методы экземпляров. Проблема возникает, когда метод – статический или другой – вызывает другие статические методы, потому что вы не можете изолировать тестируемый метод. Вот типичный примерный метод, который трудно тестировать:
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()
.
Себастьян Бергман соглашается с Мишко Хевери и часто цитирует его:
В модульном тестировании требуются швы, швы – это то место, где мы предотвращаем выполнение нормального кода, и как мы достигаем изоляции тестируемого класса. Швы работают через полиморфизм, мы переопределяем / реализуем класс / интерфейс, а затем проводя тестируемый класс по-разному, чтобы контролировать поток выполнения. С помощью статических методов переопределить нечего. Да, статические методы легко вызвать, но если статический метод вызывает другой статический метод, невозможно переопределить зависимость вызываемого метода.
Основная проблема со статическими методами заключается в том, что они вводят связь, как правило, путем жесткого кодирования зависимости в вашем потребительском коде, что затрудняет их замену заглушками или макетами в ваших модульных тестах. Это нарушает принцип Open / Closed и принцип инверсии зависимостей , два из принципов SOLID .
Вы абсолютно правы, что статика считается вредной . Избежать их.
Проверьте ссылки для получения дополнительной информации.
Обновление: обратите внимание, что, хотя статика по-прежнему считается вредной, возможность заглушить и издеваться над статическими методами удалена с PHPUnit 4.0
Я не вижу никаких проблем при тестировании статических методов (по крайней мере, ни один из них не существует в нестатических методах).
include_path
.