Здравствуйте, gandjustas.
Хочу оговориться тут, что все о чем я говорю я не считаю истинной в последней инстанции. Тем не менее, у меня было штуки три больших проектов с интеграционными и юнит-тестами, в которых я воочию видел все описанные проблемы и сам занимался их устранением.
Вы писали:
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>Я этой задачей троллю апологетов юнит-тестов.
Можем попробовать по разбираться. Кому-то точно в + итоги будут. Если без попыток убедить и навязать свое мнение