Здравствуйте, another_coder, Вы писали:
_>Дело не в цвете, а в том, что вы, т.о., по сути, проверяете внутренний алгоритм. Оптимальнее будет проверять сценарии работы. При этом допускается, что Х.З. как именно реализован метод объекта, сохраняющий данные (внутри он может дергать SaveChangesAsync, SaveChanges, или еще что-то). Юнит-тестами проверяется сценарий, интеграционными проверяется связка (что данные прошли и записались). Момент с разработчиками не выносит критики: если слишком долго, пилится на тесты "по-меньше" или CI.
Это все теория.
Вот у тебя есть метод — делает выборку, обрабатывает, сохраняет. Это твой сценарий.
Для теста ты репозиторий подменяешь банальной реализацией на list<t>, когда метод просто отдает список. И проверяешь что данные в списке поменялись.
Ты написал код, который делает обработку, а SaveChangesAsync забыл. Тест проходит. При запуске не работает, тупо ничего не происходит.
У тебя два варианта:
1) Делать реальный транзакционный inmemory storage. Но я таких не видел за 10 лет (слава богу в EFCore его сделали).
2) Проверять integration-тестом, но с ними проблем еще больше — медленно, тестовые данные нужны, писать тесты сложнее.
На практике получается еще хуже.
У меня был случай, когда программа уже была написана и покрыта юнит-тестами. Покрытие методов БЛ было 100%. И тут провели рефакторинг, в процессе случайно выпилили SaveChanges в нескольких методах. Ничего не упало, ни тесты, ни код, но приложение перестало работать. Интеграционных тестов не было ибо юнит тесты покрывают 100% и так.
После этого я бросил писать юнит-тесты для бизнес-приложений. Интеграционные тесты делаю если руками проверять дольше. Поэтому отпала потребность подменять хранилище, и стал не нужен паттерн repository.
Здравствуйте, Vladek, Вы писали:
V>На этом построена архитектура ORM — приложение от него вообще не должно зависеть, оно должно его использовать. Грубо говоря, классы для ORM должны лежать в отдельной сборке с атрибутом internal.
Это опасное заблуждение. Есть такая порода архитекторов, которая искренне полагает, что можно придумать такую архитектуру приложения, которая позволяет хранить данные сегодня в одном XML-файле, завтра — в SQL-ной RDBMS, а послезавтра — в cloud storage в стиле S3. И типа всё, что надо будет для этого сделать — подправить config.xml.
Увы, устройство хранилища невозможно игнорировать. Точно так же, как не получится запустить SQL Server поверх ленточного стримера вместо блочного устройства.
Несмотря на то, что в обоих случаях работа идёт через одну и ту же абстракцию "файл".
V>А может вам стать администраторами баз данных — там SQL, батчи, скорость, ветер, свист в ушах, а? Никаких репозиториев, классов, абстракций.
С чего бы это? Я вам пытаюсь объяснить, что архитектор обязан не только парить в эмпиреях, но и учитывать существенные свойства компонентов системы.
Вот, например, лет двадцать тому назад бытовало мнение, что RPC — это круто. Типа давайте пренебрежём расположением вызываемого компонента. Бёрем, сериализуем сообщение, десериализуем ответ — и вуаля!
Объекты в соседнем процессе так же доступны, как и объекты на другой стороне интернета, и оба доступны так же, как и объекты в нашем собственном адресном пространстве.
А потом внезапно оказывается, что этим пренебрегать никак не получается — потому что объект в нашем адресном пространстве намного доступнее. И приложения, спроектированные без учёта этих особенностей, просто не живут в реальном мире.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Vladek, Вы писали:
V>Если тебе нужна быстрая выборка из бд — ты берёшь и придумываешь для этого внятную модель, и тут же реализовываешь её за 5 минут. Зачем тебе пробуждать всю модель предметной области для простого чтения?
Проблема в том, что "быстрая выборка из БД" работает совсем не так, как "быстрая выборка из локального файла", и не совсем так, как "быстрая выборка из удалённого веб-сервиса".
И это такая забавная деталь реализации, которой невозможно пренебрегать при построении модели.
Чем именно по-вашему занимается архитектор? Как он собирается что-то проектировать в иллюзии что "всё что угодно можно заменить чем угодно"?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, Vladek, Вы писали:
V>>На этом построена архитектура ORM — приложение от него вообще не должно зависеть, оно должно его использовать. Грубо говоря, классы для ORM должны лежать в отдельной сборке с атрибутом internal. S>Это опасное заблуждение. Есть такая порода архитекторов, которая искренне полагает, что можно придумать такую архитектуру приложения, которая позволяет хранить данные сегодня в одном XML-файле, завтра — в SQL-ной RDBMS, а послезавтра — в cloud storage в стиле S3. И типа всё, что надо будет для этого сделать — подправить config.xml. S>Увы, устройство хранилища невозможно игнорировать. Точно так же, как не получится запустить SQL Server поверх ленточного стримера вместо блочного устройства. S>Несмотря на то, что в обоих случаях работа идёт через одну и ту же абстракцию "файл".
V>>А может вам стать администраторами баз данных — там SQL, батчи, скорость, ветер, свист в ушах, а? Никаких репозиториев, классов, абстракций. S>С чего бы это? Я вам пытаюсь объяснить, что архитектор обязан не только парить в эмпиреях, но и учитывать существенные свойства компонентов системы. S>Вот, например, лет двадцать тому назад бытовало мнение, что RPC — это круто. Типа давайте пренебрежём расположением вызываемого компонента. Бёрем, сериализуем сообщение, десериализуем ответ — и вуаля! S>Объекты в соседнем процессе так же доступны, как и объекты на другой стороне интернета, и оба доступны так же, как и объекты в нашем собственном адресном пространстве. S>А потом внезапно оказывается, что этим пренебрегать никак не получается — потому что объект в нашем адресном пространстве намного доступнее. И приложения, спроектированные без учёта этих особенностей, просто не живут в реальном мире.
Думаю, что предполагать о заменяемости типа хранилища сейчас моветон. А вот об изолированности от него во время тестов — это то, что нужно. Совсем отрицать, что "ORM должны лежать в отдельной сборке с атрибутом internal." так же глупо, как и думать, что все абсолютно можно спрятать за ширмой абстракции. Но важно подразумевать, что в один момент кто-то захочет переписать и выкинуть ваш кусок кода и это должно быть легко и просто.
Здравствуйте, Vladek, Вы писали:
V>Я честно не понимаю этого вопроса. Код не высекается в граните, его можно править. Если твой веб-сервис чего-то не может, поправь его. То же относится и к репозиторию, и к хранилищу. Хорошая архитектура обеспечивает лёгкое редактирование.
Под "лёгкостью редактирования" обычно понимают возможность локализовать исправления. Вот эта идея "поправить веб-сервис" уже показывает, что реализация нужного требования затрагивает как минимум два места — сервис и его клиента. В чём легкость редактирования, если вам приходится совместно править компоненты? В чём суть архитектуры, если вы меняете её на ходу по каждому чиху?
V>EmployeeData — содержит изменённые данные, предназначены для сохранения, простой DTO. Хранилище (gateway) на основе List<T> просто сравнит его со своим списком и заменит прежние версии. Новые записи, которых не окажется в списке — могут быть просто добавлены или расценены как сбой (тогда произойдёт откат, в список вернутся прежние версии).
Отлично. Мы видим, что архитектор учёл при проектировании протокола тот факт, что бывают пакетные изменения. Это позволяет ему при реализации gateway обеспечить приемлемые нефункциональные характеристики независимо от расположения хранилища. Хорошо, что вы сразу пропустили первый шаг, который обычно выполняют неопытные архитекторы — gateway.SaveEmployee(EmployeeData e).
За это вы получаете 4 балла. 5 баллов вам не дадут потому, что SaveEmployees имеет скрытое требование в контракте — формально запрошен IEnumerable, а на самом деле ему нужен конечный список. Автор кода клиента об этом знал, потому что написал .ToArray(). Если он забудет написать ToArray(), то последствия могут быть непредсказуемыми. Я уж не говорю про
Ок, усложним задачу — нам нужно назначить Васю на должность начальника отдела. Атомарно изменяем Position у Employee, который представляет Васю, и Head у Department, который представляет транспортный отдел.
Как нам реализовать это, оставаясь в рамках вашей чудесной модели gateway? По-прежнему помним, что gateway мы родили ровно для того, чтобы "бизнес-код" не знал, как устроено хранение данных. То есть есть хороший шанс, что у нас есть две (или больше) реализации интерфейса IGateway().
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Вы делаете вывод о нужности юнит-тестов, руководствуясь частным случаем и основываясь на последствиях. Конечно, практика вносит свои корректировки: непрофессионализм девелоперов, ошибки проектирования и пр., которые следует учитывать. Но то кол-во случаев на каждом IF или SWITСH, которое можно проверить unit-tests вы никогда не проверите интеграционными. Либо это превратится в такой головняк с интеграционными, когда написание тестов жрет не меньше временеи, чем сам функционал.
То о чем вы говорите стандартные заблуждения. Если бы были интеграционные тесты, то ошибки были бы замечены. Но какой-то человек решил их не делать. А вывод, почему-то, делается не о его профессионализме, а о ненужности unit-test, которые свою работу сделали на отлично, как мне кажется. Как долго вы искали проблему?
G>Вот у тебя есть метод — делает выборку, обрабатывает, сохраняет. Это твой сценарий. G>Для теста ты репозиторий подменяешь банальной реализацией на list<t>, когда метод просто отдает список. И проверяешь что данные в списке поменялись. G>Ты написал код, который делает обработку, а SaveChangesAsync забыл. Тест проходит. При запуске не работает, тупо ничего не происходит.
Я бы распилил на несколько методов, которые по-отдельности можно протестировать. Т.е. выборка проверялась бы интеграционным тестом, обработка — юнит тестом, сохранение — интеграционным. При этом в юнит тесте выборку и сохранение я бы замокал. Выборка и сохранение на локальной базе — дело быстрое и не требующее кучи предварительных настроек данных.
G>У тебя два варианта: G>1) Делать реальный транзакционный inmemory storage. Но я таких не видел за 10 лет (слава богу в EFCore его сделали).
Сделать можно, но не надо.
G>2) Проверять integration-тестом, но с ними проблем еще больше — медленно, тестовые данные нужны, писать тесты сложнее.
Корень проблемы не в интеграционных тестах, а в людях, которые не умеют их писать. Как обычно, впрочем. Скорее всего, описанные тесты у вас больше похожи на end-to-end тесты, когда вы запускаете целиков всё (ВСЁ). Такие действильно очень тяжелые во всех отношениях. Тут необходимо помнить, что задача тестов (любых): проверить работу известного сценария. Приэтом, юнит — алгоритмы проверяет, а интеграционный только связку. Если перемешивать, то возникают проблемы.
G>На практике получается еще хуже. G>У меня был случай, когда программа уже была написана и покрыта юнит-тестами. Покрытие методов БЛ было 100%. И тут провели рефакторинг, в процессе случайно выпилили SaveChanges в нескольких методах. Ничего не упало, ни тесты, ни код, но приложение перестало работать. Интеграционных тестов не было ибо юнит тесты покрывают 100% и так.
G>После этого я бросил писать юнит-тесты для бизнес-приложений. Интеграционные тесты делаю если руками проверять дольше. Поэтому отпала потребность подменять хранилище, и стал не нужен паттерн repository.
Сколько людей на проекте?? Один-два?.. Риторический вопрос.
Здравствуйте, another_coder, Вы писали:
_>Думаю, что предполагать о заменяемости типа хранилища сейчас моветон. А вот об изолированности от него во время тестов — это то, что нужно. Совсем отрицать, что "ORM должны лежать в отдельной сборке с атрибутом internal." так же глупо, как и думать, что все абсолютно можно спрятать за ширмой абстракции. Но важно подразумевать, что в один момент кто-то захочет переписать и выкинуть ваш кусок кода и это должно быть легко и просто.
Ок, давайте разбираться. У нас есть совершенно разные требования, типа
— возможность заменить природу хранилища (не нужно)
— тестируемость (требует исследования, т.к. для разных видов тестов нужно изолировать разные компоненты)
— возможность локализовать изменения кода при изменении требований (у нас должно быть представление о том, какого рода ожидаются изменения)
По факту получается, что самое важное требование — последнее. И разные архитектуры по-разному его поддерживают. В частности, всякие table gateway, репозитории, и хранимки вида sp_AddProduct, sp_GetProduct, sp_DeleteProduct, sp_UpdateProduct — это просто мусор, который осложняет процесс разработки, не давая никаких преимуществ.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Sinclair, Вы писали:
S>Здравствуйте, another_coder, Вы писали:
_>>Думаю, что предполагать о заменяемости типа хранилища сейчас моветон. А вот об изолированности от него во время тестов — это то, что нужно. Совсем отрицать, что "ORM должны лежать в отдельной сборке с атрибутом internal." так же глупо, как и думать, что все абсолютно можно спрятать за ширмой абстракции. Но важно подразумевать, что в один момент кто-то захочет переписать и выкинуть ваш кусок кода и это должно быть легко и просто. S>Ок, давайте разбираться. У нас есть совершенно разные требования, типа S>- возможность заменить природу хранилища (не нужно) S>- тестируемость (требует исследования, т.к. для разных видов тестов нужно изолировать разные компоненты) S>- возможность локализовать изменения кода при изменении требований (у нас должно быть представление о том, какого рода ожидаются изменения) S>По факту получается, что самое важное требование — последнее. И разные архитектуры по-разному его поддерживают. В частности, всякие table gateway, репозитории, и хранимки вида sp_AddProduct, sp_GetProduct, sp_DeleteProduct, sp_UpdateProduct — это просто мусор, который осложняет процесс разработки, не давая никаких преимуществ.
Мне не понятны требования. Они слишком общие. Хотя бы надо или нет заменить хранилище? Полагаю, что если вы будете придерживаться SOLID при дальнейшем проектировании и рефакторинге, то проблемы постепенно уменьшатся, т.к. вы получите изолированность, и, как следствие, возможность заменить хранилище, тестировать.
Опять же, стоит четко понимать зачем тесты и какие в каждом случае необходимы и достаточны.
Здравствуйте, gandjustas, Вы писали:
G>2) Проверять integration-тестом, но с ними проблем еще больше — медленно, тестовые данные нужны, писать тесты сложнее.
Ну писать то их наоборот легче. Вы берете класс фасада пихаете ему параметры, там внутри куча всего происходит, проверяете. Нет моков, которые сковывают рефакторинг и которые еще писать надо.
Вот что сложнее, так это понять что конкретно сломалось.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, gandjustas, Вы писали:
G>>2) Проверять integration-тестом, но с ними проблем еще больше — медленно, тестовые данные нужны, писать тесты сложнее. G>Ну писать то их наоборот легче. Вы берете класс фасада пихаете ему параметры, там внутри куча всего происходит, проверяете. Нет моков, которые сковывают рефакторинг и которые еще писать надо. G>Вот что сложнее, так это понять что конкретно сломалось.
Хотя и в теории, но если написано все нормально... При зеленых юнит-тестах, но красном интеграционном, должно быть на 100% понятно где и что упало.
Здравствуйте, another_coder, Вы писали:
_>Мне не понятны требования. Они слишком общие. Хотя бы надо или нет заменить хранилище?
На мой взгляд, это требование а) ненужно б) невозможно обеспечить.
_>Полагаю, что если вы будете придерживаться SOLID при дальнейшем проектировании и рефакторинге, то проблемы постепенно уменьшатся, т.к. вы получите изолированность, и, как следствие, возможность заменить хранилище, тестировать.
Придерживаться можно чего угодно, хоть кашрута, хоть шариата. Но я пока не видел такой абстракции "хранилища", чтобы она подходила к чему угодно — от файла на ленте до NoSQL key-value хранилищ. _>Опять же, стоит четко понимать зачем тесты и какие в каждом случае необходимы и достаточны.
Воот.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, gandjustas, Вы писали:
G>>2) Проверять integration-тестом, но с ними проблем еще больше — медленно, тестовые данные нужны, писать тесты сложнее. G>Ну писать то их наоборот легче. Вы берете класс фасада пихаете ему параметры, там внутри куча всего происходит, проверяете. Нет моков, которые сковывают рефакторинг и которые еще писать надо. G>Вот что сложнее, так это понять что конкретно сломалось.
Хм... а ты писал такие тесты? В тестах 80% работы — подготовка, 10% исполнение и 10% проверка. Когда гоняешь тесты на реальной базе есть одна маленькая проблема — база хранит состояние. А для разных тестов нужны разные данные. Записывать эталонные данные каждый раз — код тестов распухает в разы, не записывать — тесты начинают зависеть от порядка запуска.
Здравствуйте, another_coder, Вы писали:
_>Хотя и в теории, но если написано все нормально... При зеленых юнит-тестах, но красном интеграционном, должно быть на 100% понятно где и что упало.
И зачем тогда нужны юнит-тесты, если в реальности проверка делается только интеграционным?
Здравствуйте, gandjustas, Вы писали:
G>Хм... а ты писал такие тесты? В тестах 80% работы — подготовка, 10% исполнение и 10% проверка. Когда гоняешь тесты на реальной базе есть одна маленькая проблема — база хранит состояние. А для разных тестов нужны разные данные. Записывать эталонные данные каждый раз — код тестов распухает в разы, не записывать — тесты начинают зависеть от порядка запуска.
Перед запуском теста база чистится. Создается все заново, запускается кейс. Для создания базы есть что-то вроде мастеров создания, код создания базы у большинства тестов одинаковый, различается незначительно. Либо более радикальное решение, вы для теста храните бекап базы. Это если у вас миграция версий настроена...
Писать как раз таки очень легко, отлаживать тяжело и понимать что конкретно упало.
Из минусов еще — работает медленно, но т.к. запускается в фоне не критично.
Здравствуйте, another_coder, Вы писали:
_>Здравствуйте, gandjustas.
_>Вы делаете вывод о нужности юнит-тестов, руководствуясь частным случаем и основываясь на последствиях. Конечно, практика вносит свои корректировки: непрофессионализм девелоперов, ошибки проектирования и пр., которые следует учитывать. Но то кол-во случаев на каждом IF или SWITСH, которое можно проверить unit-tests вы никогда не проверите интеграционными. Либо это превратится в такой головняк с интеграционными, когда написание тестов жрет не меньше временеи, чем сам функционал.
Ты подменяешь понятия. Если можно написать юнит тест, это вовсе не означает, что им можно что-то проверить. Я несколько раз видел программы с тестовым покрытием близким к 100%, естественно юнит-тестами. В них были баги в огромном количестве.
_>То о чем вы говорите стандартные заблуждения. Если бы были интеграционные тесты, то ошибки были бы замечены. Но какой-то человек решил их не делать. А вывод, почему-то, делается не о его профессионализме, а о ненужности unit-test, которые свою работу сделали на отлично, как мне кажется. Как долго вы искали проблему?
Если у бабушки кое-что было, то она была бы не совсем бабушкой. Не делали интеграционные тесты по причине, описанной тобой выше. Покрыть интеграционными тестами значимую часть программы с разумными затратами невозможно. А юнит тестами можно. Вот только юнит-тесты не проверяют.
G>>Вот у тебя есть метод — делает выборку, обрабатывает, сохраняет. Это твой сценарий. G>>Для теста ты репозиторий подменяешь банальной реализацией на list<t>, когда метод просто отдает список. И проверяешь что данные в списке поменялись. G>>Ты написал код, который делает обработку, а SaveChangesAsync забыл. Тест проходит. При запуске не работает, тупо ничего не происходит.
_>Я бы распилил на несколько методов, которые по-отдельности можно протестировать. Т.е. выборка проверялась бы интеграционным тестом, обработка — юнит тестом, сохранение — интеграционным. При этом в юнит тесте выборку и сохранение я бы замокал.
По-моему ничего бы не изменилось.
Код был примерно такой:
void F(Repo repo)
{
var xs = repo.GetXsByXXX();
foreach(var x in xs)
{
x.y+=1;
}
// repo.SaveChanges(); // эту строку потеряли при рефакторинге
}
[Test]
void Test()
{
var mock = new Mock();
var xs = new [] { ... };
mock.Xs = xs;
F(mock);
Assert.IsTrue(xs.All(...));
}
Тест зеленый, код не работает.
Ты предлагаешь покрыть GetXsByXXX интеграционным тестом, что не имеет смысла, там примитивный запрос. Покрыть SaveChanges интеграционным тестом, что тоже не имеет смылса и написать юнит-тест для F, что не дает фактически проверки.
G>>У тебя два варианта: G>>1) Делать реальный транзакционный inmemory storage. Но я таких не видел за 10 лет (слава богу в EFCore его сделали). _>Сделать можно, но не надо.
Когда есть готовый, то не надо. А раньше пытался такое избразить, но как-то дофига сложно получилось.
G>>2) Проверять integration-тестом, но с ними проблем еще больше — медленно, тестовые данные нужны, писать тесты сложнее. _>Корень проблемы не в интеграционных тестах, а в людях, которые не умеют их писать. Как обычно, впрочем. Скорее всего, описанные тесты у вас больше похожи на end-to-end тесты, когда вы запускаете целиков всё (ВСЁ). Такие действильно очень тяжелые во всех отношениях. Тут необходимо помнить, что задача тестов (любых): проверить работу известного сценария. Приэтом, юнит — алгоритмы проверяет, а интеграционный только связку. Если перемешивать, то возникают проблемы.
Это словоблудие. Известный сценарий я привел, как его юнит-тестом проверить?
У меня еще интереснее сценарий есть:
В приложении календарь. При создании нового события надо проверить, что событие не пересекается с существующим. Событий много, тянуть все в память нельзя, проверять надо запросом к базе. И важное условие — тебе повезло, ты не можешь использовать Linq, обязательно текстовые запросы.
Напиши юнит-тест для проверки.
Здравствуйте, Gattaka, Вы писали:
G>Здравствуйте, gandjustas, Вы писали:
G>>Хм... а ты писал такие тесты? В тестах 80% работы — подготовка, 10% исполнение и 10% проверка. Когда гоняешь тесты на реальной базе есть одна маленькая проблема — база хранит состояние. А для разных тестов нужны разные данные. Записывать эталонные данные каждый раз — код тестов распухает в разы, не записывать — тесты начинают зависеть от порядка запуска.
G>Перед запуском теста база чистится. Создается все заново, запускается кейс.
Код тестов распухает в разы. Потому что сделать нормальные проверки на одних и тех же эталонных данных невозможно. Тебе как минимум надо проверять три случая — "0,1,n".
Ну или просто смириться, что покрытие тестами будет от силы 20%.
G>Для создания базы есть что-то вроде мастеров создания, код создания базы у большинства тестов одинаковый, различается незначительно. Либо более радикальное решение, вы для теста храните бекап базы. Это если у вас миграция версий настроена...
ОМГ
Здравствуйте, IT, Вы писали:
IT>Это у кого как и смотря что мы под этим понимаем. IT>Нет. Функция изоляции переехала в LINQ. А сам DAL больше нафиг не нужен.
Большие дяди живут в мире придуманных ими определений?
MSDN:
LINQ is a set of features that extends powerful query capabilities to the language syntax of C#. LINQ introduces standard, easily-learned patterns for querying and updating data, and the technology can be extended to support potentially any kind of data store. The .NET Framework includes LINQ provider assemblies that enable the use of LINQ with .NET Framework collections, SQL Server databases, ADO.NET Datasets, and XML documents.
Хочу оговориться тут, что все о чем я говорю я не считаю истинной в последней инстанции. Тем не менее, у меня было штуки три больших проектов с интеграционными и юнит-тестами, в которых я воочию видел все описанные проблемы и сам занимался их устранением.
Вы писали:
G>Ты подменяешь понятия. Если можно написать юнит тест, это вовсе не означает, что им можно что-то проверить. Я несколько раз видел программы с тестовым покрытием близким к 100%, естественно юнит-тестами. В них были баги в огромном количестве.
G>Если у бабушки кое-что было, то она была бы не совсем бабушкой. Не делали интеграционные тесты по причине, описанной тобой выше. Покрыть интеграционными тестами значимую часть программы с разумными затратами невозможно. А юнит тестами можно. Вот только юнит-тесты не проверяют.
Я не подменяю понятия. Если гнаться только за покрытие кода тестами, то понятно, почему юнит-тесты ничего не проверяют. UT должно быть легко выкинуть, переписать, написать с нуля и запустить. Покрытие — это очень косвенный показатель. Юнит тесты необходимо проверять так же, как и остальной код, чтобы не было написания тестов ради самих тестов. По сути, это спасательные якоря скалолаза (самого разработчика), если образно. Их может быть не много, но там где надо.
G>>>Вот у тебя есть метод — делает выборку, обрабатывает, сохраняет. Это твой сценарий. G>>>Для теста ты репозиторий подменяешь банальной реализацией на list<t>, когда метод просто отдает список. И проверяешь что данные в списке поменялись. G>>>Ты написал код, который делает обработку, а SaveChangesAsync забыл. Тест проходит. При запуске не работает, тупо ничего не происходит.
_>>Я бы распилил на несколько методов, которые по-отдельности можно протестировать. Т.е. выборка проверялась бы интеграционным тестом, обработка — юнит тестом, сохранение — интеграционным. При этом в юнит тесте выборку и сохранение я бы замокал. G>По-моему ничего бы не изменилось.
G>Код был примерно такой: G>
G>void F(Repo repo)
G>{
G> var xs = repo.GetXsByXXX();
G> foreach(var x in xs)
G> {
G> x.y+=1;
G> }
G> // repo.SaveChanges(); // эту строку потеряли при рефакторинге
G>}
G>[Test]
G>void Test()
G>{
G> var mock = new Mock();
G> var xs = new [] { ... };
G> mock.Xs = xs;
G> F(mock);
G> Assert.IsTrue(xs.All(...));
G>}
G>
G>Тест зеленый, код не работает.
G>Ты предлагаешь покрыть GetXsByXXX интеграционным тестом, что не имеет смысла, там примитивный запрос. Покрыть SaveChanges интеграционным тестом, что тоже не имеет смылса и написать юнит-тест для F, что не дает фактически проверки.
Тут чтобы правильно ответить надо еще вопросов позадовать. Если интересно, то расскажите, в чем главная цель этого метода F (от этого зависит как он должен быть написан и какие тесты для него писать)? А так же интересно, ваш реп должен хранить состояние и беферизировать данные, или просто явлется прослойкой между ORM (EF, например) и BO?
G>У меня еще интереснее сценарий есть: G>В приложении календарь. При создании нового события надо проверить, что событие не пересекается с существующим. Событий много, тянуть все в память нельзя, проверять надо запросом к базе. И важное условие — тебе повезло, ты не можешь использовать Linq, обязательно текстовые запросы. G>Напиши юнит-тест для проверки.
Для проверки чего? Хранимки, динамического стейтмента, условий проверки? Описание не достаточно, чтобы нормально ответить вопрос.
G>Я этой задачей троллю апологетов юнит-тестов.
Можем попробовать по разбираться. Кому-то точно в + итоги будут. Если без попыток убедить и навязать свое мнение
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, another_coder, Вы писали:
_>>Хотя и в теории, но если написано все нормально... При зеленых юнит-тестах, но красном интеграционном, должно быть на 100% понятно где и что упало.
G>И зачем тогда нужны юнит-тесты, если в реальности проверка делается только интеграционным?
Не только, а совокупностью. Т.е. например, ты знаешь, что метод Save у тебе не покрыт тестов. Случай, когда все юниты зеленые, но интеграционный нет говорит, в общем случае, о том, что логика правильна, а вот момент с записью не работает. А ведь может быть так, что интеграционный зеленый, а 5 из 20 юнит тестов красный (даже 1).
Если вы считаете, что интеграционный должен быть красным всегда, когда хотя бы один юнит тест красный, то вы заблуждаетесь. Связка двух систем у вас может работать нормально.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Gattaka, Вы писали:
G>>Здравствуйте, gandjustas, Вы писали:
G>>>Хм... а ты писал такие тесты? В тестах 80% работы — подготовка, 10% исполнение и 10% проверка. Когда гоняешь тесты на реальной базе есть одна маленькая проблема — база хранит состояние. А для разных тестов нужны разные данные. Записывать эталонные данные каждый раз — код тестов распухает в разы, не записывать — тесты начинают зависеть от порядка запуска.
G>>Перед запуском теста база чистится. Создается все заново, запускается кейс. G>Код тестов распухает в разы. Потому что сделать нормальные проверки на одних и тех же эталонных данных невозможно. Тебе как минимум надо проверять три случая — "0,1,n". G>Ну или просто смириться, что покрытие тестами будет от силы 20%.
G>>Для создания базы есть что-то вроде мастеров создания, код создания базы у большинства тестов одинаковый, различается незначительно. Либо более радикальное решение, вы для теста храните бекап базы. Это если у вас миграция версий настроена... G>ОМГ
В тестах можно использовать TransactionScope с минимальной изоляцией. Конечно, это не отменяет внимания к тому, чтобы не возникало зависимостей между тестами, но сильно облегчает создание необходимых данных.