Re[13]: Что такое Dependency Rejection
От: Pauel Беларусь http://blogs.rsdn.org/ikemefula
Дата: 04.12.23 20:05
Оценка: +1
Здравствуйте, ·, Вы писали:

P>>И если код меняется часто, в силу изменения требований, вам надо каждый раз строчить все тесты с нуля.

P>>Вот пример — у нас есть требование вернуть hash токен что бы по контенту можно было находить метадату итд
P>>кода получается вагон и маленькая тележка
·>Лучше код покажи. Так что-то не очень ясно.

const main = require('./app');
const crypto = require('crypto');

jest.mock('crypto', () => {
  return {
    createHmac: jest.fn().mockReturnThis(),
    update: jest.fn().mockReturnThis(),
    digest: jest.fn(() => '123'),
  };
});

describe('64386858', () => {
  it('should pass', () => {
    const logSpy = jest.spyOn(console, 'log');
    main();
    expect(crypto.createHmac).toBeCalledWith('sha256', 'API_TOKEN');
    expect(crypto.update).toBeCalledWith(JSON.stringify({}));
    expect(crypto.digest).toBeCalledWith('hex');
    expect(logSpy).toBeCalledWith('verified successfully. Implement next logic');
    logSpy.mockRestore();
  });
});



P>>А теперь фиаско — в либе есть уязвимость и проблема с перформансом, и её надо заменить, гы-гы все тесты на моках ушли на помойкку

·>Круто же — все тесты кода, который используют уязвимую тормозную либу — стали красными. По-моему, успех.

Ну да — проблемы нет а тесты стали красными. И так каждый раз.

P>>А если делать иначе, то юнит тестом покрываем вычисления токена — передаём контент и для него получаем тот самый токен.

P>>Теперь можно либу менять хоть по сто раз на дню, тесты это никак не меняет
·>Иными словами ты просто предлагаешь заворачивать стороннюю либу в свою обёртку. Это хорошо, иногда. Вот только моки-то тут причём?

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

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

·>Ага. Вот только эта твоя обёртка как будет попадать в использующий код? Через зависимости. И будешь мокать эту обёртку.

Вот я и говорю, что вы без моков жить не можете. Если мы проверили, что функция валидная, зачем нам её мокать? Проблема то какая?

P>>>>То есть, это инструменты которые решают разные классы задач — вместо "assert x = y" тестируем "унутре вызвали вон тот метод с такими параметрами"

P>>·>"verify(mock).x(y)"
P>>Вместо просто assert x = y вам надо засетапить мок и надеяться, что новые требования не сломают дизайн вашего решения.
·>А чего там сетапить-то? var mock = Mockito.mock(Thing.class). А что там может ломаться и зачем?

Я не понимаю, что здесь происходит.

P>>Вы чуть ниже сами об этом же пишете. Тесты на моках это те самые вайт бокс тесты, принципиально. И у них известный набор проблем, среди которых основной это хрупкость.

·>Моки и вайтбокс — это ортогональные понятия.

Вы там в своём уме? Чтобы замокать чего нибудь ради теста, вам нужно знание о реализации, следовательно, тесткейс автоматически становится вайтбоксом.

P>>А вот если в проекте пошли по пути классического юнит-тестирования, то как правило тесты особого внимания не требуют — все работает само.

·>Можно ссылочки?

Зачем?

·>Условно говоря... Моки используются чтобы чего-то передать или проверить чего нет в сигнатуре метода. Т.е. метод с сайд-эффектами — его поведение зависит не только от его параметров и|или его результат влияет не только на возвращаемое значение.


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

P>>·>Проигнорировал вопрос трижды: Напиши однострочный интеграционный тест для tryAcceptComposition. Код в студию!

P>>
P>>expect(new class().tryTryAcceptComposition(parameter)).to.eq(value);
P>>

·>Заходим на четвёртый круг. Я не знаю что это такое и к чему относится. Напомню код:

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

Вы идете наоброт — выпихиваете всё в контроллер, тогда дешевыми юнит-тестами покрывать нечего, зато теперь надо контроллер обкладывать моками, чтобы хоть как то протестировать.

P>>Вот некто с вашего акканута пишет

P>>

P>>Ну так внедрение зависимостей DI+CI это и есть плоская иерархия.

·>Выделил твою цитату выше. И где ты у меня тут слово "мок" нашел?

Если это не про моки, то вопросов еще больше. Каким образом иерархия контроллер <- сервис-бл <- репозиторий <- клиент-бд стала у вас вдруг плоской?

Я думал вы собираетесь замокать все зависимости контроллера, но вы похоже бьете все рекорды

P>>Нету такой задачи покрывать код. Тестируются ожидания пользователя, соответствие требованиями, выявление особенностей поведения и тд.

