Комментарии 26
Я не гуру TDD, тоже новичек в этом деле, но попробую дать пару советов:
1) Проверка на реализацию CacheInterface лишняя. В статически типизированных языках эта ошибка обнаружится на этапе компиляции, в динамических — при попытке вызвать нереализованный метод. Все методы интерфейся CacheInterface вы зовете в дальнейших тестах, поэтому ошибку обнаржуите и без этой проверки
2) В своих тестах стараюсь придерживаться такого правила: каждый тест проверяет не более одного метода. Т.е. ответ на ваш первый вопрос: тест может тестировать кусок метода, челый метод, но не несколько методов сразу.
3) В тестах для кеша работу с ФС я бы тоже заменил на Stub. Юнит-тест не должен зависить от внешних файлов/сервисов.
1) Проверка на реализацию CacheInterface лишняя. В статически типизированных языках эта ошибка обнаружится на этапе компиляции, в динамических — при попытке вызвать нереализованный метод. Все методы интерфейся CacheInterface вы зовете в дальнейших тестах, поэтому ошибку обнаржуите и без этой проверки
2) В своих тестах стараюсь придерживаться такого правила: каждый тест проверяет не более одного метода. Т.е. ответ на ваш первый вопрос: тест может тестировать кусок метода, челый метод, но не несколько методов сразу.
3) В тестах для кеша работу с ФС я бы тоже заменил на Stub. Юнит-тест не должен зависить от внешних файлов/сервисов.
+2
С вашим третьим пунктом я согласен. Не могли бы вы показать пример того, как можно протестировать работу с ФС в моем случае?
0
Есть например vfsStream, с помощью него можно абстрагироваться от реальной файловой системы.
+1
Сейчас попробую представить.
FileCache от ФС нужно следующее:
1. При вызове save создать файл.
2. При вызове load вернуть файл.
3. Для заданного файла вернуть время его создания
Извиняюсь, не пишу на PHP, поэтому приведу пример на Java-подобном псевдокоде.
Всю работу с ФС я бы выделил в отдельный класс:
class FileSystem {
void saveFile(filename, data);
bytes[] loadFile(filename);
Time getFileTime(filename);
}
Экземпляр этого класса добавил бы в виде поля к FileCache. Скорей всего, он бы инициализоровался в конструкторе чтобы все это происходило максимально прозрачно для production-кода. Для тестов бы оставил сеттер, чтобы возможно было заменить объект на стаб или мок.
Тесты бы поменял следующим образом:
testSettingCacheDir — FileSystem заменяется на мок, ожидаем вызова saveFile с требуемым именем файла и данными. Как мне кажется, этого будет достаточно, проверка на количество файлов до и после лишняя.
testSettingCacheLifetime — FileSystem заменется на стаб, в getFileTime возращающий время, когда кеш уже невалиден. Проверяем, что load возращает false. Затем, заменяем стаб на другой, возращающий еще валидное для кеша время и тестовые данные для какого-либо файла и проверяем, что cache->load возвращает валидные данные.
testLoadShouldReturnFalseOnNonexistId — FileSystem заменяем на стаб, возвращющий null для какого-либо файла и проверяем что load возвращает null.
Я могу и ошибатся, буду рад услышать комментарии от более опытных людей.
Очень жаль, что тема нашла такой маленький отклик, тестирование сложных модулей, взаимодействующий с внешними ресурсами, как мне кажется, очень слабо освещенна и вы молодец, что подняли ее.
FileCache от ФС нужно следующее:
1. При вызове save создать файл.
2. При вызове load вернуть файл.
3. Для заданного файла вернуть время его создания
Извиняюсь, не пишу на PHP, поэтому приведу пример на Java-подобном псевдокоде.
Всю работу с ФС я бы выделил в отдельный класс:
class FileSystem {
void saveFile(filename, data);
bytes[] loadFile(filename);
Time getFileTime(filename);
}
Экземпляр этого класса добавил бы в виде поля к FileCache. Скорей всего, он бы инициализоровался в конструкторе чтобы все это происходило максимально прозрачно для production-кода. Для тестов бы оставил сеттер, чтобы возможно было заменить объект на стаб или мок.
Тесты бы поменял следующим образом:
testSettingCacheDir — FileSystem заменяется на мок, ожидаем вызова saveFile с требуемым именем файла и данными. Как мне кажется, этого будет достаточно, проверка на количество файлов до и после лишняя.
testSettingCacheLifetime — FileSystem заменется на стаб, в getFileTime возращающий время, когда кеш уже невалиден. Проверяем, что load возращает false. Затем, заменяем стаб на другой, возращающий еще валидное для кеша время и тестовые данные для какого-либо файла и проверяем, что cache->load возвращает валидные данные.
testLoadShouldReturnFalseOnNonexistId — FileSystem заменяем на стаб, возвращющий null для какого-либо файла и проверяем что load возвращает null.
Я могу и ошибатся, буду рад услышать комментарии от более опытных людей.
Очень жаль, что тема нашла такой маленький отклик, тестирование сложных модулей, взаимодействующий с внешними ресурсами, как мне кажется, очень слабо освещенна и вы молодец, что подняли ее.
0
Если вы используете пространства имён, могу предложить реализовать в некотором роде извращённый mock:
Подключить файл в котором переопределяются родные функции работы с ФС для namespace класса FileCache.
Естественно работать будет только если вы не вызываете функции с явным указанием корневого namespace.
Подключить файл в котором переопределяются родные функции работы с ФС для namespace класса FileCache.
Естественно работать будет только если вы не вызываете функции с явным указанием корневого namespace.
0
Не обратил внимания на комментарий с vfsStream. Этот подход, конечно, более правильный и нативный.
0
Сам использую такой вариант для тестирования оберток для работы с БД, но по-хорошему это конечно тот еще костыль, получается тест меняет окружение, и тест другого класса с тем-же пространством имен столкнется с уже переопределенными функциями, вне зависимости от того нужны они в нем или нет. Был-бы в php нормальный use, как например import namespace.* в java — было-бы вообще идеально, а так — вот как-то так.
+1
«Mocking the Filesystem» — www.phpunit.de/manual/3.5/en/test-doubles.html
там все довольно доходчиво описано
там все довольно доходчиво описано
+1
Какие-то стремные и не мотивирующие плюсы.
0
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Когда я писал тесты, я руководствовался такой логикой, что тест не знает, с каким именем создается кеш-файл. Но так как каталог 100% пустой (он очищается и создается при подготовке фикстуры), то стоит проверить корректность выставления для кеш-объекта каталога записи именно путем проверки изменения количеством файлов в каталоге.
0
У вас есть зависимость FileCache от времени. Я бы сделал её явной — передавал бы в конструктор объект «Часы», который показывал бы текущее время. А в тестах использовал бы Stub, и переводил бы часы как заблагорассудится. Тогда и sleep() вызывать не понадобится.
0
testFileCacheClassShouldImplementCacheInterface
ЯБыНеСталНазыватьТакФункции
ЯБыНеСталНазыватьТакФункции
-5
А какой ваш вариант?
-1
Подобные названия тестовых методов идут из программы TestDox и достаточно распространены.
В частности phpunit может выдавать чеклист c результатами тестирования в человекопонятной форме. В данном случае результаты будут выглядеть следующим образом:
В частности phpunit может выдавать чеклист c результатами тестирования в человекопонятной форме. В данном случае результаты будут выглядеть следующим образом:
PHPUnit 3.5.5 by Sebastian Bergmann.
FileCache
[x] File cache class should implement cache interface
[x] Setting cache dir
[x] Setting cache lifetime
[x] Load should return false on nonexist id
[x] Twitter should call http client with correct url
[x] Twitter should load data from cache if is possible
+1
После каждой подобной статьи я все меньше вижу смысла в unit тестах.
Написать хороший unit тест — задача далеко не тривиальная. И вы с ней не справились.
— Тот же класс FileCache может писать в один, crc32($id)%10 и т.д. файлов. А может, да и должен, делить все на поддиректории, т.к. некоторые файловые системы начинают тормозить, если много файлов в одной директории.
— Время жизни задавать через конструктор, а не для каждого ключа отдельно — не слишком гибко.
— fileatime может не работать, ибо сохранение времени последнего доступа к файлу замедляет файловую систему. И в директориях с кешом иногда это отключают. Кроме того, старые файлы не удаляются.
Дальше смотреть, если честно, было лень.
А вот дальше я сижу и смотрю. Вы потратили кучу времени на написание тестов там, где это не надо. Не надо с точки зрения требований к качеству и сложности кода. Причем тесты написали плохие. И риторический вопрос заключается в следующем: какой смысл в потраченном времени?
На всякий случай, думаю, надо пояснить. Я люблю утрировать. И в TDD смысл вижу. Но его никак нельзя преподносить в таком виде, ибо адекватный человек сразу задаст вопрос про эффективность потраченного времени.
Как agile методики применимы только в сильных командах, которые понимают что делают и почему, также и тут — нельзя преподносить сложную для реализации идею как нечто супер простое, ибо простым TDD не является.
ЗЫ: В функциональном тестировании я смысл вижу. И большой.
Еще раз пробежался по комментариям выше. Предлагают, например, «передавать в конструктор объект „Часы“. Это просто феерично! TDD, блин, головного мозга.
Уверен, что TDD изобретался как принцип, но никак не с фразами „100% покрытия кода“ или иной другой перфекционистской чушью. А только потом эту, в принципе, светлую идею опошлили люди, которые ни одного готового продукта не выпустили. Аналогично было и с венгерской нотацией.
Написать хороший unit тест — задача далеко не тривиальная. И вы с ней не справились.
— Тот же класс FileCache может писать в один, crc32($id)%10 и т.д. файлов. А может, да и должен, делить все на поддиректории, т.к. некоторые файловые системы начинают тормозить, если много файлов в одной директории.
— Время жизни задавать через конструктор, а не для каждого ключа отдельно — не слишком гибко.
— fileatime может не работать, ибо сохранение времени последнего доступа к файлу замедляет файловую систему. И в директориях с кешом иногда это отключают. Кроме того, старые файлы не удаляются.
Дальше смотреть, если честно, было лень.
А вот дальше я сижу и смотрю. Вы потратили кучу времени на написание тестов там, где это не надо. Не надо с точки зрения требований к качеству и сложности кода. Причем тесты написали плохие. И риторический вопрос заключается в следующем: какой смысл в потраченном времени?
На всякий случай, думаю, надо пояснить. Я люблю утрировать. И в TDD смысл вижу. Но его никак нельзя преподносить в таком виде, ибо адекватный человек сразу задаст вопрос про эффективность потраченного времени.
Как agile методики применимы только в сильных командах, которые понимают что делают и почему, также и тут — нельзя преподносить сложную для реализации идею как нечто супер простое, ибо простым TDD не является.
ЗЫ: В функциональном тестировании я смысл вижу. И большой.
Еще раз пробежался по комментариям выше. Предлагают, например, «передавать в конструктор объект „Часы“. Это просто феерично! TDD, блин, головного мозга.
Уверен, что TDD изобретался как принцип, но никак не с фразами „100% покрытия кода“ или иной другой перфекционистской чушью. А только потом эту, в принципе, светлую идею опошлили люди, которые ни одного готового продукта не выпустили. Аналогично было и с венгерской нотацией.
+7
Да, кстати, у вас тут путаница получается.
Поскольку вы тестируете класс FileCache, надо четко тестировать его поведение. И «просто проверять появились ли вообще какие-то новый файлы в директории» — неверно. Раз вы тестируете конкретный класс, то и тестировать надо какие файлы появились, где, что в них записано и т.д. Т.е. тестировать не интерфейс, а именно реализацию.
Если же вы хотите протестировать интерфейс, что, скорее, будет функциональным тестированием, то интерфейс ничего о файлах не знает. Интерфейс знает лишь о методах get() и set().
Хотя на гуру тестирования я не претендую ни в коем случае, но как-то так намного логичнее выглядит.
Мне вот интересно, «НЛО» просто увидело надпись «TDD» и поэтому инвайт выдало или все же это я ничего не смыслю?
Поскольку вы тестируете класс FileCache, надо четко тестировать его поведение. И «просто проверять появились ли вообще какие-то новый файлы в директории» — неверно. Раз вы тестируете конкретный класс, то и тестировать надо какие файлы появились, где, что в них записано и т.д. Т.е. тестировать не интерфейс, а именно реализацию.
Если же вы хотите протестировать интерфейс, что, скорее, будет функциональным тестированием, то интерфейс ничего о файлах не знает. Интерфейс знает лишь о методах get() и set().
Хотя на гуру тестирования я не претендую ни в коем случае, но как-то так намного логичнее выглядит.
Мне вот интересно, «НЛО» просто увидело надпись «TDD» и поэтому инвайт выдало или все же это я ничего не смыслю?
0
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Учебный пример разработки PHP-класса с использованием TDD