Namelles_One
@Namelles_One
Программист

Как придти к автоматическому тестированию?

Я пишу достаточно большое количество промышленного кода, в основном расширяя возможности платформы DocsVision (это СЭД, если кто не в курсе). Стараюсь использовать наиболее современные технологии, например, WPF и MVVM, фреймворки и поменьше велосипедов (так как почти все уже украдено до нас).

Тем не менее — проблема одна — я никак не могу придти к мысли модульного тестирования. Да, я читал про TDD сотоварищи, теоретически все представляю, но, видимо, не могу осмыслить, с чего начать.

Более предметно — вот есть модуль, ну, скажем, прикрепления файлов к карточке документа. Что здесь тестировать? Совпадение размера исходного файла или нового проверять? Али еще что? Или некий этап из жизни документа? ИМХО, проще руками его создать и проверить — пришло задание куда надо или нет. Что тут автоматизировать?..

Просто, существует ощущение некоего постоянного дискомфорта — что есть некий клевый механизм, все его юзают и рады, а я о нем знаю, да и только.

Что я делаю не так? Как проникнуться идеей тестирования? Да и так ли оно облегчает жизнь с промышленным кодом?
  • Вопрос задан
  • 3645 просмотров
Пригласить эксперта
Ответы на вопрос 8
@egorinsk
К сожалению, про тестирование и TDD много пишут всякие теоретики и любители написать 3 абстрактных класса ради вычисления факториала. Особенно плохо совместим TDD с активным рефакторингом.

Модульное тестирование не имеет особого смысла для модулей с примитивной логикой. Модульное тестирование во-первых, применяют, к модулям с математикой/хитрой внутренней логикой, во-вторых, проверять результат надо альтернативным способом.

Пример правильного модульного тестирования:

Например, есть функция решения квадратного. ур: x1, x2 = solveQuadEq(a, b, c );
Пишем к ней тест:

a, b, с = 1, 2, 3;
x1, x2 = solveQuadEq(a, b, c);
test::assertFloatEqual(a * x1 * x1 + b * x1 + c);
test::assertFloatEqual(a * x2 * x2 + b * x2 + c);

(Заметьте, результат проверяется подстановкой корней в уравнение назад, а не решением уравнения). И так несколько раз с разными a, b,c.

Имеет смысл применять модульное тестирование, например, для проверки модуля извлечения номеров телефонов из текста или модуля OCR.

Тестировать «тонкие» и «глупые» контроллеры и вьюхи — ровно столько же смысла, сколько тестировать работу функции printf(). То есть никакого.

Для вашей же задачи больше подойдет функциональное тестирование, то есть тестирование отдельных процессов и сценариев работы, например: создать документ, добавить файл, отредактировать документ, удалить документ, при этом проверять отсутствие зависаний (таймаутов), ошибок и предупреждений на клиенте и на сервере. Опционально (но не обязательно) можно проверять например, что документов стало на 1 больше, что в хранилище появился и исчез файл, что в отчете о документообороте добавились строчки, что пользователю отправлено письмо, в котором содержится такой-то номер документа или такое-то ключевое слово.

Не знаю, есть ли средство для организации подобного тестирования вашего ПО. Возможно, что такого нет в природе. Даже браузерные средства тестирования веб-приложений типа Selenium весьма неудобны и слаборазвиты.

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

Также, если это вам кажется сложным, можно тестировать продукт на пользователях: ввести максимальное логгирование всех ошибок и предупреждений, наставить всюду в коде assert() (это стоит делать в любом случае) и собирать жалобы пользователей на баги, но это не всегда возможно, одно дело бесплатный сервис вроде фейсбука, который сломался и ничего страшного, другое дело, если из-за ошибки нарушатся какие-нибудь многомиллионные бизнес-процессы в крупной корпорации или счета в банке.
Ответ написан
Wott
@Wott
Что бы понять можно посмотреть в сторону регрессионного тестирования.
Например сваяли вы модуль, создали на него тесты и всех все устроило. но тесты не забыты а собираются в кучу, которая целиком выполняется на очередном билде после успешного прохождения своих юнит тестов. Таким макаром мы точно можем сказать что ничего из того что работало не поломано. Но проблема в том что таких тестов много и со временем на тестирование тратиться существенная часть ресурсов. Выходом является автоматическое тестирование, в которое переносятся юнит тесты после завершения своего цикла разработки. Автоматические регрессионные тесты можно выполнять ночью или по расписанию, на отдельном окружении или на целых множествах систем. Железо дешевое — можно замутить целые кластера, которые будет обслуживать один тестер-админ. А разработчики и тестеры могут сосредоточиться на более интеллектуальном труде.
Ответ написан
Комментировать
BigMosquito
@BigMosquito
Более предметно — вот есть модуль, ну, скажем, прикрепления файлов к карточке документа. Что здесь тестировать? Совпадение размера исходного файла или нового проверять? Али еще что? Или некий этап из жизни документа?

