Pull to refresh

Comments 16

Если используем mockito доля тестов, то для создания моков можно использовать аннотацию - @Mock, получается короче.

А статья о coverage будет? Как померить на сколько мы код покрыли.

Согласен, в случае использования mockito, создавать моки можно с помощью аннотации. Я предпочитаю запись вида val locationRepository: LocationRepository = mock(),потому что в этом случае можно использовать val вместо var, ну и зачастую так получается 1 строка вместо 2.

По поводу coverage, есть несколько интересных моментов, которые хотелось бы рассмотреть. В любом случае, за качеством тестов следует следить, и coverage одна из метрик, но и с ней следует быть осторожным, высокое покрытие не гарантирует высокое качество тестов, хотя вот высокого качество тестов вряд ли удастся достичь без высокого покрытия.

Работу с асинхронным кодом рассматривать будут? Там есть особенности.

Хорошее замечание! С асинхронный кодом всегда следует быть особенно внимательным, и там есть достаточно много особенностей.

На данный момент пока не планирую статей про особенности таких тестов. В этих вопросах зачастую много особенностей выбранной технологии. (Rx, Classic threads, Kotlin Coroutines, свои проприетарные решения).

К сожалению тогда большое количество кода останется за скобками - тем более сейчас когда корутины упростили работу с фоновыми задачами по сути до myFun = job (т.е. viewModel scope.launch{})

А у корутин не самое понятное апи для тестирования, откровенно говоря.

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

Свойства, характеризующие хорошие unit тесты

Дедушка Мартин это назвал FIRST. Легко запомнить. Как SOLID для приложений.

Еще можно добавить, что нужно избегать сразу нескольких ассертов в одном тесте. Это имеет смысл только, если проверяемые значения однозначно зависят друг от друга. В других же случаях, лучше написать 2 однотипных тестах, в которых вы проверите только 1 значение.

Чем это лучше? Это реализация свойства "Изоляция" в Ваших тестах. Если Вы сломаете логику расчета одного из проверяемых параметров, то в первом случае у Вас упадет сразу несколько тестов, в каждом вы проверяете несколько результирующих значений – как итог, Вам придется долго сидеть и анализировать все, что происходит в проверяющем тесте, чтобы найти, а в чем же конкретно проблема. Во втором же случае, у вас упадет только 1 тест, в котором только 1 проверка, вы тут же поймете, куда конкретно вам надо смотреть.

Принцип "Изоляции" подразумевает, что в случае проблемы в коде, у вас должен упасть ровно 1 тест, четко указывающий на 1 единственное место, где искать проблему.

Подумайте, как поправить Ваш тест, в котором сразу несколько ассертов.

А как быть если я хочу порядок вызовов у моков проверить?

А зачем Вам проверять порядок вызова методов у моков?

Есть еще одно золотое правило тестирования. Вы не проверяете внутреннюю реализацию Вашего кода, Вы проверяете результат. Тестирование Вы должны проводить "черного ящика", а не "белого".

Это тоже реализация принципа "Изоляции". Ваш тест не должен зависеть от особенностей реализации Вашего кода. Если Вы захотите отрефакторить Ваш код и поменять реализацию, то Вам придется переписывать все Ваши тесты. А этого надо избегать. И - изоляция.

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

Пример покажете? Можно будет подумать вместе.

Ну мы же об андроид разработке? Я хочу проверить что спустя 200 мс будет показан прогресс бар, если данные не пошли, если пошли, то не будет показан. Или что отмена progress bar происходит после отправки готовых данных - не мигает белый экран. Юнит тесты же это позволяют проверить. Возможно я не верно ставлю задачу?

Базовые принципы тем и хороши, что их можно к любой разработке применять. Даже к вебу или серверу ^.^

если данные не пошли, если пошли, то не будет показан

Вот это мне не очень понятно, но вообще вся идея про 200мс кажется неуместной. а что если запрос будет у вас отрабатывать 300мс, все равно будем мерцание? Но ок, давайте рассмотрим и ее.

Все, что вы тут описали можно разбить на кучу разных тестов с 1 лишь проверкой. Разве нет?

  1. проверяем, что запустился таймер. ждать 200мс в тестах - моветон по двум причинам:

    1. нарушаем буковку F - Fast, тест становится медленным

    2. тестировать системное поведение (то, что таймер вызовется, если мы его запустили) - глупо. Это за пределами нашего влияния, поэтому и тестировать это бесполезно.

  2. показ прогресс бара вы проверяете уже в другом тесте и в другой функции.

  3. отмена прогресс бара - еще один тест.

  4. то, что отмена вызовется по в колбеке получения данных - еще один и т.д.

Еще одно золотое правило в написании тестов: "Разделяй и властвуй". Не старайтесь одним тестом покрыть все Ваше приложение.

Подождите, корутины оперируют виртуальным временем, ждать там ничего не надо. delay(200) просто сдвинет виртуальное время.

Всю функцию можно свести к extension над coroutine - соответственно это цельная функция с изменением состояния стороннего объекта. Ее тесты все равно желательно дробить?

Ну, я считаю, что да. Причины описал выше. Нужно стремиться, чтобы в одном конкретном месте у вас падал только 1 тест. Тогда любой человек через 20 лет придет, увидит 1 упавший тест и за 5 минут поймет, где и что он сломал. А если через 20 лет будет падать 100500 тестов, то этот человек просто удалит такие тесты :)

Я понял, спасибо большое за ответы!

первая часть была жухлая и бессмысленная, а эта - норм, хорошая, всё четко и по делу

Sign up to leave a comment.

Articles