Comments 35
спасибо. очень познавательно. правильные выводы.
0
а почему вы проверяете
и почему нет теста типа такого
if type(n) is float or type(n) is complex:а не
if type(n) is not int:за этим стоит какой-то глубокий смысл?
и почему нет теста типа такого
def test_string(self):или даже
self.assertRaises(TypeError, factorial, 'kaka')
def test_WTF(self):это не сделало бы покрытие полней?
self.assertRaises(TypeError, factorial, WRF_Class())
+6
Возник такой же вопрос про строки: что метрики, используемые в статье дают не совсем корректные цифры.
Более того — вся статьи базируется на одной из трактовок для «полного покрытия» как «покрытие каждого метода хотя бы одним вызовом».
С другой стороны — недостаток показателен: цифра 100% вообще ни о чём не говорит, равно как и 83%. Добавленный для бюрократии кейс — тестировал лишь вызов тестируемого кода, а не сам тестируемый код, что вообще абсурдно. Что будет дальше — тесты на тесты?
Более того — вся статьи базируется на одной из трактовок для «полного покрытия» как «покрытие каждого метода хотя бы одним вызовом».
С другой стороны — недостаток показателен: цифра 100% вообще ни о чём не говорит, равно как и 83%. Добавленный для бюрократии кейс — тестировал лишь вызов тестируемого кода, а не сам тестируемый код, что вообще абсурдно. Что будет дальше — тесты на тесты?
+5
В данном случае используется метрика от coverage-модуля. Он вычисляет покрытие из расчета строчек кода — если строка кода не была вызвана в тестах, значит она не покрыта. 100% показывает, что каждая строка была вызвана в тестах хотя бы один раз.
«Вызов тестируемого кода» (как я понял, вы имеете в виду код, исполняемый для __main__) тоже должен тестироваться — некоторые скрипты вообще могут быть написаны без объявления функций и классов, и тем не менее содержать сложную логику, которую нужно протестировать. То, что это архитектурно неправильно, другой вопрос — есть legacy-код, который перед рефакторингом нужно покрыть тестами, иначе можно внести ошибку. А этот legacy-код может быть написан как угодно, и без декомпозиции на функции классы.
«Вызов тестируемого кода» (как я понял, вы имеете в виду код, исполняемый для __main__) тоже должен тестироваться — некоторые скрипты вообще могут быть написаны без объявления функций и классов, и тем не менее содержать сложную логику, которую нужно протестировать. То, что это архитектурно неправильно, другой вопрос — есть legacy-код, который перед рефакторингом нужно покрыть тестами, иначе можно внести ошибку. А этот legacy-код может быть написан как угодно, и без декомпозиции на функции классы.
0
__main__ это клиентский код. клиентский код тестируется приёмочными и функциональными тестами, а не модульными.
«100% показывает» — с бюрократической точки зрения, да показывает. на деле остаётся миллион вариантов, которые нужно протестировать, но вы уже успокоились, ибо 100%.
Повторю свои выводы: эта цифра в 100% не значит абсолютно ничего, и тесты с 83% покрытием ничем не хуже, чем 100%. почему? да потому что при 83% можно покрыть вызовы со стандартными аргументами, граничными и ошибочными, а при 100 — передать одно значение и успокоиться. Вы в своей статье делаете неправильные акценты и манипулируете терминами, которые работают в маркетинге («В наших проектах мы уже больше года обеспечиваем стопроцентное покрытие кода»), но с точки зрения программистов — фразы бессмысленные и ничего не характеризующие.
«100% показывает» — с бюрократической точки зрения, да показывает. на деле остаётся миллион вариантов, которые нужно протестировать, но вы уже успокоились, ибо 100%.
Повторю свои выводы: эта цифра в 100% не значит абсолютно ничего, и тесты с 83% покрытием ничем не хуже, чем 100%. почему? да потому что при 83% можно покрыть вызовы со стандартными аргументами, граничными и ошибочными, а при 100 — передать одно значение и успокоиться. Вы в своей статье делаете неправильные акценты и манипулируете терминами, которые работают в маркетинге («В наших проектах мы уже больше года обеспечиваем стопроцентное покрытие кода»), но с точки зрения программистов — фразы бессмысленные и ничего не характеризующие.
+8
Ах да, забыл: ваши трудности вроде «хотя и придется работать с магией Python-а» как раз и вызваны тем, что вы используете неправильные инструменты (модульные тесты, вместо функциональных).
+5
Вот как раз вы и разводите бюрократию. Кто сказал, что __main__ — это клиентский код? Может в виртуальном мире сферических программистов в вакууме весь __main__-код и является клиентским (т.е. отвечает за взаимодействие с пользователем, как я понимаю), но в реальном мире скрипт может быть вызван через exec, и иметь всю логику именно в __main__.
Приемочные и функциональные тесты — это тоже из области бюрократии. Разве не может быть функциональный тест оформлен в виде модульного теста? Для меня главное — обеспечить качество продукта. Если для вас главное — следовать слово-в-слово апологетам TDD, то нам разговаривать не о чем, я практик, а не теоретик, и мне нужно сдавать проекты в срок с надлежащим качеством и с минимальными расходами ресурсов.
100% лучше 83%, потому что как минимум я буду уверен, что при прохождении тестов на данной конфигурации большая часть кода будет работать. А иметь 17% кода, который неизвестно когда будет вызван, и когда сломается, мне совершенно не хочется, особенно когда код уже внедряется в production.
Приемочные и функциональные тесты — это тоже из области бюрократии. Разве не может быть функциональный тест оформлен в виде модульного теста? Для меня главное — обеспечить качество продукта. Если для вас главное — следовать слово-в-слово апологетам TDD, то нам разговаривать не о чем, я практик, а не теоретик, и мне нужно сдавать проекты в срок с надлежащим качеством и с минимальными расходами ресурсов.
100% лучше 83%, потому что как минимум я буду уверен, что при прохождении тестов на данной конфигурации большая часть кода будет работать. А иметь 17% кода, который неизвестно когда будет вызван, и когда сломается, мне совершенно не хочется, особенно когда код уже внедряется в production.
-7
Я очень даже практик с 5+ летней практикой тестирования.
>> «Если для вас главное — следовать слово-в-слово апологетам TDD»
Это для меня не главное, более того — вы ссылаетесь на «апологетов», которых читали давно и/или плохо: как раз сильные мира сего от ТДД в один голос и твердят, что 100% покрытие (именно в логическом его смысле, когда охватываются все мыслимые и немыслимые ситуации) не нужно :-)
Ещё более того — как практик со стажем (ведь 5 лет это стаж?) я вам могу сказать, что у меня тесты пишутся до написания тестируемых субъектов — именно это и называется TDD. То, что вы делаете — это tests after. В случае с академическим (см. «правильным») подходом — никогда не возникнет ситуации, когда покрытие тестами (согласно выбранной ранее методике) будет отличаться от 100%.
И да, в то время как вы довольствуетесь кодом, который работает «по большей части» — я просто пишу код, который работает.
>> «Если для вас главное — следовать слово-в-слово апологетам TDD»
Это для меня не главное, более того — вы ссылаетесь на «апологетов», которых читали давно и/или плохо: как раз сильные мира сего от ТДД в один голос и твердят, что 100% покрытие (именно в логическом его смысле, когда охватываются все мыслимые и немыслимые ситуации) не нужно :-)
Ещё более того — как практик со стажем (ведь 5 лет это стаж?) я вам могу сказать, что у меня тесты пишутся до написания тестируемых субъектов — именно это и называется TDD. То, что вы делаете — это tests after. В случае с академическим (см. «правильным») подходом — никогда не возникнет ситуации, когда покрытие тестами (согласно выбранной ранее методике) будет отличаться от 100%.
И да, в то время как вы довольствуетесь кодом, который работает «по большей части» — я просто пишу код, который работает.
+5
скажите, у вас баги бывают? ;)
> Разве не может быть функциональный тест оформлен в виде модульного теста
ваще как бы модульные тесты тестят юниты, заменяя все внешние вызовы в другие юниты на mock-объекты на момент теста. Это как бы идеальный вариант). Функциональные тесты вообще не парятся по поводу где какой юнит и что он там вызывает. Когда ломается фунциональный тест однозначно сказать что сломалось нельзя. Когда юнит-тест ломается — вариант только такой что сломался именно тестируемый юнит. In a nutshell :)
вы конечно можете даже через питоновский unittest в тесты засунуть даже 3d-игру. Я не понимаю что вы имеете под «оформить как».
А по поводу теста __main__ — ну какого хрена спорить-то? Самый правильный способ это протестировать — вызвать чертов этот скрипт факториала и передавать ему че надо строками и парсить ответ в виде буковок. И тест это будет приёмочный, т.е. тот что ИМИТИРУЕТ конечного пользователя. Пытаться проверить функцию __main__ самим питоном — это же ИДИОТИЗМ!
> Разве не может быть функциональный тест оформлен в виде модульного теста
ваще как бы модульные тесты тестят юниты, заменяя все внешние вызовы в другие юниты на mock-объекты на момент теста. Это как бы идеальный вариант). Функциональные тесты вообще не парятся по поводу где какой юнит и что он там вызывает. Когда ломается фунциональный тест однозначно сказать что сломалось нельзя. Когда юнит-тест ломается — вариант только такой что сломался именно тестируемый юнит. In a nutshell :)
вы конечно можете даже через питоновский unittest в тесты засунуть даже 3d-игру. Я не понимаю что вы имеете под «оформить как».
А по поводу теста __main__ — ну какого хрена спорить-то? Самый правильный способ это протестировать — вызвать чертов этот скрипт факториала и передавать ему че надо строками и парсить ответ в виде буковок. И тест это будет приёмочный, т.е. тот что ИМИТИРУЕТ конечного пользователя. Пытаться проверить функцию __main__ самим питоном — это же ИДИОТИЗМ!
+1
Абсолютно с вами согласен. Брутальное количество строк, которые были затронуты тестом — далеко не показатель качественного тестирования. Максимум, что можно твердо утверждать после 100% покрытия — что в программе нет синтаксических ошибок.
Грубо говоря, можно и вовсе написать:
Грубо говоря, можно и вовсе написать:
self.assertRaises(ValueError, factorial, -1)
self.assertRaises(TypeError, factorial, 1.25)
self.assertEqual(1, factorial(0))
покрытие будет полным, но что мы на самом деле протестировали? А ничего! Даже простой «return 1» вместо непосредственно умножения пройдет этот тест.+2
Черт, отправилось раньше, не успел «self.assertEqual(1, factorial(1))» дописать туда же в листинг.
0
Среди целочисленных типов в Python 2.6 еще есть тип long. Но на самом деле, дополнительные проверки не нужны — Python сам выдаст TypeError при работе с объектами. Я выделил отдельный случай для float и complex, т.к. для них есть метод вычисления, но он просто не реализован в данной функции.
Насчет дополнительных тестов — они не изменили бы покрытие, оно и так полное. Но даже то, что оно полное, не предотвращает ошибок, и вы правильно указали, какие тесты еще можно было написать.
Насчет дополнительных тестов — они не изменили бы покрытие, оно и так полное. Но даже то, что оно полное, не предотвращает ошибок, и вы правильно указали, какие тесты еще можно было написать.
0
Питон точно так же сам выдаст и эксепшн при float. Цель тестов — зафиксировать ожидаемое поведение кода, равно как и задокументировать это поведение.
Т.о. тест на строку — обязателен, особенно — если вы говорите о 100%.
Т.о. тест на строку — обязателен, особенно — если вы говорите о 100%.
0
лучше проверяйте isinstance(n, (float, complex))
+3
а лучше не проверяйте вообще =)
попытка сделать питон типизируемым — обречена на провал. Ибо как раз это и является особенностью подобных языков. Либо тогда уж интерфейсы творить надо, но кто ж мне будет запрещать симитировать этот чертов int своим объектом? Хочется строгой типизации — увы, надо тогда поменять python на тот же boo.
Посмотрите в библиотеки мудрых людей. Кто-нибудь проверяет isinstance(something, string) ну или что то в таком духе? Нет нет и ещё раз нет. Я должен иметь возможность засунуть в эту функцию ВСЁ что мне заблагорассудится. Если это работать не будет — это уже моя вина целиком, функция тут ну совершенно не при чём. Именно такая гибкость языка экономит профессионалам кучу времени и делает жутко нестабильными приложения у не особо крутых (пока ещё) девелоперов =)
попытка сделать питон типизируемым — обречена на провал. Ибо как раз это и является особенностью подобных языков. Либо тогда уж интерфейсы творить надо, но кто ж мне будет запрещать симитировать этот чертов int своим объектом? Хочется строгой типизации — увы, надо тогда поменять python на тот же boo.
Посмотрите в библиотеки мудрых людей. Кто-нибудь проверяет isinstance(something, string) ну или что то в таком духе? Нет нет и ещё раз нет. Я должен иметь возможность засунуть в эту функцию ВСЁ что мне заблагорассудится. Если это работать не будет — это уже моя вина целиком, функция тут ну совершенно не при чём. Именно такая гибкость языка экономит профессионалам кучу времени и делает жутко нестабильными приложения у не особо крутых (пока ещё) девелоперов =)
0
;) Я лишь указал как лучше в данном конкретном случае, не уходя в сторону.
Что же касается строгой типизации — то иногда без нее не обойтись, зависит от задачи. Хотя как правило ее прячут глубоко в иерархии кода и она больше нужна для отлова внутренних багов, внешний API лучше оставлять чистым.
Кстати, мне понравился ваш ответ, случайно работу не ищете? А то можно было бы поговорить.
Что же касается строгой типизации — то иногда без нее не обойтись, зависит от задачи. Хотя как правило ее прячут глубоко в иерархии кода и она больше нужна для отлова внутренних багов, внешний API лучше оставлять чистым.
Кстати, мне понравился ваш ответ, случайно работу не ищете? А то можно было бы поговорить.
0
Работы у меня как то никогда недостатка не было =). В данный момент кстати нахожусь в шаге ухода от своего бизнеса (ipi-manager) на работу в Москве. В общем-то и переезжаю уже вот в начале июля туда из питера ). Сложных задач в мире хватает, а я просто кипятком писаю от восторга если что то сложное попадается) Есть и обратная сторона медали — рутина меня уничтожает ;). Дон Кихот, чтож тут ещё добавить)
А вообще я всегда открыт к знакомствам и размышлениям. За чашечкой кофе)
А вообще я всегда открыт к знакомствам и размышлениям. За чашечкой кофе)
0
А на основе каких метрик делается вывод о проценте покрытия?
0
Стремиться к полному покрытию как к отдельной ценности — вредно. Обычно, 20% тестов решают 80% проблем, а остальные 80% тестов просто обеспечивают покрытие.
Не знаю как в nose, а в phpUnit есть интересный отчет — пофайловое покрытие, где в каждом файле зеленым подсвечиваются строки, которые выполнялись, а белым — которые нет. Этот отчет очень помогает находить ветви логики, которые забыли покрыть тестами, и низкое покрытие в пакете/файле — индикатор, что стоит взглянуть на этот отчет.
Не знаю как в nose, а в phpUnit есть интересный отчет — пофайловое покрытие, где в каждом файле зеленым подсвечиваются строки, которые выполнялись, а белым — которые нет. Этот отчет очень помогает находить ветви логики, которые забыли покрыть тестами, и низкое покрытие в пакете/файле — индикатор, что стоит взглянуть на этот отчет.
+1
Для факториальчиков я могу сделать такое:
pastebin.com/Zxew5kh4
(не на питоне, естественно). По ссылке — формально верифицированный факториал (три версии) на ATS, с небольшим объяснением, что и как.
Давайте что-нибудь более практичное рассмотрим?
pastebin.com/Zxew5kh4
(не на питоне, естественно). По ссылке — формально верифицированный факториал (три версии) на ATS, с небольшим объяснением, что и как.
Давайте что-нибудь более практичное рассмотрим?
-3
интересно, откуда ты взял слово «репозитарий»?
+2
Лучше бы рассказали как полезно кромсать тесты на тесты интеграции, юнит тесты приёмочные тесты… уж куда полезнее чем гнаться за покрытием.
Второй и самый гигантский минус: 100% покрытие можно сделать только для относительно простого кода. Модуль на Си? Ой покрытие вообще не проверить. Какой-нибудь сложный препроцессинг питоновских файлов? Ой и тут не проверить. Тесты у вас 1 час отрабатывают? Поздравляю, покрытие будет считаться часов 10.
Как уже выше писалось выше 20% тестов решают 80% проблем. Если есть 1 большой приёмочный тест, то на нём будет видно вообще практически все возможные касяки а-ля «typo» при вызове функций (ну если например именем кто ошибся =)). Касяк лишь в том, что при этом сообщение об ошибке может быть весьма двусмысленным.И валятся такие тесты не когда что-то одно сломалось, а когда вообще хоть что-нибудь не так. И понять «что» нереально, если только специально не начать копаться уже. А вот сообщения при ошибках самих юнит-тестов будут уже четкими и понятными и будут четко говорить что сломалось. Интеграционные тесты же тестят связки разных кусочком друг с другом (чтоб не забыть все mock-объектики изменить после смены основного объекта).
В-третьих, пока собаку на тестах не съешь, часами отлаживая казалось бы простые вещи — не будешь понимать как писать тесты. Увы, всем надо через это пройти) Это как ходить научится.
Второй и самый гигантский минус: 100% покрытие можно сделать только для относительно простого кода. Модуль на Си? Ой покрытие вообще не проверить. Какой-нибудь сложный препроцессинг питоновских файлов? Ой и тут не проверить. Тесты у вас 1 час отрабатывают? Поздравляю, покрытие будет считаться часов 10.
Как уже выше писалось выше 20% тестов решают 80% проблем. Если есть 1 большой приёмочный тест, то на нём будет видно вообще практически все возможные касяки а-ля «typo» при вызове функций (ну если например именем кто ошибся =)). Касяк лишь в том, что при этом сообщение об ошибке может быть весьма двусмысленным.И валятся такие тесты не когда что-то одно сломалось, а когда вообще хоть что-нибудь не так. И понять «что» нереально, если только специально не начать копаться уже. А вот сообщения при ошибках самих юнит-тестов будут уже четкими и понятными и будут четко говорить что сломалось. Интеграционные тесты же тестят связки разных кусочком друг с другом (чтоб не забыть все mock-объектики изменить после смены основного объекта).
В-третьих, пока собаку на тестах не съешь, часами отлаживая казалось бы простые вещи — не будешь понимать как писать тесты. Увы, всем надо через это пройти) Это как ходить научится.
+1
А, ну и да, совсем забыл =)
проверьте-ка мне 100% такое вот чудо
[a() for a in x if a is JopaClass]
покрытие будет 100% независимо от того вызывался хоть раз a() или же ни разу.
Ну или более простой касяк:
if something: do something
ещё веселее с разными жопками вроде
{
'a': a_func,
'b': b_func
}[func_name]()
вообще в любом случае тут покрытие будет 100% если код хотя бы прочитался. А вот вызвалась ли a_func или b_func — уже загадка)
проверьте-ка мне 100% такое вот чудо
[a() for a in x if a is JopaClass]
покрытие будет 100% независимо от того вызывался хоть раз a() или же ни разу.
Ну или более простой касяк:
if something: do something
ещё веселее с разными жопками вроде
{
'a': a_func,
'b': b_func
}[func_name]()
вообще в любом случае тут покрытие будет 100% если код хотя бы прочитался. А вот вызвалась ли a_func или b_func — уже загадка)
+1
> Т.к. гарантируется, что вызовется каждая строчка кода, все несоотвествия кода и API будут обнаружены
уберите этот бред из статьи — я могу к 3м выше ещё примеров 20 намастерить, когда в 1 строке кода гораздо больше 1 логической штуки.
уберите этот бред из статьи — я могу к 3м выше ещё примеров 20 намастерить, когда в 1 строке кода гораздо больше 1 логической штуки.
+1
> Полное покрытие помогает при изменении API используемых библиотек и при изменении самого языка программирования (см. пример для Python 3 далее). Т.к. гарантируется, что вызовется каждая строчка кода, все несоотвествия кода и API будут обнаружены.
ещё одна бредятина. Если (куда уж без них) использовать mock-объекты, и если модуль1 вызывает модуль2, при этом модуль2 заменятся mock-объектом, то модуль1 будет вызывать всё и вся на mock-объекте но никак не на модуле2. Поменяете модуль2 — все равно все тесты будут работать. А чтобы все действительно рассыпалось в тестах интеграционные тесты и нужны. Именно они должны отвечать за соответствие API. Сказать что за соотвествие API отвечает 100% коверейдж — бред бред и ещё раз бред.
Я бы на вашем месте вообще убрал фразы в которых встречается слово «гарантируется...» =).
ещё одна бредятина. Если (куда уж без них) использовать mock-объекты, и если модуль1 вызывает модуль2, при этом модуль2 заменятся mock-объектом, то модуль1 будет вызывать всё и вся на mock-объекте но никак не на модуле2. Поменяете модуль2 — все равно все тесты будут работать. А чтобы все действительно рассыпалось в тестах интеграционные тесты и нужны. Именно они должны отвечать за соответствие API. Сказать что за соотвествие API отвечает 100% коверейдж — бред бред и ещё раз бред.
Я бы на вашем месте вообще убрал фразы в которых встречается слово «гарантируется...» =).
+1
А ещё все так любят так любят nose. И преимущества его описывают (хотя against who?)
Но мать вашу там НЕТ распараллеливания для многоядерных машин.
Я терпеть не могу когда начинают что то советовать вообще не говоря об альтернативах. Тот же py.test.
Учитывая что тесты отнимают зачастую больше времени чем написание самого кода. И учитывая что с течением времени «волшебно» переехать с одной системы тестирования на другую крайне затруднительно, я бы например готов был бы через месяц-два использования nose и найдя другую либу которая, оказывается, покруче будет, каждый понедельник вас бы недобрым словом вспоминал а вам бы икалось. Идиотизм.
Все такие расиз**атые советчики вокруг, годик на питоне пописали и уже пишут статьи будто они прям уже коммитеры питоновские и все знают. Скажите, мол «да я просто статью написал, хочешь читай, хочешь не читай». Да, но йопт это не статья это прям учебник какой-то «так делать НАДО, а так НЕ НАДО». Есть статьи иногда, когда читаешь просто как будто беседуешь с человеком, там не грех ему спокойно объяснить что оказывается nose это далеко не единственная штука. А вам… тьфу.
Но мать вашу там НЕТ распараллеливания для многоядерных машин.
Я терпеть не могу когда начинают что то советовать вообще не говоря об альтернативах. Тот же py.test.
Учитывая что тесты отнимают зачастую больше времени чем написание самого кода. И учитывая что с течением времени «волшебно» переехать с одной системы тестирования на другую крайне затруднительно, я бы например готов был бы через месяц-два использования nose и найдя другую либу которая, оказывается, покруче будет, каждый понедельник вас бы недобрым словом вспоминал а вам бы икалось. Идиотизм.
Все такие расиз**атые советчики вокруг, годик на питоне пописали и уже пишут статьи будто они прям уже коммитеры питоновские и все знают. Скажите, мол «да я просто статью написал, хочешь читай, хочешь не читай». Да, но йопт это не статья это прям учебник какой-то «так делать НАДО, а так НЕ НАДО». Есть статьи иногда, когда читаешь просто как будто беседуешь с человеком, там не грех ему спокойно объяснить что оказывается nose это далеко не единственная штука. А вам… тьфу.
+1
Sign up to leave a comment.
Полное покрытие кода