Вы можете, например, приатачить документ, закрыть/сохранить файл, потом открыть документ опять, «стянуть» атачмент и проверить на размер/название/открываемость в родном приложении.
Ответ написан
Комментировать
Подумайте какие сценарии использования вашего модуля могут быть, что поступает на вход и что должно быть на выходе. Вход и выход это не только параметры конструктора/методов и возвращаемое return значение, но и состояние БД, ФС и т. п., а для выхода ещё и исключения. Для каждого сценария подготавливаете с нуля вход (создаёте нужные объекты рантайма, файлы, схему и данные в БД), выполняете сценарий (в простейшем случае вызов одного метода) и проверяете выход.

Если архитектура не заточена под тестирование, то, скорее всего, тесты будут большие, хрупкие и медленные, ведь кроме вашей собственной логики они будут проверять логику системы, фреймворка,, библиотек, ОС и т. п. Наступает время рефакторинга с целью облегчения тестирования. Скажем все файловые операции в тестируемом модуле выделяете в методы отдельного класса FileSystem, который передаётся вашему модую в параметрах или контейнере. Убеждаетесь, что первая группа тестов всё ещё работает после рефакторинга, копируете её как новую группу и начинаете заменять в ней реальные вызовы на фэйковые, моки и стабы. Получаете более лёгкие тесты. Как-то так.
Мне в своё время очень помогла книга Эффективная работа с унаследованным кодом
Ответ написан
Комментировать
@petuhov_k
Если Ваш модуль является простой прослойкой к DV, то тестировать в нём нечего — это забота «ребят из питера». Если же Ваш модуль содержит ещё какую-то логику то её и надо протестировать.

Например, вы прежде чем прикрепить документ к карточке, проверяете его размер и формат. Допустим вы не хотите, чтобы пользователи клали в документы полноцветные сканы или видео (у нас бывало и такое). Тогда Вы пишете «прослойку» к DV, чтобы её можно было подменить на время тестов и не дёргать саму DV непосредственно. И дальше проверяете различные юзкейсы: 1) дали чб bmp — должен пройти вызов к «прослойке» по добавленю файла, 2) дали avi на 200мб, вызов к dv не идёт, идёт эксепшн или возвращается соответствующий код и т.д.
Ответ написан
Комментировать
@AM5800
Я бы посоветовал начать с того, чтобы писать тесты для того, что уже упало. Например, после тестирования тестер сказал, что при нажатии кнопки «Создать документ» падает исключение. Ок. Пишете тест, который это воспроизводит. И только потом чините исключение.

Примите за правило прежде чем сломя голову бросаться чинить такие ошибки — сначала написать тест. Со временем придет понимание и что тестировать, и как, а главное — после тяжелых мучений в духе «Блиииин. Ну вот и как написать тест для ЭТОГО?» придет понимание как писать код так, чтобы его можно было тестировать.

Здесь уже написали кучу примеров как писать тесты для разных ситуаций — «вот если есть то и это, то тест надо писать так». Но самый лучший генератор примеров — жизнь. Пишите тесты. Учитесь на своих ошибках. Ну, как обычно.

Еще люто рекомендую системы автоматической сборки и тестирования, описанные в посте Wott'а. Например, у нас после push'а в СКВ — на специально выделенном сервере происходит сборка проекта и запуск всех тестов. Главное преимущество в этом лично для меня — практически нет необходимости гонять тесты на моей машине. Ведь наши тесты могут работать по несколько часов.

Также могу посоветовать различные программы для анализа покрытия тестами. Часто такие программы помимо непосредственно анализа покрытия умеют анализировать и некоторые другие полезные метрики. Например, С.R.A.P. — метрика. Сортировка по такой метрике может помочь обнаруживать сложные, не покрытые тестами функции. Для них стоит писать юнит-тесты в первую очередь.
Ответ написан
Комментировать
ganouver
@ganouver
Сам проходил через такое и задавал себе аналогичные вопросы.
А потом что-то щелкнуло и все встало на свои места.

По этому поводу я написал пост.
И в комментариях к нему есть ответы на те вопросы, которые Вы задаете.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы