Pull to refresh
27
0
ApeCoder @ApeCoder

Разработчик

Send message

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

Я думаю тех типов, которые в контексте вашего приложения отвечают за измерения

Кто-то все равно должен это сделать — разбить intent на конкретные операции и проверки.

Да. В моем случае тест должен содержать intent а детальные операции и проверки должны быть уровнем ниже. То есть, если абстрактная формулировка требует, чтобы при получении корректного способа апгрейда и достаточности средств он должен быть применен, то "корректный сопособ апгрейда" и "проверка, что способ применен" должно быть сформулировано отдельно.


А откуда вы взяли этот второй "контракт"?

Если объект x содержит метод m требующий реализации подмножества y интерфейса z то логично назвать это подмножество каким-то именем и написать именно его в требованиях m — нет?


В нашем примере можно не добавлять методы в существующий интерфейс, а сделать новый расширяющий его с добавленными методами.


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

В нашем случае системы типов достаточно. См. Выше, если мы этого не делаем, значит мы считаем что высока вероятность, что методы нужны всем.


Еще раз: то, что у вас — это и есть мок.

Мне надо перечитать, но, насколько я понял по Месзаросу мок должен содержать assertions. У меня он их не содержит — он просто готов сообщить внешнему миру о каких-то обобщенных свойствах. Я постараюсь перечитать его и то, что читал по #NoMocks в ближайшие дни, возможно я что-то не так понял или додумал, спасибо вам за внимательность.


Мой (как автора SUT) продакшн-код не изменился. Более того, весь мой продакшн-код корректно работает. Сломались только и исключительно тесты, дав мне false positive — что и является запахом ("When we don't think the change should have affected the tests that are failing"). Никакой пользы мой продакшн-код от этого поведения тестов не получил.

Мне кажется, я на это уже отвечал. Я понимаю вашу точку зрения.

Чаще всего это не позволяет писать тесты — в том смысле, что кто-то все равно декомпонует требования до уровня дизайна при написании assertions.

С моей точки зрения надо стремиться абстрагироваться от этого — детали реализации загонять на уровень ниже custom assertions и т.д. чтобы был виден intention.


В любом случае контракт метода не "я буду работать если мне передадут способ улучшения автомобиля" а не "я буду работать, если мне передадут способ улучшения автомобиля, но только если работают методы X и Y и я не обязуюсь дергать никакой метод кроме них" в последнем случае получается, что на самом деле он требует какой-то другой интерфейс являщийся подмножеством исходного.


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


When we don't think the change should have affected the tests that are failing or we haven't changed any production code or tests

Production code изменился, и добавление метода это ломающее изменение для любого кода который имплементирует интерфейс.

Это требования к фиче. Из них юнит-тесты писать не надо (обычно). Требования, из которых вырастают тесты, обычно возникают на уровне дизайна.

С моей точки зрения требования к этому уровню абстракции должны быть близки к требованию к фиче


Особенно это хорошо заметно, если интерфейс в одном компоненте, а тесты — в другом, и их авторы вообще никак не связаны.

Допустим я ввожу новй метод в интерфейс, компилирую и вижу, что в тестовом дубле он не реализован. У меня есть выбор:


  1. Передумать его вводить (например завести другой интерфейс и не заставлять людей его имплементировать)
  2. Разобраться в коде и реализовать
  3. Написать NotImplementedException
  4. Заглушки, возвращающие пустые значения
  5. Прочее

Если тестовый объект не мой я могу сделать что-то из этого, и послать хозяину письмо — он будет в курсе новых возможностей и посоветует как поступить лучше.


То есть, по сравнению с вариантами с моками больше выбора и решение более осознанное. Те же самые гарантии как и для production кода (кстати, интересно подумать на тему, почему в prod не используются умолчания типа "Если в объекте нету метода верни пустое значение" — даже в Смолтоке, насколько я помню).

Это зависит от того, что именно было в требованиях

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


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

Мне кажется это скорее строгость а не хрупкость — они не падют непонятно от чего, а просто напоминают мне, что я не подумал о чем-то.

С моей точки зрения тесты стали некорректны — они стали перестали описывать требования корректно.


С моей точки зрения хорошо, что компилятор нашел их некорректность.


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

Да, это мне нравится, но на мой вкус минусы перевешивают.
И, как уже писал, конкретно я, не вижу много тестов проверяющих вызов одного и того же метода (из разных тестов).

