Как стать автором
Обновить

Комментарии 26

Я не гуру TDD, тоже новичек в этом деле, но попробую дать пару советов:
1) Проверка на реализацию CacheInterface лишняя. В статически типизированных языках эта ошибка обнаружится на этапе компиляции, в динамических — при попытке вызвать нереализованный метод. Все методы интерфейся CacheInterface вы зовете в дальнейших тестах, поэтому ошибку обнаржуите и без этой проверки
2) В своих тестах стараюсь придерживаться такого правила: каждый тест проверяет не более одного метода. Т.е. ответ на ваш первый вопрос: тест может тестировать кусок метода, челый метод, но не несколько методов сразу.
3) В тестах для кеша работу с ФС я бы тоже заменил на Stub. Юнит-тест не должен зависить от внешних файлов/сервисов.
С вашим третьим пунктом я согласен. Не могли бы вы показать пример того, как можно протестировать работу с ФС в моем случае?
Есть например vfsStream, с помощью него можно абстрагироваться от реальной файловой системы.
Сейчас попробую представить.
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.

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

Если вы используете пространства имён, могу предложить реализовать в некотором роде извращённый mock:
Подключить файл в котором переопределяются родные функции работы с ФС для namespace класса FileCache.
Естественно работать будет только если вы не вызываете функции с явным указанием корневого namespace.
Не обратил внимания на комментарий с vfsStream. Этот подход, конечно, более правильный и нативный.
Сам использую такой вариант для тестирования оберток для работы с БД, но по-хорошему это конечно тот еще костыль, получается тест меняет окружение, и тест другого класса с тем-же пространством имен столкнется с уже переопределенными функциями, вне зависимости от того нужны они в нем или нет. Был-бы в php нормальный use, как например import namespace.* в java — было-бы вообще идеально, а так — вот как-то так.
Ага, там как раз описывается пример работы с vfsStream. Спасибо!
Какие-то стремные и не мотивирующие плюсы.
Упс, глюк хабра, не к этому топику комментарий.
НЛО прилетело и опубликовало эту надпись здесь
НЛО прилетело и опубликовало эту надпись здесь
Когда я писал тесты, я руководствовался такой логикой, что тест не знает, с каким именем создается кеш-файл. Но так как каталог 100% пустой (он очищается и создается при подготовке фикстуры), то стоит проверить корректность выставления для кеш-объекта каталога записи именно путем проверки изменения количеством файлов в каталоге.
НЛО прилетело и опубликовало эту надпись здесь
У вас есть зависимость FileCache от времени. Я бы сделал её явной — передавал бы в конструктор объект «Часы», который показывал бы текущее время. А в тестах использовал бы Stub, и переводил бы часы как заблагорассудится. Тогда и sleep() вызывать не понадобится.
Еще у меня были мысли напрямую изменять время доступа к созданному кеш-файлу, sleep() использовать бы не пришлось, но зависимость бы не исчезла
testFileCacheClassShouldImplementCacheInterface

ЯБыНеСталНазыватьТакФункции
А какой ваш вариант?
Нормальное название тест метода.
Чорт. Я имел ввиду что вы нормально назвали ваш тестовый метод.
нормально название из конвенции имен Zend'а — CamelCaps
длинновато немного, но это методы эти конечны в плане использования
Подобные названия тестовых методов идут из программы TestDox и достаточно распространены.

В частности 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

Twitter
[x] Twitter should call http client with correct url
[x] Twitter should load data from cache if is possible
После каждой подобной статьи я все меньше вижу смысла в unit тестах.

Написать хороший unit тест — задача далеко не тривиальная. И вы с ней не справились.
— Тот же класс FileCache может писать в один, crc32($id)%10 и т.д. файлов. А может, да и должен, делить все на поддиректории, т.к. некоторые файловые системы начинают тормозить, если много файлов в одной директории.
— Время жизни задавать через конструктор, а не для каждого ключа отдельно — не слишком гибко.
— fileatime может не работать, ибо сохранение времени последнего доступа к файлу замедляет файловую систему. И в директориях с кешом иногда это отключают. Кроме того, старые файлы не удаляются.

Дальше смотреть, если честно, было лень.

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

 

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

Как agile методики применимы только в сильных командах, которые понимают что делают и почему, также и тут — нельзя преподносить сложную для реализации идею как нечто супер простое, ибо простым TDD не является.

 

ЗЫ: В функциональном тестировании я смысл вижу. И большой.

 

 

Еще раз пробежался по комментариям выше. Предлагают, например, «передавать в конструктор объект „Часы“. Это просто феерично! TDD, блин, головного мозга.

Уверен, что TDD изобретался как принцип, но никак не с фразами „100% покрытия кода“ или иной другой перфекционистской чушью. А только потом эту, в принципе, светлую идею опошлили люди, которые ни одного готового продукта не выпустили. Аналогично было и с венгерской нотацией.
Да, кстати, у вас тут путаница получается.

Поскольку вы тестируете класс FileCache, надо четко тестировать его поведение. И «просто проверять появились ли вообще какие-то новый файлы в директории» — неверно. Раз вы тестируете конкретный класс, то и тестировать надо какие файлы появились, где, что в них записано и т.д. Т.е. тестировать не интерфейс, а именно реализацию.

Если же вы хотите протестировать интерфейс, что, скорее, будет функциональным тестированием, то интерфейс ничего о файлах не знает. Интерфейс знает лишь о методах get() и set().

Хотя на гуру тестирования я не претендую ни в коем случае, но как-то так намного логичнее выглядит.

 

Мне вот интересно, «НЛО» просто увидело надпись «TDD» и поэтому инвайт выдало или все же это я ничего не смыслю?
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории