Информация об изменениях

Сообщение Re[90]: Что такое Dependency Rejection от 07.03.2024 12:00

Изменено 07.03.2024 13:40 ·

Re[90]: Что такое Dependency Rejection
Здравствуйте, Sinclair, Вы писали:

P>>>·>Только если у тебя какой-то очень частый случай и ты завязываешься на какую-то конкретную реализацию — а это значит, что у тебя тесты не будут тестировать даже регрессию.

P>>>Покажите пример регрессии, которая не будет обнаружена этим тестом.
S>·>Ну загрузится пол таблицы. Или банально твой .top(10) не появится для какой-то конкретной комбинации фильтров, т.к. для этой комбинации решили немного переписать кусочек кода и забыли засунуть .top(10).
S>Нет. Эта проблема возникает только в том случае, если тестируется весь кусок про генерацию запроса целиком.
Мой поинт, что такой тест запроса целиком должен быть в любом случае. Что делает ценность этих ваших тестов <=0.

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

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

S>Альтернатива — не в том, чтобы сгенерировать 2N комбинаций условий фильтрации, а в том, чтобы распилить функцию построения запроса на две части:

S>1. Сгенерировать предикатную часть запроса
Это всё скучные тривиальные детали реализации.

S>2. Добавить к любому запросу .top(10).

Вот только невозможно проверить в _тесте_, что .top(10) действительно добавляется к _любому_ запросу.

S>Вот этот addLimit мы тестируем отдельным набором тестов, чтобы посмотреть, что он будет делать для отрицательных, нулевых, и положительных значений аргумента.

Это уже лажа. addLimit это метод некоего QueryBuilder, некий библиотечный метод, который уже имеет свои тесты, включая эти твои отрицательные аргументы. Мы полагаемся, что он работает в соотвествии с контрактом когда им пользуемся. А здесь мы говорим тут о тестировании некего условного UserRepository, которому какие-либо QueryBuilders совершенно побоку. В контексте разговора UserRepositoryTest предлагать тесты работоспособности addLimit — это как минимум паранойя.

S>Шанс "забыть засунуть .top(10)" у нас ровно в одном месте — конкретно вот в этой glue-функции, которая клеит два куска запроса.

S>Для того, чтобы этот шанс окончательно устранить, нам достаточно 1 (одного) теста для функции buildQuery — благодаря линейности кода, этот тест покроет нужный нам путь.
А зачем этот тест нужен-то? Убедиться, что в двух строчках кода одна из них действительно addLimit? Это и так напрямую явно видно в коде и каждый заметит наличие-отсутствие этого вызова и в PR диффе.
Ты по сути предлагаешь написать строчку кода, а потом продублировать в тесте, что эта строчка действительно написана. Ценность околонулевая. Твои тесты тестируют "works as coded".
Завтра придёт "специалист" и немного допишет:
IQueryable<User> buildQuery(UserFilter criteria, int pageSize)
    => someMagicalCondition(criteria) ? buildOptimizedQuery(criteria) : buildWhere(criteria).addLimit(pageSize);

И твои супер-тесты это не обнаружат. Т.е. твой тест даст false negative. Или тебе известна методология тестирования кода на простоту и линейность?

Мой поинт в том, что ценным тестом будет проверка что addLimit строчка действительно работает в соответствии с бизнес-требованиями — запустить на 11 записях и заассертить, что вернулось только 10 как прописано в FRD — это цель этой строчки. И заметь, такой 11/10 тест достаточно сделать для ровно той же комбинации фильтров что и в твоём тесте на наличие addLimit в коде, никакой экспоненциальности которой ты грозишься. Твой тест теоретически не может гарантировать, что некая функция buildQuery возвращает IQueryable с addLimit всегда, для любых входных параметров, такое можно лишь гарантировать через ревью текста самого кода.
Цель моего теста: если мы завтра перепишем addLimit на голый sql c "where rownum <= 10" или наоборот — то у нас будет регрессия, т.е. тесты которые так же продолжают работать и ожидаются, что должны быть зелёными без каких-либо изменений после рефакторинга. Такие тесты нужны в любом случае. А твой тест даст false positive и будет просто выкинут. Напишут взамен другой тест, в который закопипастят .contains("rownum < 10") перепутав что rownum zero- или one- based. Отличить на ревью корректность <= от < гораздо сложнее, чем условно save(...11 records...); assert find(10).size() == 10; assert find(5).size() == 5;.

