Unit тестирование в Android c Clean architecture

Sdílet
Vložit
  • čas přidán 5. 07. 2024
  • Рассказываю про Unit тестирование в Android, очень подробно, как для "чайников" ;). На реальном примере показываю JUnit и Mockito, зачем это и как эффективно использовать тесты в разработке. Показываю пример с Clean architecture Android.
    Курсы по разработке Android приложений: ✅ KIPARO.COM.
    СОДЕРЖАНИЕ:
    -------------------------------------------------------------------
    00:00:00 - введение
    00:00:39 - Clean architecture Android
    00:01:40 - кратко, что такое Unit тестирование Android
    00:02:25 - подключаем библиотеки JUnit и Mockito
    00:03:26 - пишем тест на JUnit 5
    00:14:46 - используем библиотеку Mockito
    -------------------------------------------------------------------
    На канале также есть уроки по Android Studio, уроки по Java и уроки по Kotlin.
    Так же, найти меня можно вот тут:
    ✅ Linkedin: / timofeykovalenko
    ✅ Instagram: / ttimofey
    ✅ На моем сайте: kiparo.com/teacher/timofey-ko...
    ✅ FB с анонсами видео: / kiparocom
    #android #junit #kiparo

Komentáře • 45

  • @TimofeyKovalenko
    @TimofeyKovalenko  Před 2 lety +9

    СОДЕРЖАНИЕ:
    00:00:00 - введение
    00:00:39 - Clean architecture Android
    00:01:40 - кратко, что такое Unit тестирование Android
    00:02:25 - подключаем библиотеки JUnit и Mockito
    00:03:26 - пишем тест на JUnit 5
    00:14:46 - используем библиотеку Mockito

  • @MarselNz
    @MarselNz Před 2 lety +9

    Единственный человек во всем ютубе, кто дает такую актуальную информацию. Спасибо вам

  • @dmitriygushel5587
    @dmitriygushel5587 Před 2 lety +8

    Спасибо, очень подробно! Кстати, что бы не создавать самому пакеты, можно нажать ctrl+shift+T и студия сама создаст класс с названием исходного класса+test с тем же пакетом в папке test

  • @readmeandanswer8142
    @readmeandanswer8142 Před 2 lety +5

    Спасибо, понятно, после ваших уроков статьи по этим темам читаются легче.

  • @pauilegorov
    @pauilegorov Před 2 lety +1

    Спасибо, очень полезная тема!!

  • @spyro2008
    @spyro2008 Před 3 měsíci

    Как всегда все на высшем уровне, спасибо вам!

  • @user-we6si4mi3x
    @user-we6si4mi3x Před 2 měsíci

    Спасибо, все очень понятно объясняете!

  • @chriswaytt6009
    @chriswaytt6009 Před rokem +1

    спасибо, сделайте еще пожалуйста на эту тему видео. Объясняете доступно но хочется больше примеров и по сложнее задачи

  • @TheVincet1998
    @TheVincet1998 Před rokem

    Спасибо за видео!

  • @user-bu7zr1kd1j
    @user-bu7zr1kd1j Před 2 lety +1

    Спасибо за ваш труд!

  • @namefamily957
    @namefamily957 Před 2 lety

    Спасибо! :)

  • @Arman_127
    @Arman_127 Před 2 lety +1

    Выглядит и вправду смешно но в реальных проектах я думаю пойму всю прелесть использование тестов спасибо

  • @alexanderj8981
    @alexanderj8981 Před 2 lety +7

    Спасибо за видео. Понравился стиль объяснения, спокойный рассказ с примерами без лишней воды - приятно слушать такое.
    Я конечно понимаю, что все упрощено для новичков, но некоторые моменты хотелось бы уточнить, т.к. где-то можно чуть улучшить, а где-то из-за упрощения даются немного вредные советы, которые сказываются потом.
    1. czcams.com/video/iGqVm0atAMQ/video.html : вообще вначале, когда проект почти пустой и все элементарно, то классы с тестами могут лежать в точно таких же пакетах и называться по имени класса. Но чем дальше, тем все больше тесты должны быть структурированы иначе, чем основной код. У них будет единый фасад как точка входа, но вовсе не обязательно, что на каждый класс вообще будет ровно симметричный класс с тестами. Тесты должны быть не чувствительными к изменению структуры основного кода, а это означает, что если вдруг из того же UseCase мы захотим вынести что-то в отдельный класс или сделать изменение, которое не меняет поведение программы, то тесты при этом должны практически не меняться и на новый выделенный класс не создаются новые тесты, ведь поведение уже будет протестировано через внешний.
    Если соблюдать это правило про класс и симметричный тест класс к нему под тем же именем и расположением, то мы создаем лишнюю связанность, которая будет мешать при дальнейших изменениях кода.
    2. czcams.com/video/iGqVm0atAMQ/video.html : еще хорошо бы показать, раз код в IDE, что тесты создавать проще не вручную, а через хоткеи: Alt+Enter (если windows) и там через диалог дальше. А создавать тесты в том же пакете и переключаться между основным кодом и тестами можно по Ctrl+Shift+T. Вот тут можно посмотреть справку: www.jetbrains.com/help/idea/create-tests.html.
    3. czcams.com/video/iGqVm0atAMQ/video.html : стандартным Assertions лучше предпочесть AssertJ и ему подобные с более выразительным синтаксисом, где вместо assertEquals(a, b), в котором новички долго путаются где “ожидаемое”, а где “реальное” на каком месте, - там будет assertThat(2+2).isEqual(4).
    4. czcams.com/video/iGqVm0atAMQ/video.html : вообще-то, названия тестов как предложения с пробелами в котлине работают и для junit4, тут никакой разницы. “shouldReturnCorrectData” - совсем слабое название и второй вариант заметно лучше. И хотя “should” достаточно распространенная практика именования, но следовать ей не стоит: это лишнее слово в каждом тесте, притом еще и лексически означающее, что “тут следовало бы вернуть (но не обязательно)”, а мы хотим, чтоб поведение было строгим точным требованием без “желательно”.
    5. czcams.com/video/iGqVm0atAMQ/video.html : а вот это неправильный и вредный совет. Как раз любым mock библиотекам лучше предпочитать свои тестовые утилиты с настоящими и полноценными методами, а где можно, то и реальные классы. Для этого нужно аккуратно продумать куда положить такой TestRepository, чтоб можно было переиспользовать в других тестах (т.е. не тут в этом же файле). Для этого кстати нельзя просто так забивать имена внутри (first и last name), а придется передавать данные параметрами. Это делается очень просто и потом легко менять в одном месте раз и для всех тестов. А вот с моками если что меняется, то нужно обновить каждый тест в отдельности. Настройка каждого мока - кроме того, что это лишний код - так это и каждый раз излишняя привязка к тому, как тест ожидает какой метод вызовется в основном коде и как он себя ведет. А это означает, что тест знает детали имплементации, чего он знать не должен. И именно это потом не позволяет менять основной код без тонны изменений в тестах.
    6. czcams.com/video/iGqVm0atAMQ/video.html : mockito был стандартом в java. С котлином он изначально плохо работал. Зато упомянутый mockk и советую использовать вместо mockito - он создавался сразу для котлина с его синтаксическими возможностями, не требует второй вспомогательной библиотеки, более мощный (может при необходимости и object, и enum, и конструкторы чужих классов мокать).
    7. czcams.com/video/iGqVm0atAMQ/video.html : вообще да, про это и вправду надо задумываться будет ли сравнение по ссылке или по equals тоже сработает. Но для теста самого это не должно быть важным, ведь поведение будет работать так или иначе. Даже технически именно сам testUserName, а не копию, метод и вернет. А если очень важно убедиться, что возвращенное значение equals подобному, то это отдельно можно протестировать. Есть в AssertJ и методы такие как assertThat(actual).isSameAs(expected) (или isNotSameAs).

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před 2 lety +3

      Уххххх) Спасибо за такой развернутый фидбек 👍.
      1) Не могу полностью согласиться. Да, когда проект большой, то и тестов бывает много на один класс, а где-то и структура другая. Но для большинства классов классическая структура более чем подходит, и позволяет проще ориентироваться в коде, особенно, когда заводишь в проект новых разработчиков. К тому же это видео для студентов, если начать показывать на таких примерах, то студентов быстро поубавится))))).
      2) По опыту, не стоит такое показывать со старта. Лучше пусть студент чуть больше потратит времени, но набьет руку. А потом уже и IDE под себя настроят и хот кеи. Вообще, когда преподаешь, приходится отучиваться от хот кеев))) и писать код намного более развернуто.
      3) Нуу не знаю, мне больше нравится использовать стандартный стек, так проще программистов заводить на проект. Да и для обучения, я бы никогда не брал что-то не стандартное. Все такие либы уже учатся на работе в реальном проекте если необходимо.
      4) Хм, спасибо не знал. “should” - да, это лишнее.
      5) Не спорю, вариант со своими реализациями стоящий, но нужно понимать, что в этом случае вы теряете весь функционал mock, который не ограничивается просто созданием фейковых объектов. Плюс, подход с mock все же популярнее.
      6) mockito это стандарт от гугла, mockk все же сторонняя либа, поэтому для базового обучения не годится.
      7) "для теста самого это не должно быть важным," - еще как важно, так можно написать тест, который только с виду будет работать).

  • @mikhaillazarev5378
    @mikhaillazarev5378 Před rokem

    Спасибо большое.

  • @MxMayers
    @MxMayers Před 2 lety

    Спасибо за видео!) 14:27 я представил как в офис разработки на 3м этаже, в помещение с джунами заходит сеньер и говорит что тесты сломались, вся команда подрывается и выпрыгивает в окно 😂

  • @pavelpotapov9866
    @pavelpotapov9866 Před 2 lety +1

    🔥🔥🔥

  • @max__am
    @max__am Před 2 lety +1

    лучший!

  • @DarkWeret
    @DarkWeret Před 2 lety +1

    Спасибо, все очень понятно объясняете) Вопрос: будут ли видео с тестированием viewModel? В котором возвращается какой-нибудь sealed класс state: с loading, success или error? Очень сложная для моего понимания тема)

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před 2 lety +1

      Будет но чуть позже, сначала закончим с этим приложением, а вот затем сделаем что-то посложнее.

    • @fournonblondes4089
      @fournonblondes4089 Před 2 lety

      viewModel в котором возвращается какой-нибудь sealed класс state: с loading, success или error скажите где об этом можно почитать, лучше на русском. смотрел видео у Филиппа но не всё понял, делать по аналогии сложновато даже.

    • @luckydevil1601
      @luckydevil1601 Před rokem

      @@fournonblondes4089 это который Philipp Lackner?)

  • @__Minecraft_Poseidon__

    Сотый лайк 👍

  • @user-vl2uw6rb1v
    @user-vl2uw6rb1v Před rokem

    лучший

    • @user-vl2uw6rb1v
      @user-vl2uw6rb1v Před rokem

      хочу подсказать, у вас в шапке канала тот основной баннер, там ничего не написано о котлине)) не порядоооок

  • @PandaTop.
    @PandaTop. Před 2 lety +1

    Дякую

  • @karamba6936
    @karamba6936 Před rokem

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

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před rokem

      Я придерживаюсь подхода, который строится на конкретной необходимости. Если интерфейсы решают какую-то проблему и улучшают работу с приложением, то почему нет. Если же это делается потому, что в умной книжке так написали, нууу такое себе. А не использовать DI библиотеки - это называется сделать свой велосипед, который никто кроме тебя не поймет))).

  • @oleg12395
    @oleg12395 Před 2 lety +1

    Как вы поставили кавычки в методе: should return the same data ...

  • @nikitatkachenko1694
    @nikitatkachenko1694 Před 2 lety

    Привет, я начинающий андроид разработчик и для меня этот канал как алмаз среди груды мусора)
    Посмотрев твои уроки начал переделывать свой пет-проект на клин, но столкнулся с проблемой(
    У меня в проекте есть работа с API(retrofit+moshi+okhttp), если я правильно понял то это относиться к "data" слою ?
    Короче говоря, мои модельки для работы с API лежат в слое "data", а также эти модели нужны для слоя "domain", так как там описан интерфейс репозитория, как мне достучаться до этих моделек если по правильному я не могу юзать модели "data" в "domain" ?
    Заранее спасибо за ответ !

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před 2 lety +2

      Да, API(retrofit+moshi+okhttp) это часть слоя "data" и у них есть свои модельки, тут все правильно. Но, модельки, которые лежат в слое "data", никакому больше кроме как слою "data" не должны быть видны. Интерфейс репозитория должен возвращать модельки "domain". То есть в реализации репозитория вы должны конвертнуть "data" модельки в "domain" и вернуть их из методов репозитория. Domain главный слой и задает всем правила работы, тоесть репозиторий должен отдать то, что хочет домен. Поэтому "data" модельки используются чисто для внутренней работы "data" слоя.

    • @nikitatkachenko1694
      @nikitatkachenko1694 Před 2 lety +1

      @@TimofeyKovalenko спасибо за ответ, а сможешь как-то показать на практике как правильно внедрить работу з api, room, fireBase ?! Думаю будет полезно и информативно)

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před 2 lety +2

      Да, есть планы на такие темы, но чуть позже.

  • @alishanvaliani9952
    @alishanvaliani9952 Před 2 lety +1

    let's make course about Jetpack Compose!!

  • @vladlen_ak
    @vladlen_ak Před rokem

    Как ставить эти одинарные кавычки под скосом на маке?

  • @mirzorasulpulotov129
    @mirzorasulpulotov129 Před rokem

    Что делать? Выдает ошибку Mockito cannot mock/spy because :
    - final class. Хотя класс не финал. Это в котлин.

  • @devkot7040
    @devkot7040 Před 2 lety

    Есть вопросы. Вот мы тестируем реализацию UseCase'а(интерактор) .
    1. Почему этот интерактор, не наследуется от какого-то интерфейса интерактора? То есть каким образом мы будем тестировать viewmodel, подставляю туда фейковый интерактор?
    2. Так же хотел спросить, нет ли какого-то либо механизма, который позволяет тестировать интерфейс этого самого интерактора, но, чтобы посредством di каким-то образом в тест inject-ить реализацию этого интерактора. То есть, мы один раз провайдим реализацию интерфейса, а он уже сам тестируется, т.к. в тесте тестировался интерфейс, а не его реализация.

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před 2 lety

      1) Просто делаете фейковый интерактор на основе реального и все. Что-бы делать моки не обязательно использовать именно интерфейсы.
      2) "тесте тестировался интерфейс, а не его реализация" - не понял, если честно ваш вопрос, что значит тестировался только интерфейс, интерфейс никогда не тестируется, в этом нет никакого смысла.

    • @devkot7040
      @devkot7040 Před 2 lety

      @@TimofeyKovalenko 1) да, можно сделать фейковый интерактор на основе реального, но получается, что если мы заменим реализацию интерактора, то нам придётся во всех тестах его менять вручную, да и в коде собственно.
      2) про тестировался интерфейс. Я имел ввиду, что прописывается работа с интерфейсом в классе теста. Во время компиляции, с помощью DI в класс теста inject- ится реализация этого интерфейса, и уже тестируется реальный класс. Чтобы в следующий раз, когда меняется реализация этого интерфейса, которую мы везде inject- им, нам не пришлось вручную менять тесты.

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před 2 lety

      1) Понял вас. Ну тут с одной стороны да, это может быть удобно, но с другой - это очень редкий случай. Дело в том, что для каждой реализации интерактора нужно писать новый тест. А если логика возвращаемых данных остается также, то и врят ли придется писать новую реализацию ;). Да и вообще, лучше все же менять вручную, дело в том, что я часто вижу тесты, которые написаны так, что при изменение тестируемой логики, они тоже "самоизменяются" или подстраиваются под новые требования))), в итоге, от таких тестов нет никакого толка.
      2) Да, в тесте можно использовать зависимости из DI, но опять же вопрос зачем(пункт 1).

  • @garifzyanovrr
    @garifzyanovrr Před rokem

    Тимофей привет!
    Выдает ошибку,
    Expected :...cleanarchitecturelearnapp.domain.models.UserName@632ceb35
    Actual :...cleanarchitecturelearnapp.domain.models.UserName@1c93f6e1
    Все как у тебя в коде.

    • @TimofeyKovalenko
      @TimofeyKovalenko  Před rokem +1

      Вы сравниваете объекты, а нужно сравнивать содержимое этих объектов.

    • @decemd1946
      @decemd1946 Před rokem +1

      в дата классе метод equals(==) содержимое сравнивает