·>Покрытие позволяет найти непротестированный код. Если мы его нашли — мы видим пропущенные требования и начинаем выяснять ожидание пользователя в пропущенном сценарии. И вот после этого тестируем ожидание.

Совсем необязательно. Тесты это далеко не единственный инструмент в обеспечении качества. Потому покрывать код идея изначально утопичная. Покрывать нужно требования, ожидания вызывающей стороны, а не код.
Для самого кода у нас много разных инструментов — от генерации по мат-модели, всяких инвариантов-пред-пост-условий и до код-ревью.


P>>Мне непонято что в моем объяснении вам не ясно

·>Как выглядит код, который тестирует tryAcceptComposition.

Да хоть вот так, утрирую конечно
curl -X POST -H "Content-Type: application/json" -d '{<резервирование>}' http://localhost:3000/api/v3/reservation


P>>Браво — вы вспомнили про вайтбокс. Моки это в чистом виде вайтбокс, я это вам в прошлый раз говорил раз десять

P>>А вот если вы идете методом блакбокс тестирования, то никакие моки вам применить не удастся, и это дает более устойчивый код решения и более устойчивые тесты
·>Вайтбокс знает имплементацию. Каким образом ты будешь блакбоксить такое? int square(int x) {return x == 47919 ? 1254 : x * x;}. Заметь, никаки моков, тривиальная пюрешка.

Постусловия к вам еще не завезли? Это блакбокс, только это совсем не тест.

P>>Уже объяснил. Что именно вам непонятно?

·>А я просил что-то объяснять? Я просил показать код.

curl -X POST -H "Content-Type: application/json" -d '{<резервирование>}' http://localhost:3000/api/v3/reservation


P>>А вот простые тесты на вычисления резервирования остаются, потому как ни на какой MaitreD не завязаны. Что будет вместо MaitreD — вообще дело десятое.

·>А конкретно? Простые тесты чего именно в терминах ЯП? Какой языковой конструкции? И почему эта конкретная языковая конструкция отлита в граните и никак никогда поменяться не может?

Вы же моками обрезаете зависимости. Забыли? Каждая зависимость = причины для изменений.

Простая функция типа "пюрешечки" — у неё зависимостей около нуля. Меньше зависимостей — меньше причин для изменения.
В идеале меняем только если поменяется бизнес-логика. Например, решим сделать все большие столы общими, что бы усадить больше гостей. Тогда нам нужно будет переделать функцию резервирования и много чего еще И это единственное основание для изменения, кроме найденных багов.
Вот с maitred все не просто — любое изменение дизайна затрагивает такой класс, тк у него куча зависимостей, каждая из которых может меняться по разными причнам.
И вам каждый раз надо будет подфикшивать тесты.

P>>Выбираю абстракцию наименьшего размера, которая покрывает кейс, и которую я могу протестировать

·>А если её потом надо будет переделать на другой размер?

Мне нужно, что бы изменения были из за изменения бизнес-логики, а не из за изменения дизайна смежных компонентов.

P>>>>Ровно наоборот — аннотации в покрытии учавствуют, просто обязаны.

P>>·>Покажи мне coverage report, где непокрытые аннотации красненьким подсвечены.
P>>
P>>Expectation failed: ControllerX tryAccept to have metadata {...}
P>>

·>Это не coverage report, это test report.

Тогда не совсем понятен ваш вопрос.


P>>>>Прикрутили метаданные — есть тест на это. Используются ли эти метаданные — тоже есть тест.

P>>·>Покажи как выглядит тест на то, что аннотация повешена корректно.
P>>
P>>const tryAccept = Metadata.from(ControllerX, 'tryAccept')
P>>expext(tryAccept).metadata.to.match({...})
P>>

·>Самое что ни на есть вайтбокс тестирование. Что конкретный метод содержит конкретную аннотацию.

Мы тестируем, что есть те или иные метаданные. А вот чем вы их установите — вообще дело десятое.
А вот метаданные это совсем не вайтбокс, это нефункциональные требования.

·>
·>controllerX.tryAccept(...)
·>verify(metadata).to.match(...);
·>

·>вот только у тебя не будет приколачивания к конкретному методу

Ну то есть tryAccept что в вашем тесте это никакой не конкретный метод?

> и никакой рефлекии не надо.


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

·>Особенно приключения начнутся, если действия, совершаемые аннотацией потребуют какой-то логики. Например, "а вот в этом методе не делать аудит для внутреннего аккаунта, если цена меньше 100".


В вашем случае придется писать намного больше кода из за моков — перемокать всё подряд что бы выдать "вот этот метод не вызвался тут, там" и так по всем 100500 кейсам

Мне нужно будет добавить функцию которая будет возвращать экземпляр аудитора по метаданным и покрыть её простыми дешовыми тестами. А раз весь аудит проходить будет через неё, то достаточно сверху накинуть интеграционный тест, 1шт
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.