логи в тестировании что это такое
JUnit тесты для логирования
Логирование относится к сквозной функциональности — разбросанной по коду и, как правило, редко покрываемой юнит-тестами. Слабое покрытие тестами, очевидно, связано с тем что вывод в лог не всегда достаточно важен и воспринимается скорее как вспомогательная функция а не цель работы метода, к тому же тестировать сквозную функциональность бывает достаточно сложно.
Но когда корректность вывода в лог становится критичной или же чувство прекрасного требует от нас полного покрытия кода тестами — без тестирования логгеров становится не обойтись.
Предположим у нас есть простой класс с log4j-логгером и методом doSomethingWithInt
Мы хотим протестировать факт того что вызов метода
приведет к выводу в лог
— метод doSomethingWithInt вызван с параметром i = 1
— параметр i больше нуля
Традиционный подход к тестированию предполагает, инжектирование mock-объекта (с помощью Mockitio) в тестируемый класс и, после отработки тестируемого кода, проверку того как и какие параметры передавались в mock.
Проблема в том, что инжектировать логгер в наш класс достаточно сложно — он не передается в ClassWithLog4JLogger а возвращается из static-метода, подменять returnValue которого Mockito не умеет (и у этого есть определенные основания — Mockito предназначено для тестирования объектов, в то время как static-метод относится к классу а не объекту). Но проблема, конечно, решаема — причем несколькими способами
Способ 1. Mock для log4j-Appender-а
“За неимением горничной имеем дворника. ” Пусть мы не можем подменить сам логгер — но мы можем подсунуть ему mock-аппендер и убедиться, что логгер передает в аппеддер те события которые мы ожидаем.
Добавим проект JUnit и Mockito
и напишем вот такой тест
Все вроде бы достаточно просто и не нуждается в пояснении.
Единственный минус такого подхода — так как мы тестируем не логгер а аппендер то мы проверяем не аргументы методов логгера а аргументы которые логгер передает аппендеру (LoggingEvent-ы), а их верификация занимает чуть больше строчек кода.
Данный подход сработает и в том случае если в качестве логгера используется slf4J Это надстройка над log4j (и еще несколькими фреймворками логирования), позволяющая, например, выводить в лог параметры без конкатинации строк (см. пример ниже). Сам slf4J-логгер не имеет методов для добавления аппендера. Но при этом он в процессе работы использует нижестоящий фреймворк (из найденных в classpath). Если этим фреймворком будет log4j то мы можем подсунуть mock-аппендер в log4j-логгер — он в свою очередь будет вызван из slf4J
Итак, добавляем зависимости на slf4j и его связку с log4j
И протестируем класс почти такой же как в предыдущем примере — отличие только в логгере и передачи параметров в лог (теперь без конкатенации строк)
Тест для него останется точно таким же — изменится только название класса для которого получаем логгер (при том что это по-прежнему log4j а не slf4j логгер)!
Способ 2. Подмена slf4j-имплементации.
Но что если мы все же хотим подменить не аппендер а сам логгер? Это таки-возможно. Как уже говорилось выше — slf4 использует на выбор один из нижестоящих фреймворков (log4j, logback и т.д.) Можно добавить в проект еще одну реализацию и при этом удалить из classpath остальные — тогда slf4 “подхватит” именно ее. А в тестовой реализации логгера есть методы позволяющие проверить его вызовы.
Итак — добавляем зависимость
и — ВАЖНО(!) удаляем в процессе сборки другие slf4j-логгеры если они есть в проекте.
Тест (для класса, используемого в прошлом примере) выглядит крайне просто
Проще некуда. Но есть и минус — работать тест будет только в связке с maven-ом или другой системой сборки которая удалит классы других логгеров, при этом предыдущий тест, заточенный на slf4j-log4j не отработает. На мой взгляд это не очень удобно так как сковывает нас в используемых средствах (обязательно запуск мавеном) и инструментах (не использование в тестах других логгеров).
Способ 3. Mock-логгер при помощи PowerMock
PowerMock — это как Mockito. Но круче. Чем круче? Тем, что может работать со static-методами, финальными классами, protected и даже private полями… Эдакий молоток в ювелирной лавке (кстати, именно кувалда изображена на эмблеме PowerMock) — в повседневном использовании инструмент слишком мощный, но иногда и без него — никуда. Вот и для тестирования логирования он отлично подходит — мы просто переопределялем метод LoggerFactory.getLogger и подсовываем ему наш mock-объект,
Резюме
Все способы имеют право на существование. Мокирование аппендера представляется наиболее простым, не требующем использования новых (кроме JUnit и Mockito) библиотек, но не работает непосредственно с логгером.
Slf4j-test требует минимум кода — но заставляет играться с подменой классов в classpath. А PowerMock достаточно прост и позволяет инжектить в тестируемый класс mock-логгер.
Логирование как способ отлаживать код
Почему так важно запретить самому себе отладку руками?
Когда вы отлаживаете программу, то вы, сами того не осознавая, думаете что за один отладочный сеанс исправите все проблемы, возникшие в рамках этой задачи. Но наша недальновидность не хочет верить в то, что на самом деле там не одна проблема, а несколько. И за один отладочный сеанс не получится решить все эти проблемы.
Поэтому вам надо будет несколько раз запускать этот код в отладочном режиме, проводя часы отладки над одним и тем же куском кода. И это только вы один столько времени потратили над этой частью программы. Каждый член команды, кому «посчастливится» работать с этим кодом, будет вынужден прожить ту же самую историю, которую прожили вы.
Я уже не говорю о том, что люди в командах меняются, команды меняются и так далее. Человеко-часы уходят на одно и то же. Перестаньте делать это. Я серьёзно. Возьмите ответственность за других людей на себя. Помогите им не переживать тот же самый участок вашей жизни.
Проблематика: Сложно отлаживать составной код
Возможный алгоритм решения проблемы:
Таким образом, вы избавляете других людей от отладки этого кода, т.к. если возникнет проблема, то человек посмотрит ваши логи и поймёт в чём причина. Логировать стоит только важные участки алгоритма.
Давайте посмотрим пример. Вы знаете, что по отдельности все реализации интерфейсов работают (т.к. написаны тесты, доказывающие это). Но при взаимодействии всего вместе возникает некорректное поведение. Что вам нужно? Нужно логировать ответы от «корректных» интерфейсов:
В этом примере видно, как мы трассируем отдельные части алгоритма для того, чтобы можно было зафиксировать тот или иной этап его выполнения. Посмотрев на логи, станет ясно в чём причина. В реальной жизни весь этот алгоритм стоило бы разбить на отдельные методы, но суть от этого не меняется.
Всё это в разы ускоряет написание кода. Вам не нужно бежать по циклу с F10 для того, чтобы понять на какой итерации цикла возникла проблема. Просто залогируйте состояние нужных вам объектов при определённых условиях на определённой итерации цикла.
В конечном итоге вы оставляете важные логи и удаляете побочные логи, например, где вы узнавали состояние объекта на определённой итерации цикла. Такие логи не помогут вашим коллегам, т.к. вы, скорее всего, уже поняли проблему некорректного алгоритма и исправили её. Если нет, то ведите разработку в вашей ветке до тех пор, пока не найдёте в чём причина и не исправите проблему. А вот логи, которые записывают важные результаты от выполнения других алгоритмов, понадобятся всем. Поэтому их стоит оставить.
Что если нам не нужен этот мусор? В таком случае выполняйте трассировку только в рамках отладки, а затем подчищайте за собой.
LogRock: Тестирование через логирование
LogRock: Тестирование через логирование
Уже более 2-х лет мы работаем над своим проектом Cleverbrush. Это софт для работы с векторной графикой. Работа с графическим редактором подразумевает огромное количество вариантов использования приложения. Мы пытаемся экономить деньги и время, поэтому оптимизируем все, в том числе тестирование. Покрывать тест кейсами каждый вариант это слишком дорого и нерационально, тем более что все варианты покрыть невозможно.
В ходе разработки был создан модуль для React JS приложений — LogRock (github).
Этот модуль позволяет организовать современное логирование приложения. На основании логов мы производим тестирование. В этой статье я расскажу Вам о тонкостях использования данного модуля и как организовать тестирование через логирование.
В чем проблема?
Если сравнивать программу с живым организмом, то баг в ней — это болезнь. Причиной возникновения этой «болезни» может стать целый ряд факторов, в том числе и окружение конкретного пользователя. Это особенно актуально если мы рассматриваем веб-платформу. Иногда причинно-следственная связь очень сложная, и баг, который нашли при тестировании — результат целого ряда событий.
Как и при человеческих недугах, лучше пациента никто не объяснит свои симптомы, ни один тестировщик не сможет рассказать, что произошло, лучше чем сама программа.
Что же делать?
Для понимания что происходит нам нужен список действий, которые совершил пользователь в нашем приложении.
Для того, чтобы наша программа сама нам могла сообщить что у неё «болит», мы возьмем модуль LogRock (github) и свяжем его с ElasticSearch, LogStash и Kibana.
ElasticSearch — мощная система полнотекстового поиска. Можете посмотреть тутор по ElasticSearch здесь.
LogStash — система сбора логов из всевозможных источников, которая умеет отправлять логи в том числе и в ElasticSearch.
Kibana — веб-интерфейс к ElasticSearch с большим количеством дополнений.
Как это работает?
В случае ошибки (или просто по требованию) приложение отправляет логи на сервер где они сохраняются в файл. Logstash инкрементально сохраняет данные в ElasticSearch — в базу данных. Пользователь заходит в Kibana и видит сохраненные логи.
Так выглядит хорошо настроенная Kibana. Она отображает данные из ElasticSearch. Kibana может отображать данные в виде таблиц, графиков, карт и т. д., что очень удобно для анализа и понимания что происходит с нашим приложением.
В данной статье я НЕ буду рассматривать настройку ElasticStack!
Создание системы логирования
Для примера мы будем интегрировать систему логирования в одностраничном JS приложении, написанном на React. В действительности не важно на каком фреймворке будет написано ваше приложение. Я постараюсь описать сам подход построения лог системы.
1. Клиент
1.0 LogRock. Установка
Для установки необходимо выполнить:
1.1 LogRock. Настройка приложения
Для начала обернем наше приложение в компонент
LoggerContainer – это компонент который реагирует на ошибки вашего приложения и формирует стек.
Стек – это объект с информацией о операционной системе пользователя, браузере, какая кнопка мыши или клавиатуры была нажата и конечно же подмассив actions, где записаны все действия юзера, которые он совершил в нашей системе.
LoggerContainer имеет ряд настроек, рассмотрим часть из них
active – включает или отключает логгер
limit – задает лимит на количество сохраняемых последних действий юзером. Если юзер совершит 21 действие, то первое в данном массиве автоматически удалится. Таким образом, мы будем иметь 20 последних действий, которые предшествовали ошибке.
onError – колбек, который вызывается в момент возникновения ошибки. В него приходит объект Стека, в котором сохранена вся информация об окружении, действиях пользователя и т.д. Именно из этого колбека нам необходимо отправлять эти данные в ElasticSearch или бекенд, или сохранять в файл для дальнейшего анализа и мониторинга.
1.2 LogRock. Логирование
Для того, чтобы произвести качественное логирование действий пользователя, нам придется покрыть наш код лог-вызовами.
В комплекте модуля LogRock идет логгер, который связан с LoggerContainer
Предположим у нас есть компонент
Для того, чтобы его правильно покрыть логом, нам нужно модифицировать метод toggle
Мы добавили логгер, в котором информация разделена на 2 части. React.Toggle показывает нам, что данное действие произошло на уровне React, компонента Toggle, а далее мы имеем словесное пояснение действия и текущий state, который пришел в этот компонент. Подобное разделение на уровни не обязательно, но с таким подходом будет понятнее, где конкретно выполнился наш код.
Также мы можем использовать метод “componentDidCatch”, который введен в React 16 версии, на случай возникновения ошибки.
2. Взаимодействие с сервером
Рассмотрим следующий пример.
Допустим, у нас есть метод собирающий данные о пользователе с бекенда. Метод асинхронный, часть логики запрятана в бекенд. Как правильно покрыть логами данный код?
Во-первых, так как у нас клиентское приложение, все запросы идущие на сервер будут проходить в рамках одной сессии юзера, без перезагрузки страницы. Для того, чтобы связать действия на клиенте с действиями на сервере, мы должны создать глобальный SessionID и добавлять его в хедер к каждому запросу на сервер. На сервере же мы можем использовать любой логгер, который будет покрывать нашу логику подобно примеру с фронтенда, и в случае возникновения ошибки отправлять эти данные с прикрепленным sessionID в Elastic, в табличку Backend.
1. Генерируем SessionID на клиенте
2. Мы должны установить SessionID для всех запросов на сервер. Если мы используем библиотеки для запросов, это сделать очень просто, объявив SessionID для всех запросов.
3. В LoggerContainer есть специальное поле для SessionID
4. Сам запрос (на клиенте) будет выглядеть так:
Как это все будет работать: у нас записывается лог, перед запросом на клиенте. По нашему коду мы видим, что сейчас начнется загрузка данных с сервера. Мы прикрепили SessionID к запросу. Если у нас покрыт бекенд логами с добавлением этого SessionID и запрос завершился ошибкой, то мы можем посмотреть что случилось на бекенде.
Таким образом, мы следим за всем циклом работы нашего приложения не только на клиенте, но и на бекенде.
3. Тестировщик
Работа с тестировщиком заслуживает отдельного описания процесса.
Так как у нас стартап, формальных требований мы не имеем и иногда в работе не все логично.
Если тестировщик не понимает поведение — это случай, который как минимум нужно рассмотреть. Также, зачастую, тестировщик просто не может повторить одну ситуацию дважды. Так как шаги, приведшие к некорректному поведению, могут быть многочисленными и нетривиальными. К тому же, не все ошибки приводят к критическим последствиям, таким как Exception. Некоторые из них могут лишь менять поведение приложения, но не трактоваться системой как ошибка. Для этих целей на стейджинге можно добавить кнопку в хедере приложения для принудительной отправки логов. Тестировщик видит, что что-то работает не так, нажимает на кнопку и отправляет Стек с действиями на ElasticSearch.
Если все-таки критическая ошибка произошла, мы должны блокировать интерфейс, чтобы тестировщик не кликал дальше и не заходил в тупик.
Для этих целей, мы выводим синий экран смерти.
Мы видим вверху текст со Стеком этой критической ошибки, а внизу — действия, которые ей предшествовали. Еще мы получаем ID ошибки, тестировщику достаточно его выделить и прикрепить к тикету. В последствии эту ошибку легко можно будет найти в Kibana по этому ID.
Для этих целей в LoggerContainer есть свои свойства
bsodActive – включает/отключает BSOD (отключение BSOD применимо к продакшен коду)
bsod – это компонент. По умолчанию, он выглядит как выше приведенный скриншот.
Для вывода кнопки в UI LoggerContainer, мы можем использовать в context
4. LogRock. Взаимодействие с пользователем
Вы можете выводить логи в консоль или показывать их юзеру, для этого нужно использовать метод stdout:
stdout – это метод, который отвечает за вывод сообщений.
Для того, чтобы сообщение стало важным достаточно передать в логгер вторым параметром true. Таким образом можно вывести это сообщение для пользователя в всплывающем окне, например при неудачной загрузке данных, мы можем вывести сообщение о ошибке.
Продвинутое логирование
Если вы используете Redux, или подобные решения с одним Store, вы можете в Middleware обработки ваших Actions поставить logger, тем самым, все значимые действия будут проходить через нашу систему.
Для эффективного логирования можно оборачивать ваши данные в Proxy-объект, и ставить логгеры на всех действиях с объектом.
Для покрытия логированием сторонних методов (методов библиотек, методов Legacy кода), вы можете использовать декораторы — “@”.
Советы
Логируйте приложения в том числе и на продакшене, потому что лучше, чем реальные пользователи, узкие места никакой тестировщик не найдет.
Не забудьте указать о сборе логов в лицензионном соглашении.
НЕ логируйте пароли, банковские данные и прочую личную информацию!
Избыточность логов это тоже плохо, делайте максимально понятными подписи.
Альтернативы
В качестве альтернативных подходов я выделяю:
Что дальше
Логирование — это не только поиск ошибок, это еще и мониторинг действий пользователя, сбор данных. Логирование может быть хорошим дополнением к Google Analytics и проверкой User Experience.
Выводы
Когда вы выпускаете приложение, для него жизнь только начинается. Будьте ответственны за свое детище, получайте отзывы, следите за логами и улучшайте его. Пишите качественный софт и процветайте 🙂
Тестирование ПО
Цель работы тестировщика
Зачем нужно тестировать софт?
Идёт 2021-й год и такой вопрос задают реже, но ответ на него знать нужно.
Тестировщик вообще нужен бизнесу для того, чтобы снять с разработчиков самую простую работу. Просто потому, что время тестировщика дешевле.
Изучение логов
Логи в стиле Матрицы. Фото: freepik.com
Если у Вас возникли проблемы с работой софта первым делом стоит изучить логи.
Что такое лог
Какие бывают логи
Лог может быть подробным, тогда он занимает больше места на диске и отвлекает больше ресурсов.
Чтобы сократить занимаемое место можно записывать только самые важные события.
Один и тот же софт может иметь несколько режимов логирования. Режим задаётся в настройках и отличается уровнем детализации.
Степень детализации может отличаться очень сильно. От никаких или минимальных записей вроде
2020-02-10-16-06-01T Включился
2020-02-10-16-08-23T Выключился
До записи каждого действия.
Часто одной и той же программе можно указать разный уровень подробности логов.
Для работы с логами может пригодится знание скриптовых языков программирования, или текстовых препроцессоров (sed, grep, awk)
Пример: показать только сегодняшние ERROR и WARNING строки из лога а также те, где присутствует слово panic
Где лежат логи в Windows
Лог файл обычно называется по дате, например 2021-12-01-heiheiru-log.txt или 2021-12-01-heiheiru.log
Расположение лог файла обычно зависит от конкретного проекта, например:
У одного клиента логи могут лежать в
Glassfish на Windows server может писать в
Где лежат логи в Linux
В Linux системные логи находятся в
Например, лог утилиты cron за сегодня находится в
Иногда проще спросить расположение логов у разработчика
Зачастую полезно посмотреть, что именно клиент пытается отправить на сервер.
Откройте логи с помощью Notepad++ и сделайте поиск по слову POST
Советую не пренебрегать опцией Find All in Current Document.
Зачастую смотреть полный лог нет смысла. В нём может быть очень много мусора, который легко убрать с помощью текстовых препроцессоров.
Кто должен читать логи: тестировщик или разработчик
Логи для того и созданы, чтобы когда софт работает неправильно тестировщик мог, например по таймкоду, найти проблемное место и приложить нужный кусок лога к баг-репорту.
Конечно, разработчик и сам может всё это сделать. Но его время стоит дороже и для бизнеса выгодно, чтобы всё что может делать тестировщик делал тестировщик.
Тестирование пользовательского ввода
Если есть хотя бы небольшой шанс того, что Вы будете тестировать что-то связаннное с user input, почитайте статью Big List of Naughty Strings
Изучение спецификаций
Перед началом работы над новым проектом Вам нужно будет изучить одну или несколько спецификаций.
То какая информация попадает в одну спецификацию, а какая в другую зачастую завист от менеджера ведущего проект, либо может быть чётко прописана в корпоративных правилах.
В любом случае, в спецификации интерфейсов мы ожидаем увидеть описание API и задача тестировщика здесь сводится к тому, чтобы
Так как логика разработчиков отличается от логики тестировщиков бывает полезным уточнить какие из перечисленных запросов создаются непосредственно клиентами а какие являются вторичными, то есть нуждаются в запросе триггере, который приходит от клиента или бэкенда.
Результатом проверки спецификации интерфейсов будет карта составленная в виде документа, либо просто в воображении тестировщика, которая накладывает на бизнес процессы соответсвующием им запросы либо цепочки запросов.
Контроль версий
Руководств и тренировочных материалов довольно много, моё можете найти в статье «GIT для начинающих»
Чем занимается тестировщик
Нужно помнить, что тестирование сильно зависит от того, в какой компании работает тестировщик.
Это очевидно, но тем не менее акцентирую внимание на том, что очень сложно стать универсальным тестировщиком, разве что сменив несколько работодателей из разных IT сфер.
Я прочитал некоторые вакансии в рунете и в LinkedIn и сделал подборку популярных требований и описаний задач.
Постараюсь перевести их на понятный новичку язык.
Тестирование отдельных задач в тестовом и рабочем окружениях
Если Вы тестируете сервер, который хостится Вашей конторой, то разница только в ответственности.
Покрытие тест-кейсами функционала системы
Означает, что нужно изучить спецификацию и понять, что можно протестировать. Затем описать эти тесты.
Проверка входящих баг-репортов из Tech Support
Клиенты обычно жалуются на баги и не только на баги.
Поддержка не всегда может быстро понять, что к чему, поэтому проще переслать баг-репорт тому тестировщику, который знаком с проектом.
Вы проверяете воспроизводится ли баг в тестовом окружении, если нет, то ковыряетесь в production логах где-нибудь на Kibana.
Функциональное тестирование и отслеживание качества выпускаемого сервиса
Анализ функциональности сервиса
Может означать всё что угодно. Похоже скорее на задание для исследовательского тестирования.
Общение с командой разработки и менеджерами, принятие совместных решений об улучшении сервиса.
Это неотъемлемая часть работы практически любого инженера по тестированию, причём не только софта.
Локализация и документирование дефектов.
Под локализацией обычно понимают выяснение источника проблемы. Это выливается в поиск логов, относящихся непосредственно к ошибке и отслеживанию stack trace.
Документация это: описать что вызывает баг, какое действие клиента или какой конкертно запрос. Максимальное количество полезной информации приветствуется.
Обязательно указывать версию ПО в которой был получен баг и приложить логи.
Оптимизация процесса тестирования внутри команды и постановка задач разработчикам автотестов
Запуск и анализ результатов автотестов
Это очевидное продолжение предыдущего пункта.
Проведение ручного функционального тестирования
Участие в регрессионном тестировании
Регрессионное тестирование обычно означает следующее. У Вас уже есть работающий продукт, но к нему пришёл Change Request (CR) и разработчики сделали новую фичу.
Фича работает, но теперь нужно понять не сломала ли новая фича что-то из старого функционала.
Ведение тестовой документации, подготовка тест кейсов
Рутина, без которой никуда особенно в большиъ компаниях.
Регистрация найденных дефектов в баг-трекере, контроль их исправления.
Назначение баг-трекеров это упрощение контроля за ошибками.
Взаимодействие с командой разработки.
2019-01-10 10:01:15 [ERROR]: Something is not ok
О ней написан репорт. Разработчик выпустил фикс. Тестировщик проверил и не увидев больше этого предупреждения в логах зааксептил.
Прошла неделя, тестировщик тестирует совершенно другую историю и вдруг
2019-01-17 10:01:15 [DEBUG]: Something is not ok
Тестировщик звонит разработчику и говорит, что ошибка снова появилась.
Присылайте свои истории в комментарии. Лучшие я включу в статью.
Куда складывают задачи и/или баги
Список планировщиков проектов и багтрекеров: