Здравствуйте, Pauel, Вы писали:
P>>>И если код меняется часто, в силу изменения требований, вам надо каждый раз строчить все тесты с нуля.
P>>>Вот пример — у нас есть требование вернуть hash токен что бы по контенту можно было находить метадату итд
P>>>кода получается вагон и маленькая тележка
P>·>Лучше код покажи. Так что-то не очень ясно.
P> expect(crypto.createHmac).toBeCalledWith('sha256', 'API_TOKEN');
Мде... Или это "особенности" js?.. Но допустим. Как ты это предлагаешь переделать с "value"?
P>>>А теперь фиаско — в либе есть уязвимость и проблема с перформансом, и её надо заменить, гы-гы все тесты на моках ушли на помойкку
P>·>Круто же — все тесты кода, который используют уязвимую тормозную либу — стали красными. По-моему, успех.
P>Ну да — проблемы нет а тесты стали красными. И так каждый раз.
Ничего не понял. Если использование уязвимой тормозной либы — не проблема, то зачем вообще код трогать?
P>·>Иными словами ты просто предлагаешь заворачивать стороннюю либу в свою обёртку. Это хорошо, иногда. Вот только моки-то тут причём?
P>Иными словами я предлагаю использовать value тест где только можно, т.к. таких можно настрочить сколько угодно.
Ок, покажи код как правильно должно быть по-твоему.
P>>>и кода у нас ровно один короткий тест, который вызывается с двумя-тремя десятками разных вариантов параметров, из которых большая часть это обработка ошибок, граничных случаев итд итд
P>·>Ага. Вот только эта твоя обёртка как будет попадать в использующий код? Через зависимости. И будешь мокать эту обёртку.
P>Вот я и говорю, что вы без моков жить не можете. Если мы проверили, что функция валидная, зачем нам её мокать? Проблема то какая?
В том, что в SUT нам не важно всё сложное и навороченное поведение функции, пусть даже идеально работающей. Мы хотим только проверить те случаи, которые важны для тестируемого кода. Условно говоря, у нас есть функция "validateThing", написанная и отлаженая/етс.
Теперь мы используем её в коде:
Result doSomething(Request r) {
if(validateThing(r.part1)) {
wroom(r.zcxv);
if(validateThing(r.part2) {
return NiceResult(r.a, r.b);
} else {
foBarBaz();
return SoSoResult(meh(r));
}
} else {
taramparam(r.aaa);
return NoNoNo(zzzz(r));
}
}
Вот тут нам совершенно неважно что и как делает validateThing — тут нам надо всего три сценария рассмотреть:
1. validateThing(part1) => returns false;
2. validateThing(part1) => returns true; validateThing(part2) => returns false;
3. validateThing(part1) => returns true; validateThing(part2) => returns true;
вот это и будет замокано. Состав и содержимое part1/part2 — в данном месте нам совершенно неважно, там могут быть затычки. Более того, когда мы будем дорабатывать поведение фукнции validateThing потом, эти три наших теста не пострадают, что они вдруг не очень правильно сформированные part1/part2 подают. Вдобавок, функция хоть пусть и валидная, но она может быть медленной — лазить в сеть или банально жечь cpu вычислениями.
P>>>>>То есть, это инструменты которые решают разные классы задач — вместо "assert x = y" тестируем "унутре вызвали вон тот метод с такими параметрами"
P>>>·>"verify(mock).x(y)"
P>>>Вместо просто assert x = y вам надо засетапить мок и надеяться, что новые требования не сломают дизайн вашего решения.
P>·>А чего там сетапить-то? var mock = Mockito.mock(Thing.class). А что там может ломаться и зачем?
P>Я не понимаю, что здесь происходит.
Создаётся мок для класса Thing.
Дальше мокаешь методы и инжектишь в SUT.
P>>>Вы чуть ниже сами об этом же пишете. Тесты на моках это те самые вайт бокс тесты, принципиально. И у них известный набор проблем, среди которых основной это хрупкость.
P>·>Моки и вайтбокс — это ортогональные понятия.
P>Вы там в своём уме? Чтобы замокать чего нибудь ради теста, вам нужно знание о реализации, следовательно, тесткейс автоматически становится вайтбоксом.
В общем случае: чтобы передать что-то методу для теста, тоже надо знание о реализации, иначе не узнаешь corner-case.
P>>>А вот если в проекте пошли по пути классического юнит-тестирования, то как правило тесты особого внимания не требуют — все работает само.
P>·>Можно ссылочки?
P>Зачем?
Я понимаю код лучше, чем твои объяснения.
P>·>Условно говоря... Моки используются чтобы чего-то передать или проверить чего нет в сигнатуре метода. Т.е. метод с сайд-эффектами — его поведение зависит не только от его параметров и|или его результат влияет не только на возвращаемое значение.
P>В том то и проблема, что вы строите такой дизайн, что бы его потом моками тестировать. Потому и топите везде за моки.
Хочу посмотреть на твой дизайн.
P>>>·>Проигнорировал вопрос трижды: Напиши однострочный интеграционный тест для tryAcceptComposition. Код в студию!
P>>>P>>>expect(new class().tryTryAcceptComposition(parameter)).to.eq(value);
P>>>
P>·>Заходим на четвёртый круг. Я не знаю что это такое и к чему относится. Напомню код:
P>Я не в курсе того фп языка, и ничего на нем не выдам. Для интеграционного тестам нам надо вызвать контроллер верхнего уровня, и убедиться, что результат тот самый.
Это хаскель вроде, впрочем это совершенно неважно.
P>В том и бенефит — раз вся логика покрыта юнит-тестами, то tryAcceptComposition нужно покрыть интеграционным тестом, ради чего никаких моков лепить не нужно
Важно то, что в этом tryAcceptComposition находится connectionString. Т.е. условно говоря твой "интеграционнный тест" полезет в реальную субд и будет безбожно тормозить. В коде с моками — был reservationsRepository и возможность отмежеваться от реальной субд чтобы тестировать что параметры поиска и создания резерваций верные — таких тестов можно выполнять пачками за миллисекунды. И отдельно иметь медленные тесты репозитория, который лазит в субд и проверяет корректность имплементации самого репозитория.
P>Вы идете наоброт — выпихиваете всё в контроллер, тогда дешевыми юнит-тестами покрывать нечего, зато теперь надо контроллер обкладывать моками, чтобы хоть как то протестировать.
В некоторых случаях интеграционным тестом будет "код скомпилировался".
P>·>Выделил твою цитату выше. И где ты у меня тут слово "мок" нашел?
P>Если это не про моки, то вопросов еще больше. Каким образом иерархия контроллер <- сервис-бл <- репозиторий <- клиент-бд стала у вас вдруг плоской?
DI же, не?
var db = new Db(connectionString);
var repo = new Repo(db);
var bl = new BlService(repo);
var ctl = new Controller(bl);
var server = new Server(port);
server.handle("/path", ctl);
server.start();
P>Я думал вы собираетесь замокать все зависимости контроллера, но вы похоже бьете все рекорды 
Для юнит-теста Controller надо мокать только BlService.
P>>>Нету такой задачи покрывать код. Тестируются ожидания пользователя, соответствие требованиями, выявление особенностей поведения и тд.
P>·>Покрытие позволяет найти непротестированный код. Если мы его нашли — мы видим пропущенные требования и начинаем выяснять ожидание пользователя в пропущенном сценарии. И вот после этого тестируем ожидание.
P>Совсем необязательно. Тесты это далеко не единственный инструмент в обеспечении качества. Потому покрывать код идея изначально утопичная. Покрывать нужно требования, ожидания вызывающей стороны, а не код.
Как? И как оценивать качество покрытия требований/ожиданий?
P>Для самого кода у нас много разных инструментов — от генерации по мат-модели, всяких инвариантов-пред-пост-условий и до код-ревью.
А метрики какие? Чтобы качество обеспечивать, его надо как-то измерять и контролировать.
P>>>Мне непонято что в моем объяснении вам не ясно
P>·>Как выглядит код, который тестирует tryAcceptComposition.
P>Да хоть вот так, утрирую конечно
P>P>curl -X POST -H "Content-Type: application/json" -d '{<резервирование>}' http://localhost:3000/api/v3/reservation
P>
А где ассерт?
P>>>Браво — вы вспомнили про вайтбокс. Моки это в чистом виде вайтбокс, я это вам в прошлый раз говорил раз десять
P>>>А вот если вы идете методом блакбокс тестирования, то никакие моки вам применить не удастся, и это дает более устойчивый код решения и более устойчивые тесты
P>·>Вайтбокс знает имплементацию. Каким образом ты будешь блакбоксить такое? int square(int x) {return x == 47919 ? 1254 : x * x;}. Заметь, никаки моков, тривиальная пюрешка.
P>Постусловия к вам еще не завезли? Это блакбокс, только это совсем не тест.
А куда их завезли?
P>>>Уже объяснил. Что именно вам непонятно?
P>·>А я просил что-то объяснять? Я просил показать код.
P>P>curl -X POST -H "Content-Type: application/json" -d '{<резервирование>}' http://localhost:3000/api/v3/reservation
P>
Это слишком тяжелый тест. Количество таких тестов должно быть минимально.
P>>>А вот простые тесты на вычисления резервирования остаются, потому как ни на какой MaitreD не завязаны. Что будет вместо MaitreD — вообще дело десятое.
P>·>А конкретно? Простые тесты чего именно в терминах ЯП? Какой языковой конструкции? И почему эта конкретная языковая конструкция отлита в граните и никак никогда поменяться не может?
P>Вы же моками обрезаете зависимости. Забыли? Каждая зависимость = причины для изменений.
Я не понял на какой вопрос этот ответ.
P>Простая функция типа "пюрешечки" — у неё зависимостей около нуля. Меньше зависимостей — меньше причин для изменения.
P>В идеале меняем только если поменяется бизнес-логика. Например, решим сделать все большие столы общими, что бы усадить больше гостей. Тогда нам нужно будет переделать функцию резервирования и много чего еще И это единственное основание для изменения, кроме найденных багов.
P>Вот с maitred все не просто — любое изменение дизайна затрагивает такой класс, тк у него куча зависимостей, каждая из которых может меняться по разными причнам.
Куча это сколько? Судя по коду в статье я там аж целых одну насчитал.
P>И вам каждый раз надо будет подфикшивать тесты.
По-моему ты очень странно пишешь тесты и дизайнишь, от того и проблемы.
P>>>Выбираю абстракцию наименьшего размера, которая покрывает кейс, и которую я могу протестировать
P>·>А если её потом надо будет переделать на другой размер?
P>Мне нужно, что бы изменения были из за изменения бизнес-логики, а не из за изменения дизайна смежных компонентов.
А конкретно? Пример кода в студию.
P>>>>>Ровно наоборот — аннотации в покрытии учавствуют, просто обязаны.
P>>>·>Покажи мне coverage report, где непокрытые аннотации красненьким подсвечены.
P>>>P>>>Expectation failed: ControllerX tryAccept to have metadata {...}
P>>>
P>·>Это не coverage report, это test report.
P>Тогда не совсем понятен ваш вопрос.
https://en.wikipedia.org/wiki/Code_coverage
P>>>P>>>const tryAccept = Metadata.from(ControllerX, 'tryAccept')
P>>>expext(tryAccept).metadata.to.match({...})
P>>>
P>·>Самое что ни на есть вайтбокс тестирование. Что конкретный метод содержит конкретную аннотацию.
P>Мы тестируем, что есть те или иные метаданные. А вот чем вы их установите — вообще дело десятое.
А как ещё на метод можно метаданные навесить? Или ты про js?
P>А вот метаданные это совсем не вайтбокс, это нефункциональные требования.
Зачем тогда их в эти тесты класть?..
P>·>P>·>controllerX.tryAccept(...)
P>·>verify(metadata).to.match(...);
P>·>
P>·>вот только у тебя не будет приколачивания к конкретному методу
P>Ну то есть tryAccept что в вашем тесте это никакой не конкретный метод? 
Это публичная точка входа. Там внутре может быть что-то, вызов приватных методов или ещё чего. Не важно же как, не важно из какого конкретного места кода, главное, чтобы аудит был записан — всё как ты требуешь от меня, а сам гвоздями к имплементации приколачиваешь.
>> и никакой рефлекии не надо.
P>В вашем случае придется пол-приложения перемокать, что бы сделать вызов tryAccept, и так повторить для каждого публичного метода.
Нет. См. код в статье.
P>·>Особенно приключения начнутся, если действия, совершаемые аннотацией потребуют какой-то логики. Например, "а вот в этом методе не делать аудит для внутреннего аккаунта, если цена меньше 100".
P>В вашем случае придется писать намного больше кода из за моков — перемокать всё подряд что бы выдать "вот этот метод не вызвался тут, там" и так по всем 100500 кейсам
Нет.
P>Мне нужно будет добавить функцию которая будет возвращать экземпляр аудитора по метаданным и покрыть её простыми дешовыми тестами. А раз весь аудит проходить будет через неё, то достаточно сверху накинуть интеграционный тест, 1шт
Тебе как-то рефлексивно придётся извлекать детали аудита из места вызова и параметров метода. На ЯП со статической типизацией будет полный швах.