S>Эта функция — строго линейна, она проверяется ровно одним тестом, который убеждается, что мы не забыли собрать все компоненты предиката.

Это какой-то тривиальный случай. Это значит не то, что мы магически сделали функцию линейной, а что никаких зависимостей между фильтрами нет в требованиях и никаких 2^N комбинаций просто не нужно, где тут эта ваша грозная data complexity — совершенно неясно.
В реальности у тебя могут быть хитрые зависимости между параметрами фильтра и вот тут и полезут неявные комбинации экспоненциально всё взрывающие и код несводимый к линейной функции, а будет хитрая if-else-switch лесенка.

S>А каждая из запчастей проверяется двумя или четырьмя тестами для всех нужных нам комбинаций её значений.

S>Всё, мы вместо 144 тестов (3*24*3) получили 3+2+4+2+2+3+1 = 17 тестов. При этом нам не нужны ни моки БД, ни тестовая БД в памяти, в которую запихано большое количество записей с разными комбинациями параметров.
S>То есть мы имеем 17 мгновенных тестов вместо 144 медленных, и гарантию полного покрытия.
Гарантию того, что у тебя works as coded. Зато отличить u => u.LastLoginTimeStamp >= criteria.MinLastLoginTimestamp.Value) от u => u.LastLoginTimeStamp <= criteria.MinLastLoginTimestamp.Value) такие тесты не смогут, т.к. пишутся как правило копипастом. На ревью очепятку очень вряд ли кто-то заметит.
Re[90]: Что такое Dependency Rejection
Здравствуйте, Sinclair, Вы писали:

P>>>·>Только если у тебя какой-то очень частый случай и ты завязываешься на какую-то конкретную реализацию — а это значит, что у тебя тесты не будут тестировать даже регрессию.

P>>>Покажите пример регрессии, которая не будет обнаружена этим тестом.
S>·>Ну загрузится пол таблицы. Или банально твой .top(10) не появится для какой-то конкретной комбинации фильтров, т.к. для этой комбинации решили немного переписать кусочек кода и забыли засунуть .top(10).
S>Нет. Эта проблема возникает только в том случае, если тестируется весь кусок про генерацию запроса целиком.
Мой поинт, что такой тест запроса целиком должен быть в любом случае. Что делает ценность этих ваших тестов <=0.

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

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

S>Альтернатива — не в том, чтобы сгенерировать 2N комбинаций условий фильтрации, а в том, чтобы распилить функцию построения запроса на две части:

S>1. Сгенерировать предикатную часть запроса
Это всё скучные тривиальные детали реализации.

S>2. Добавить к любому запросу .top(10).

Вот только невозможно проверить в _тесте_, что .top(10) действительно добавляется к _любому_ запросу.

S>Вот этот addLimit мы тестируем отдельным набором тестов, чтобы посмотреть, что он будет делать для отрицательных, нулевых, и положительных значений аргумента.

Это уже лажа. addLimit это метод некоего QueryBuilder, некий библиотечный метод, который уже имеет свои тесты, включая эти твои отрицательные аргументы. Мы полагаемся, что он работает в соотвествии с контрактом когда им пользуемся. А здесь мы говорим о тестировании некего условного UserRepository, которому какие-либо QueryBuilders совершенно побоку. В контексте разговора UserRepositoryTest предлагать тесты работоспособности addLimit — это как минимум паранойя.