Они могут косвенно использовать метод но не проверять его. Например, если добавится запись названия улучшения в какой-то журнал И название будет обязательным, придется обходить все такие тесты и впиливать туда название. Или moq как-то считает что свойство ненулевое и его присвоит?


Лишние подробности убраны из теста в отдельный объект


Обычно нет необходимости дублировать много зависимостей (даже если дизайн не очень хорош), так что это скорее минус. Уходит наглядность, которая очень удобна.

Тут вопрос, что такое наглядность. Мне кажется, текст моего теста более похож на формулировку исходного требования.


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

В принципе, количество инфы в теле теста можно выносить в какие-то билдеры/фектори методы.


Мок вариант делает тоже самое, в конечном счете оба подхода прибиты к Enhancement.Upgrade(...), т.е. к конкретному механизму внутри метода.

В конечном счете все сводится к смене ориентации магнитных доменов на жестком диске :)


Спасибо за обсуждение. Мне надо еще подумать и почитать. Некоторые части концепции #NoMocks я додумал, возможно неправильно — надо это подробнее изучить.

Нет. "Применить улучшение" — это операция, описанная в контракте улучшения, и с точки зрения любого потребителя "применить улучшение" — это вызвать эту операцию

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


Я бы сказал, что мой подход это как использование Path.Combine вместо X + "\" + Y — и то и другое сводится к конкатенации но сама по себе формулировка требований более устойчива.

В вашем случае не описано, что произойдет, когда дернут метод Cost — что будет в тесте?


var enhancement = new RecordingEnhancement();
var subject  = new Person();
subject.UpgradeCar(ehnahncement);
AssertTrue(enhachement.WasAppliedTo(Person.Car));

class RecordingEnhancement : Enhancement {
     Car subject;
     Money cost;
     RecordingEnhancement(Money cost = 0)
     {
          this.cost = cost;
     }

     Maoney  cost{get{ return cost; }}

     void Upgrade(Car car)
     {
            subject = car;
     }

     Boolean WasAppliedTo(Car car)
     {
            return subject = car;
     }
}

Я вижу следующие достоинства:


  • У нас целостная абстракция — компилятор нас заставит определить Cost и этот тест не перестанет валиться потому, что вдруг кому-то понадобилось дернуть Cost
  • Есть отдельное описание того, что такое Ehancement и что такое факт его применения — то есть если протокол применения изменится, надо это будет учесть только в одном месте
  • Лишние подробности убраны из теста в отдельный объект

И это не проверка внутренней работы метода. Контракт метода, "если мне дали улучшение, я его применю" — мы ему даем улучшение и проверяем, что оно применено.


Что именно значит "применить улучшение" и "проверить что улучшение применено" является внутренним делом улучшения, ведь так?

Сделать recording enhancement который будет записывать факт применения enhancement внутри себя. Вне зависимости от того, сколькими и какими вызовами это было сделано.

"Только то, что нужно" должно иметь имя интерфейса

То есть если какое-то подмножество интерфейса используется чаще, то надо его выделять и использовать и фейкать.

Для оставшихся мы используем тот же симулятор, только создаем его уже в залогиненом виде
ИЛИ используем другой ограниченный интерфейс со своим симулятором

Затем, чтобы протестировать вот эти требования:
•SUT должен логиниться с credentials, полученными из конфигурации
•на каждый успешный логин должен быть логаут
•если логин неуспешен, отправка (и вообще никакие действия с нотификатором) не осуществляется

Напишите требования к SUT использующие данные методы, а я скажу, зачем

Для тех, кто зависит от логина реализация нужна, значит мы ее делаем.
Для тех кто не зависит от логина, предоставляем factory method InMemorySender.CreateLoggedInSender() и не тащим подробности логина и логаута в их тесты.


Тестировать надо его реализацию, очевидно.

Это да.

Если нам нужен очень редко Login и Logout надо просто сделать интерфейс, который не содержит их

Э нет. С точки зрения технологии они нужны. Просто для тестируемых бизнес-сценариев они неинтересны.

Я ж не говорю убрать существующий интерфейс, где они есть


Который тоже придется тестировать, ага.

Это интерфейс, что его тестировать?

Information

Rating
Does not participate
Location
Россия
Date of birth
Registered
Activity