Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, Gattaka, Вы писали:
G>>Здравствуйте, gandjustas, Вы писали:
G>>>Хм... а ты писал такие тесты? В тестах 80% работы — подготовка, 10% исполнение и 10% проверка. Когда гоняешь тесты на реальной базе есть одна маленькая проблема — база хранит состояние. А для разных тестов нужны разные данные. Записывать эталонные данные каждый раз — код тестов распухает в разы, не записывать — тесты начинают зависеть от порядка запуска.
G>>Перед запуском теста база чистится. Создается все заново, запускается кейс. G>Код тестов распухает в разы. Потому что сделать нормальные проверки на одних и тех же эталонных данных невозможно. Тебе как минимум надо проверять три случая — "0,1,n". G>Ну или просто смириться, что покрытие тестами будет от силы 20%.
Есть еще подход, где "двигают" данные. Положим у вас система, где в вашей предметной область есть некоторая лицензия, позволяющая создать объекты. Так вот вы загружаете лицензию, затем создаете первый объект на котором проверяете тест. Затем не загружая лицензии и не удаляя первый объект создаете второй объект и на нем проверяете второй тест. Но лично я не практиковал такой подход.
G>>Для создания базы есть что-то вроде мастеров создания, код создания базы у большинства тестов одинаковый, различается незначительно. Либо более радикальное решение, вы для теста храните бекап базы. Это если у вас миграция версий настроена... G>ОМГ
А что такого? Тесты это тот же код, и там работают те же правила что и при разработке софта...
Здравствуйте, Baudolino, Вы писали:
IT>>Нет. Функция изоляции переехала в LINQ. А сам DAL больше нафиг не нужен. B>Большие дяди живут в мире придуманных ими определений?
Большие дяди живут прежде всего в мире здравого смысла.
А маленькие дяди (или большие дети?) не различают в огороде бузину, а в Киеве дядьку.
B>MSDN: B>
B>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.
И что? С чем ты здесь не согласен и как это противоречит моим "придуманным" определениям?
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, another_coder, Вы писали:
_>Я не подменяю понятия. Если гнаться только за покрытие кода тестами, то понятно, почему юнит-тесты ничего не проверяют. UT должно быть легко выкинуть, переписать, написать с нуля и запустить. Покрытие — это очень косвенный показатель. Юнит тесты необходимо проверять так же, как и остальной код, чтобы не было написания тестов ради самих тестов. По сути, это спасательные якоря скалолаза (самого разработчика), если образно. Их может быть не много, но там где надо.
Тогда возникает резонный вопрос — стоят ли юнит-тесты затрачиваемых на них усилий?
И следом второй вопрос — надо ла обязательно обвешивать программу абстракциями для UT, если затраты на UT превышают пользу?
G>>Ты предлагаешь покрыть GetXsByXXX интеграционным тестом, что не имеет смысла, там примитивный запрос. Покрыть SaveChanges интеграционным тестом, что тоже не имеет смылса и написать юнит-тест для F, что не дает фактически проверки.
_>Тут чтобы правильно ответить надо еще вопросов позадовать. Если интересно, то расскажите, в чем главная цель этого метода F (от этого зависит как он должен быть написан и какие тесты для него писать)? А так же интересно, ваш реп должен хранить состояние и беферизировать данные, или просто явлется прослойкой между ORM (EF, например) и BO?
Очень интересный вопрос, учитывая, что разговор начался с того, что репозиторий создается для изоляции программы от "деталей" работы с хранилищем. А тут получается что эти "детали" выходят на первое место.
Пусть является прослойкой к EF.
_>Для проверки чего? Хранимки, динамического стейтмента, условий проверки? Описание не достаточно, чтобы нормально ответить вопрос.
Для проверки метода.
Псевдокод такой:
Основная логика сосредоточена в построении текстового запроса, но храниище настолько сложное, что мелкие детали в запросе и в данных могут привести к сильно разным результатам.
То есть просто проверять строку на соответствие "эталонной" нельзя, для гарантии корректности запрос надо в хранилище отправить.
Любой метод можно мокать, но повторить поведение хранилища — трудозатраты в сотни раз превышающие само приложение.
Зеленый UT должен показывать что код отработает корректно в продакшене, если база будет доступна.
G>>Я этой задачей троллю апологетов юнит-тестов. _>Можем попробовать по разбираться. Кому-то точно в + итоги будут. Если без попыток убедить и навязать свое мнение
Здравствуйте, another_coder, Вы писали:
_>Здравствуйте, gandjustas, Вы писали:
G>>Здравствуйте, another_coder, Вы писали:
_>>>Хотя и в теории, но если написано все нормально... При зеленых юнит-тестах, но красном интеграционном, должно быть на 100% понятно где и что упало.
G>>И зачем тогда нужны юнит-тесты, если в реальности проверка делается только интеграционным?
_>Не только, а совокупностью. Т.е. например, ты знаешь, что метод Save у тебе не покрыт тестов. Случай, когда все юниты зеленые, но интеграционный нет говорит, в общем случае, о том, что логика правильна, а вот момент с записью не работает. А ведь может быть так, что интеграционный зеленый, а 5 из 20 юнит тестов красный (даже 1). _>Если вы считаете, что интеграционный должен быть красным всегда, когда хотя бы один юнит тест красный, то вы заблуждаетесь. Связка двух систем у вас может работать нормально.
Ты ушел от ответа на вопрос. Зачем вообще нужны юнит-тесты, какова их ценность, если UT могут быть зелеными, а интеграционный — красным.
От ответа на этот вопрос зависит и другой вопрос — надо ли вообще код приложения изолировать от деталей работы с хранилищем? Ведь мы прекрасно понимает, что в реальности приложение, работающее с базой, не начнет работать с веб-сервисом после простой замены репозитория. Поэтому можем попробовать найти другую причину появления такой абстракции.
Здравствуйте, another_coder, Вы писали:
_>В тестах можно использовать TransactionScope с минимальной изоляцией. Конечно, это не отменяет внимания к тому, чтобы не возникало зависимостей между тестами, но сильно облегчает создание необходимых данных.
Это после того, как несколько человек утверждали, что репозиторий создается чтобы изолировать приложение от "деталей" доступа к данным? Вдруг у нас "на другом" конце вообще веб-сервис, состояние которого мы не можем напрямую контролировать?
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, another_coder, Вы писали:
_>>Здравствуйте, gandjustas, Вы писали:
G>>>Здравствуйте, another_coder, Вы писали:
_>>>>Хотя и в теории, но если написано все нормально... При зеленых юнит-тестах, но красном интеграционном, должно быть на 100% понятно где и что упало.
G>>>И зачем тогда нужны юнит-тесты, если в реальности проверка делается только интеграционным?
_>>Не только, а совокупностью. Т.е. например, ты знаешь, что метод Save у тебе не покрыт тестов. Случай, когда все юниты зеленые, но интеграционный нет говорит, в общем случае, о том, что логика правильна, а вот момент с записью не работает. А ведь может быть так, что интеграционный зеленый, а 5 из 20 юнит тестов красный (даже 1). _>>Если вы считаете, что интеграционный должен быть красным всегда, когда хотя бы один юнит тест красный, то вы заблуждаетесь. Связка двух систем у вас может работать нормально. G>Ты ушел от ответа на вопрос. Зачем вообще нужны юнит-тесты, какова их ценность, если UT могут быть зелеными, а интеграционный — красным. G>От ответа на этот вопрос зависит и другой вопрос — надо ли вообще код приложения изолировать от деталей работы с хранилищем? Ведь мы прекрасно понимает, что в реальности приложение, работающее с базой, не начнет работать с веб-сервисом после простой замены репозитория. Поэтому можем попробовать найти другую причину появления такой абстракции.
Я на него ответил. Смысл в том, что юнит-тесты показывают правильность использованных алгоритмов. Если они зеленные и правильно написаны, то ты можешь быть уверен, что в известных сценариях у тебя все нормально. Но всегда есть доля неизвестного и тут ничего не поделаешь. Интеграционные тесты проверяют не алгоритмы, а связку между системами. Т.е. можно не заниматься подготовкой запуска всего-всего, а, например, проинициализировать данные перед сохранением и проверить, что это отработало. Это два типа тестов, не зависимые друг от друга и показывающие общую картину в совокупности.
Тесты end-to-end, которые многие путают с интеграционными, нужны в очень ограниченном кол-ве, достаточном для проверки работоспособности системы в целом.
Здравствуйте, gandjustas, Вы писали:
G>Здравствуйте, another_coder, Вы писали:
_>>В тестах можно использовать TransactionScope с минимальной изоляцией. Конечно, это не отменяет внимания к тому, чтобы не возникало зависимостей между тестами, но сильно облегчает создание необходимых данных. G>Это после того, как несколько человек утверждали, что репозиторий создается чтобы изолировать приложение от "деталей" доступа к данным? Вдруг у нас "на другом" конце вообще веб-сервис, состояние которого мы не можем напрямую контролировать?
Как я понял, тут речь шла об интеграционных тестах. В них не изолироваться.
G>Зеленый UT должен показывать что код отработает корректно в продакшене, если база будет доступна.
Вот тут поправлю: UT покажет, что алгоритм генерации запросов работает так, как разработчик предполагает он должен, а интеграционный тест покажет как потом он работает на _тестовом_ окружении. Не стоит полагать, что тесты заткнут все возможные дыры, даже еще не встреченные.
Что я сделал бы...
1) необходимо иметь возможность мокать:
db.query(query)
db.addEvent(evt)
Не знаю что из себя представляет db. Это интерфейс? Как создается/инжектится?
2) buildQuery(evt) необходимо передать другой сущности, которая занимается его построением. Тогда:
— можно будет отдельно протестировать
— как следствие, можно будет замокать в тестах
Например, вынесем в другой class QueryBuilder : IQueryBuilder {}
На этом этапе код может, предположительно, выглядеть так, после изменений:
3) В этом случае на метод F можно написать такие юнит тесты (проверяем ветви алгоритма):
— должен возвращать false, если rs.Count>0
— должен возвращать true, если rs.Count<=0
— должен вызвать addEvent с переданным evt, если rs.Count<=0
4) Отдельно можно протестировать генерацию запросов в методе buildQuery класса QueryBuilder. Не знаю деталей, но девелопер же написал алгоритм генерации, значит может, подавая на вход разные evt, проверить построение соответствующих для них строк. Получится несколько юнит тестов.
5) Я полагаю, что в db.query только сам механизм вызова БД. Поэтому, в интеграционном тесте проверяем, что переданный запрос в db.query вернул то, что требовалось запросом. Этот тест покажет, что этот метод получает данные из базы и адекватно выполняет запросы, что и требуется.
В итоге получилось UT 3+ и IT 1 = 4 или более тестов.
Теперь представим, что вносили какие-то изменения. Может оказаться так, что все зеленное, но почему-то не работает на продакшене. Это проблема не тестов, а тестового окружения. Предположим, там лочится таблица, метод query падает. Тут понятно что делать. У вас все проверяется, кроме этого момента. Значит, специальным интеграционным тестом необходимо проверить этот момент, а метод buildQuery проапдейтить так, чтобы, например, он ставил (nolock).
Здравствуйте, another_coder, Вы писали:
_>Я на него ответил. Смысл в том, что юнит-тесты показывают правильность использованных алгоритмов. Если они зеленные и правильно написаны, то ты можешь быть уверен, что в известных сценариях у тебя все нормально. Но всегда есть доля неизвестного и тут ничего не поделаешь. Интеграционные тесты проверяют не алгоритмы, а связку между системами. Т.е. можно не заниматься подготовкой запуска всего-всего, а, например, проинициализировать данные перед сохранением и проверить, что это отработало. Это два типа тестов, не зависимые друг от друга и показывающие общую картину в совокупности. _>Тесты end-to-end, которые многие путают с интеграционными, нужны в очень ограниченном кол-ве, достаточном для проверки работоспособности системы в целом.
Если ты не в курсе, то система в целом — это и есть система. Почитай хотя бы теорию систем. Отдельные части системы не определяют её сложность, а лишь обозначают её. Сложность системы определяется связями между её компонентами. Интеграционные тесты как раз тестируют свяь компонентов системы, как бы ты их не называл.
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, another_coder, Вы писали:
_>1) необходимо иметь возможность мокать: _>db.query(query) _>db.addEvent(evt) _>Не знаю что из себя представляет db. Это интерфейс? Как создается/инжектится?
Неважно, говорю же мокать можно все.
_>2) buildQuery(evt) необходимо передать другой сущности, которая занимается его построением. Тогда: _>- можно будет отдельно протестировать _>- как следствие, можно будет замокать в тестах _>Например, вынесем в другой class QueryBuilder : IQueryBuilder {}
Зачем? Это чистая функция, её протестировать можно и так. Но тест этот ничего не дает без отправки запроса в базу.
_>3) В этом случае на метод F можно написать такие юнит тесты (проверяем ветви алгоритма): _>- должен возвращать false, если rs.Count>0 _>- должен возвращать true, если rs.Count<=0 _>- должен вызвать addEvent с переданным evt, если rs.Count<=0
Тестировать if — это сильно.
_>4) Отдельно можно протестировать генерацию запросов в методе buildQuery класса QueryBuilder. Не знаю деталей, но девелопер же написал алгоритм генерации, значит может, подавая на вход разные evt, проверить построение соответствующих для них строк. Получится несколько юнит тестов.
Само по себе это тестирование ничего не дает. Запросто может просто название поля поменяться.
_>5) Я полагаю, что в db.query только сам механизм вызова БД. Поэтому, в интеграционном тесте проверяем, что переданный запрос в db.query вернул то, что требовалось запросом. Этот тест покажет, что этот метод получает данные из базы и адекватно выполняет запросы, что и требуется.
db — внешний код его тестировать не надо.
_>В итоге получилось UT 3+ и IT 1 = 4 или более тестов.
Из которых один не нужен, один тестирует очевидные вещи, остальные не дают гарантий.
Здравствуйте, gandjustas, Вы писали:
_>>2) buildQuery(evt) необходимо передать другой сущности, которая занимается его построением. Тогда: _>>- можно будет отдельно протестировать _>>- как следствие, можно будет замокать в тестах _>>Например, вынесем в другой class QueryBuilder : IQueryBuilder {} G>Зачем? Это чистая функция, её протестировать можно и так. Но тест этот ничего не дает без отправки запроса в базу.
Её конечно можно протестировать. Через вызов Application.Run например тоже можно протестироват её же. И еще кучу других.
Но зачем так усложнять, если можно сделать проще? Выгода: проще понять код, легче модифицировать/выкинуть, в тестах пример использования.
А вопрос про базу и про что дает ниже...
_>>3) В этом случае на метод F можно написать такие юнит тесты (проверяем ветви алгоритма): _>>- должен возвращать false, если rs.Count>0 _>>- должен возвращать true, если rs.Count<=0 _>>- должен вызвать addEvent с переданным evt, если rs.Count<=0 G>Тестировать if — это сильно.
IF часть алгоритма, поэтому необходимо. Вы еще скажите, например, что расчет факториала можно проверить только одним тестом.
_>>4) Отдельно можно протестировать генерацию запросов в методе buildQuery класса QueryBuilder. Не знаю деталей, но девелопер же написал алгоритм генерации, значит может, подавая на вход разные evt, проверить построение соответствующих для них строк. Получится несколько юнит тестов. G>Само по себе это тестирование ничего не дает. Запросто может просто название поля поменяться.
Именно, что дает. Ваш метод генерилка ни что иное, как y = F(x). x и y определены, а девелопер пишет F. Зачем y отправлять в базу, если это часть требований?
С полем все просто: это часть требований. Поменялись требования к методу, необходимо это отразить в коде.
_>>5) Я полагаю, что в db.query только сам механизм вызова БД. Поэтому, в интеграционном тесте проверяем, что переданный запрос в db.query вернул то, что требовалось запросом. Этот тест покажет, что этот метод получает данные из базы и адекватно выполняет запросы, что и требуется. G>db — внешний код его тестировать не надо.
У вас был тест на Save в описанном вами случае? Полагаю, с таким тестом вы не пропустили бы проблему и легко нашли бы её.
Главная идея тестов: разбиение кода по принципу SRP, определение требований к полученым кускам и кодирование этих требований в виде UT, IT и пр. Тесты меняются, выкидываются, пишутся новые — не надо этого бояться. TDD форева! )
оффтоп: по вашим коментам выглядит так, словно девелоперы у вас пишут "во тьме": без требований, без понимания того, как оно должно быть. Такое возможно, напрмиер в R&D и там тесты реально не всегда столь эфективны, иногда даже пустая трата времени. Но на то это и исследование/прототипирование.