Распространенные ошибки при написании юнит-тестов, Катерина Павленко

Поделиться
HTML-код
  • Опубликовано: 10 фев 2025
  • Все знают, что писать юнит-тесты нужно. Но иногда слишком сложно: код пишешь десять минут, а тесты все два часа. А когда дело доходит до поддержки и расширения старых тестов, иногда проще удалить их и написать всё заново. Рассмотрим частые ошибки, приводящие к этому, их причины и способы избежать.
    Катерина Павленко на конференции Web Standards Days 18 августа 2018 в Санкт-Петербурге - wsd.events/201...
    Слайды: wsd.events/201...

Комментарии • 4

  • @ДенисДемиденко-й7о
    @ДенисДемиденко-й7о 4 года назад +1

    Очень понятно, спасибо

  • @jgkdmdevienjjgg8866
    @jgkdmdevienjjgg8866 4 года назад +1

    1ый пример с кофе. допустим мы методы покупки кофе и его готовки вынесли в отдельные зависимости (сервисы). Вроде код от этого не поменялся, это просто рефакторинг. Однако этот класс уже нельзя протестировать вместе с этими зависимостями (или можно?), т.к. это уже другой unit. соответственно придется делать на них mock-и. Вопрос - а как теперь тестировать юнит тестом такой класс, нужно ли его тестировать вообще и не вылезет там всех тех же проблем которые были при тестировании приватных методов по отдельности .ведь наши тест по сути останется такими же как в ситуации с тестированием приватных методов = основной тест будет тестировать то, что вызвались эти самые mock-и, а отдельный тесты на созданные сервисы-зависимости будут тестировать их публичных методы на покупку кофе и на варку конфе. В итоге ситуация и эффект от нее ровно тот же что от тестирования приватных методов, хотя мы и сделали все по красоте, разве нет?
    или вот еще пример. Скажем у меня есть сервис и есть репозиторий. есть метод service.GetAccountById(id) и внутри в реализации вызывается 1 строчка с аналогичным вызовом в репозитории. Например я сделал на репозиторий(этот метод) какие-то тесты. если следовать правилу "тестируем контракты а не реализацию" я должен на этот сервисный метод с 1 строчкой кода сделать тест (какой??? и надо ли вообще делать тесты для такого рода простой реализации? если мы тестируем контракты а не реализацию то нам поидее надо на любой, даже такой контракт делать тест). Окей, допустим я что-то там протестировал. А теперь финт ушами и я решил бизнес логику которая была внутри репозитория вытащить на уровень сервисов, т.к. ей место там. Теперь мне надо менять 2 теста - тот что относится к репозиторию и тот что к сервисам (там ведь теперь куча бизнес логики вместо 1 вызова).
    Вот вопрос - если мы "тестируем контракты" - почему в этой ситуации столько гимора случилось, мы же контракт сервиса не меняли.
    Пока что этот что прошлый пример мне подсказывает что юниты которые мы тестируем это не отдельный класс нифига, а некая цепочка связанных классов. Но как определить границы этих юнитов в этом случае. И сюда же лезет то что несколько классов это типа уже интеграционные тесты, а не юнит.
    И получается по этим 2м примерам что рефакторинг ломает тесты, а такого же быть не должно, т.к. мы же не реализацию тестируем.
    Эх на бумажке все просто, а на деле, сами понимаете....
    Чем больше изучаю юнит тесты тем они мне меньше нравятся.
    Вот никогда не писал тесты, сейчас "тимлид заставляет".
    Как ни посмотришь у всех всегда (в ютубах и возможно во всяких книжках про
    тесты) все четко выходит и юнит тесты это прям одна польза, однако на
    практике вообще все не так получается и от них проблем больше чем
    пользы.
    Пока я вижу смысл только покрывать тестами какие-то сложные вычисления с
    минимумом (а лучше с 0лем) зависимостей, то есть только tricky места в
    которых легко допустить ошибку, но блин во всех местах делать такого
    рода юнит тесты, мне пока кажется это больше проблем чем толку
    (переубедите меня)
    Может мне какие-то книжки умные нужно почитать

    • @alexanderj8981
      @alexanderj8981 3 года назад

      За целый год такой длинный вопрос никто не удостоил даже парой строк ответа. Надо исправлять :)
      "Ведь наши тест по сути останется такими же как в ситуации с тестированием приватных методов = основной тест будет тестировать то, что вызвались эти самые mock-и, а отдельный тесты на созданные сервисы-зависимости будут тестировать их публичных методы на покупку кофе и на варку кофе. В итоге ситуация и эффект от нее ровно тот же что от тестирования приватных методов, хотя мы и сделали все по красоте, разве нет?"
      Это хорошо замечено. Да, если выносить приватные методы в отдельные классы, тестировать их и потом как моки проверять в изначальном классе, то это проблема. Тесты должны быть устойчивыми ко внутренней имплементации (рефакторингу), но быть чувствительными к изменению поведения. Когда мы выносим приватные методы, то это никак не меняет поведения в системе, требования к начальному классу по готовке кофе остаются точно такими же - мы имеем дело с рефакторингом, а значит тесты не должны меняться. Мы можем поменять приватные методы, разбить их еще на большее количество методов, можем вынести их в отдельный класс - как угодно - тесты для начального класса от этого не меняются, они остаются на месте, их не надо переносить в новые классы. При этом новые классы - это деталь имплементации, поэтому их лучше делать package private/private/internal и т.п, чтоб они не были доступны публично снаружи. Ведь мы и не собирались всем предоставлять раздельно доступ к покупке кофе и отдельно к варке. Только "все в одном" мы и хотели, а как оно будет устроено внутри уже не важно для тех, кто пользует такой модуль. И для тестов тоже, ведь тесты по-сути и есть пользователи модуля.
      На второй вопрос про репозиторий примерно тот же ответ. Если тестировать можно легко через более высокий уровень абстракции, который уже ближе к тому, что модуль и должен предоставлять наружу - так и надо делать. Ведь через вышестоящие методы все внутренние зависимости так или иначе тоже вызываются и тестируются.
      "Пока что этот что прошлый пример мне подсказывает что юниты которые мы тестируем это не отдельный класс нифига, а некая цепочка связанных классов. Но как определить границы этих юнитов в этом случае. И сюда же лезет то что несколько классов это типа уже интеграционные тесты, а не юнит."
      Модуль для тестирования - это не всегда класс или метод. Это какой-то набор требований к подсистеме. И сложность тестирования как раз и заключается в правильном определении насколько высокий этот уровень, какой же есть публичный интерфейс (api) этого модуля, что должно быть доступно из-вне, а что лишь приватная деталь. Но иногда приходится уровень тестируемой абстракции понижать если это упрощает тестирование. Юнит может означать много классов в цепочке, верно. Типичное разбиение на "юнит тесты" и "интеграционные" обычно только запутывает. Тесты должны быть всегда с интеграцией в некой степени. Возьмем, например, тривиальный пример "найти максимальное число из 3-ех". И если тестировать только эту функцию в отдельности, то все скажут, что это не интеграционный тест. Но внутри можно использовать не простой if-else, а вызов библиотечной функции max(a,b). Получается, что это уже интеграционный тест на библиотечную функцию. Мало смысла в этой классификации. Тут более важно насколько тест остается быстрым, надежным, стабильным к рефакторингу и чувствительным к изменению поведения (при изменении которого уже надо менять тест). И это всегда компромисс - большая надежность и приближенность к реальности будет часто медленней.
      "Может мне какие-то книжки умные нужно почитать".
      - Классика Kent Beck "Test Driven Development. By Example", которую мало кто читает, но уже тогда изначально были ответы на подобные вопросы.
      - Очень важные есть пару видео от Ian Cooper про тесты и TDD.
      - Ищи все по этим темам от Robert Martin (Uncle Bob) - есть видео лекции, есть прямо уроки про тесты/TDD на практике, есть у него еще блог clean coder, на котором можно найти десяток-два статей достаточно важных для понимания.
      - Кое-что можно найти от Martin Fowler - видео и блог.
      - Достойная книга, с которой по большей части можно соглашаться, это "Unit testing. Principles, Practices, and Patterns" от Vladimir Khorikov.

  • @olharamanouskaya2397
    @olharamanouskaya2397 4 года назад

    Супер 🔥