S>Шанс "забыть засунуть .top(10)" у нас ровно в одном месте — конкретно вот в этой glue-функции, которая клеит два куска запроса.

S>Для того, чтобы этот шанс окончательно устранить, нам достаточно 1 (одного) теста для функции buildQuery — благодаря линейности кода, этот тест покроет нужный нам путь.
А зачем этот тест нужен-то? Убедиться, что в двух строчках кода одна из них действительно addLimit? Это и так напрямую явно видно в коде и каждый заметит наличие-отсутствие этого вызова и в PR диффе.
Ты по сути предлагаешь написать строчку кода, а потом продублировать в тесте, что эта строчка действительно написана. Ценность околонулевая. Твои тесты тестируют "works as coded".
Завтра придёт "специалист" и немного допишет:
IQueryable<User> buildQuery(UserFilter criteria, int pageSize)
    => someMagicalCondition(criteria) ? buildOptimizedQuery(criteria) : buildWhere(criteria).addLimit(pageSize);

И твои супер-тесты это не обнаружат. Т.е. твой тест даст false negative. Или тебе известна методология тестирования кода на простоту и линейность?

Мой поинт в том, что ценным тестом будет проверка что addLimit строчка действительно работает в соответствии с бизнес-требованиями — запустить на 11 записях и заассертить, что вернулось только 10 как прописано в FRD — это цель этой строчки, а сама по себе строчка — это средство. И заметь, такой 11/10 тест достаточно сделать для ровно той же комбинации фильтров что и в твоём тесте на наличие addLimit в коде, никакой экспоненциальности которой ты грозишься. Твой тест теоретически не может гарантировать, что некая функция buildQuery возвращает IQueryable с addLimit всегда, для любых входных параметров, такое можно лишь гарантировать через ревью текста самого кода.
Цель моего теста: если мы завтра перепишем addLimit на голый sql c "where rownum <= 10" или наоборот — то у нас будет регрессия, т.е. тесты которые так же продолжают работать и ожидаются, что должны быть зелёными без каких-либо изменений после рефакторинга. Такие тесты нужны в любом случае. А твой тест даст false positive и будет просто выкинут. Напишут взамен другой тест, в который закопипастят .contains("rownum < 10") перепутав что rownum zero- или one- based. Отличить на ревью корректность <= от < гораздо сложнее, чем условно save(...11 records...); assert find(10).size() == 10; assert find(5).size() == 5;.

S>Эта функция — строго линейна, она проверяется ровно одним тестом, который убеждается, что мы не забыли собрать все компоненты предиката.

Это какой-то тривиальный случай. Это значит не то, что мы магически сделали функцию линейной, а что никаких зависимостей между фильтрами нет в требованиях и никаких 2^N комбинаций просто не нужно, где тут эта ваша грозная data complexity — совершенно неясно.
В реальности у тебя могут быть хитрые зависимости между параметрами фильтра и вот тут и полезут неявные комбинации экспоненциально всё взрывающие и код несводимый к линейной функции, а будет хитрая if-else-switch лесенка.

S>А каждая из запчастей проверяется двумя или четырьмя тестами для всех нужных нам комбинаций её значений.

S>Всё, мы вместо 144 тестов (3*24*3) получили 3+2+4+2+2+3+1 = 17 тестов. При этом нам не нужны ни моки БД, ни тестовая БД в памяти, в которую запихано большое количество записей с разными комбинациями параметров.
S>То есть мы имеем 17 мгновенных тестов вместо 144 медленных, и гарантию полного покрытия.
Гарантию того, что у тебя works as coded. Зато отличить u => u.LastLoginTimeStamp >= criteria.MinLastLoginTimestamp.Value) от u => u.LastLoginTimeStamp <= criteria.MinLastLoginTimestamp.Value) такие тесты не смогут, т.к. пишутся как правило копипастом. На ревью очепятку очень вряд ли кто-то заметит.