Комментарии 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{})
А у корутин не самое понятное апи для тестирования, откровенно говоря.
Еще можно добавить, что нужно избегать сразу нескольких ассертов в одном тесте. Это имеет смысл только, если проверяемые значения однозначно зависят друг от друга. В других же случаях, лучше написать 2 однотипных тестах, в которых вы проверите только 1 значение.
Чем это лучше? Это реализация свойства "Изоляция" в Ваших тестах. Если Вы сломаете логику расчета одного из проверяемых параметров, то в первом случае у Вас упадет сразу несколько тестов, в каждом вы проверяете несколько результирующих значений – как итог, Вам придется долго сидеть и анализировать все, что происходит в проверяющем тесте, чтобы найти, а в чем же конкретно проблема. Во втором же случае, у вас упадет только 1 тест, в котором только 1 проверка, вы тут же поймете, куда конкретно вам надо смотреть.
Принцип "Изоляции" подразумевает, что в случае проблемы в коде, у вас должен упасть ровно 1 тест, четко указывающий на 1 единственное место, где искать проблему.
Подумайте, как поправить Ваш тест, в котором сразу несколько ассертов.
А как быть если я хочу порядок вызовов у моков проверить?
А зачем Вам проверять порядок вызова методов у моков?
Есть еще одно золотое правило тестирования. Вы не проверяете внутреннюю реализацию Вашего кода, Вы проверяете результат. Тестирование Вы должны проводить "черного ящика", а не "белого".
Это тоже реализация принципа "Изоляции". Ваш тест не должен зависеть от особенностей реализации Вашего кода. Если Вы захотите отрефакторить Ваш код и поменять реализацию, то Вам придется переписывать все Ваши тесты. А этого надо избегать. И - изоляция.
Подумайте тут, а что же конкретно Вы хотите проверить? Что является результатом работы Вашего метода, который вызывает методы моков? И вот уже этот результат и проверяйте.
Пример покажете? Можно будет подумать вместе.
Ну мы же об андроид разработке? Я хочу проверить что спустя 200 мс будет показан прогресс бар, если данные не пошли, если пошли, то не будет показан. Или что отмена progress bar происходит после отправки готовых данных - не мигает белый экран. Юнит тесты же это позволяют проверить. Возможно я не верно ставлю задачу?
Базовые принципы тем и хороши, что их можно к любой разработке применять. Даже к вебу или серверу ^.^
если данные не пошли, если пошли, то не будет показан
Вот это мне не очень понятно, но вообще вся идея про 200мс кажется неуместной. а что если запрос будет у вас отрабатывать 300мс, все равно будем мерцание? Но ок, давайте рассмотрим и ее.
Все, что вы тут описали можно разбить на кучу разных тестов с 1 лишь проверкой. Разве нет?
проверяем, что запустился таймер. ждать 200мс в тестах - моветон по двум причинам:
нарушаем буковку F - Fast, тест становится медленным
тестировать системное поведение (то, что таймер вызовется, если мы его запустили) - глупо. Это за пределами нашего влияния, поэтому и тестировать это бесполезно.
показ прогресс бара вы проверяете уже в другом тесте и в другой функции.
отмена прогресс бара - еще один тест.
то, что отмена вызовется по в колбеке получения данных - еще один и т.д.
Еще одно золотое правило в написании тестов: "Разделяй и властвуй". Не старайтесь одним тестом покрыть все Ваше приложение.
Подождите, корутины оперируют виртуальным временем, ждать там ничего не надо. delay(200) просто сдвинет виртуальное время.
Всю функцию можно свести к extension над coroutine - соответственно это цельная функция с изменением состояния стороннего объекта. Ее тесты все равно желательно дробить?
Ну, я считаю, что да. Причины описал выше. Нужно стремиться, чтобы в одном конкретном месте у вас падал только 1 тест. Тогда любой человек через 20 лет придет, увидит 1 упавший тест и за 5 минут поймет, где и что он сломал. А если через 20 лет будет падать 100500 тестов, то этот человек просто удалит такие тесты :)
первая часть была жухлая и бессмысленная, а эта - норм, хорошая, всё четко и по делу
Про тестирование мобильных приложений. Часть 2. Unit tests