Pull to refresh

Comments 35

спасибо. очень познавательно. правильные выводы.
а почему вы проверяете
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())
это не сделало бы покрытие полней?
Возник такой же вопрос про строки: что метрики, используемые в статье дают не совсем корректные цифры.

Более того — вся статьи базируется на одной из трактовок для «полного покрытия» как «покрытие каждого метода хотя бы одним вызовом».

С другой стороны — недостаток показателен: цифра 100% вообще ни о чём не говорит, равно как и 83%. Добавленный для бюрократии кейс — тестировал лишь вызов тестируемого кода, а не сам тестируемый код, что вообще абсурдно. Что будет дальше — тесты на тесты?
В данном случае используется метрика от coverage-модуля. Он вычисляет покрытие из расчета строчек кода — если строка кода не была вызвана в тестах, значит она не покрыта. 100% показывает, что каждая строка была вызвана в тестах хотя бы один раз.

«Вызов тестируемого кода» (как я понял, вы имеете в виду код, исполняемый для __main__) тоже должен тестироваться — некоторые скрипты вообще могут быть написаны без объявления функций и классов, и тем не менее содержать сложную логику, которую нужно протестировать. То, что это архитектурно неправильно, другой вопрос — есть legacy-код, который перед рефакторингом нужно покрыть тестами, иначе можно внести ошибку. А этот legacy-код может быть написан как угодно, и без декомпозиции на функции классы.
__main__ это клиентский код. клиентский код тестируется приёмочными и функциональными тестами, а не модульными.

«100% показывает» — с бюрократической точки зрения, да показывает. на деле остаётся миллион вариантов, которые нужно протестировать, но вы уже успокоились, ибо 100%.

Повторю свои выводы: эта цифра в 100% не значит абсолютно ничего, и тесты с 83% покрытием ничем не хуже, чем 100%. почему? да потому что при 83% можно покрыть вызовы со стандартными аргументами, граничными и ошибочными, а при 100 — передать одно значение и успокоиться. Вы в своей статье делаете неправильные акценты и манипулируете терминами, которые работают в маркетинге («В наших проектах мы уже больше года обеспечиваем стопроцентное покрытие кода»), но с точки зрения программистов — фразы бессмысленные и ничего не характеризующие.
Ах да, забыл: ваши трудности вроде «хотя и придется работать с магией Python-а» как раз и вызваны тем, что вы используете неправильные инструменты (модульные тесты, вместо функциональных).
Вот как раз вы и разводите бюрократию. Кто сказал, что __main__ — это клиентский код? Может в виртуальном мире сферических программистов в вакууме весь __main__-код и является клиентским (т.е. отвечает за взаимодействие с пользователем, как я понимаю), но в реальном мире скрипт может быть вызван через exec, и иметь всю логику именно в __main__.

Приемочные и функциональные тесты — это тоже из области бюрократии. Разве не может быть функциональный тест оформлен в виде модульного теста? Для меня главное — обеспечить качество продукта. Если для вас главное — следовать слово-в-слово апологетам TDD, то нам разговаривать не о чем, я практик, а не теоретик, и мне нужно сдавать проекты в срок с надлежащим качеством и с минимальными расходами ресурсов.

100% лучше 83%, потому что как минимум я буду уверен, что при прохождении тестов на данной конфигурации большая часть кода будет работать. А иметь 17% кода, который неизвестно когда будет вызван, и когда сломается, мне совершенно не хочется, особенно когда код уже внедряется в production.
Я очень даже практик с 5+ летней практикой тестирования.

>> «Если для вас главное — следовать слово-в-слово апологетам TDD»
Это для меня не главное, более того — вы ссылаетесь на «апологетов», которых читали давно и/или плохо: как раз сильные мира сего от ТДД в один голос и твердят, что 100% покрытие (именно в логическом его смысле, когда охватываются все мыслимые и немыслимые ситуации) не нужно :-)
Ещё более того — как практик со стажем (ведь 5 лет это стаж?) я вам могу сказать, что у меня тесты пишутся до написания тестируемых субъектов — именно это и называется TDD. То, что вы делаете — это tests after. В случае с академическим (см. «правильным») подходом — никогда не возникнет ситуации, когда покрытие тестами (согласно выбранной ранее методике) будет отличаться от 100%.

И да, в то время как вы довольствуетесь кодом, который работает «по большей части» — я просто пишу код, который работает.
Я ниже писал, опыт не заменить нравоучениями ) Я лишь против чтобы такие как он молодёжь не в то русло пихали сразу =/
Почитал, эмоциональненько :-)
скажите, у вас баги бывают? ;)

> Разве не может быть функциональный тест оформлен в виде модульного теста
ваще как бы модульные тесты тестят юниты, заменяя все внешние вызовы в другие юниты на mock-объекты на момент теста. Это как бы идеальный вариант). Функциональные тесты вообще не парятся по поводу где какой юнит и что он там вызывает. Когда ломается фунциональный тест однозначно сказать что сломалось нельзя. Когда юнит-тест ломается — вариант только такой что сломался именно тестируемый юнит. In a nutshell :)

вы конечно можете даже через питоновский unittest в тесты засунуть даже 3d-игру. Я не понимаю что вы имеете под «оформить как».

А по поводу теста __main__ — ну какого хрена спорить-то? Самый правильный способ это протестировать — вызвать чертов этот скрипт факториала и передавать ему че надо строками и парсить ответ в виде буковок. И тест это будет приёмочный, т.е. тот что ИМИТИРУЕТ конечного пользователя. Пытаться проверить функцию __main__ самим питоном — это же ИДИОТИЗМ!

Абсолютно с вами согласен. Брутальное количество строк, которые были затронуты тестом — далеко не показатель качественного тестирования. Максимум, что можно твердо утверждать после 100% покрытия — что в программе нет синтаксических ошибок.

Грубо говоря, можно и вовсе написать:
self.assertRaises(ValueError, factorial, -1)
self.assertRaises(TypeError, factorial, 1.25)
self.assertEqual(1, factorial(0))

покрытие будет полным, но что мы на самом деле протестировали? А ничего! Даже простой «return 1» вместо непосредственно умножения пройдет этот тест.
Абсолютно верно. Меня это и смущает в статье — человек с одной стороны говорит о достигаемом высоком качестве, с другой стороны не видит очевидных недочётов даже в тестах на несчастный факториал с реализацией в 7 значащих строк кода.
Черт, отправилось раньше, не успел «self.assertEqual(1, factorial(1))» дописать туда же в листинг.
Среди целочисленных типов в Python 2.6 еще есть тип long. Но на самом деле, дополнительные проверки не нужны — Python сам выдаст TypeError при работе с объектами. Я выделил отдельный случай для float и complex, т.к. для них есть метод вычисления, но он просто не реализован в данной функции.

Насчет дополнительных тестов — они не изменили бы покрытие, оно и так полное. Но даже то, что оно полное, не предотвращает ошибок, и вы правильно указали, какие тесты еще можно было написать.
Питон точно так же сам выдаст и эксепшн при float. Цель тестов — зафиксировать ожидаемое поведение кода, равно как и задокументировать это поведение.
Т.о. тест на строку — обязателен, особенно — если вы говорите о 100%.
лучше проверяйте isinstance(n, (float, complex))
а лучше не проверяйте вообще =)
попытка сделать питон типизируемым — обречена на провал. Ибо как раз это и является особенностью подобных языков. Либо тогда уж интерфейсы творить надо, но кто ж мне будет запрещать симитировать этот чертов int своим объектом? Хочется строгой типизации — увы, надо тогда поменять python на тот же boo.

Посмотрите в библиотеки мудрых людей. Кто-нибудь проверяет isinstance(something, string) ну или что то в таком духе? Нет нет и ещё раз нет. Я должен иметь возможность засунуть в эту функцию ВСЁ что мне заблагорассудится. Если это работать не будет — это уже моя вина целиком, функция тут ну совершенно не при чём. Именно такая гибкость языка экономит профессионалам кучу времени и делает жутко нестабильными приложения у не особо крутых (пока ещё) девелоперов =)
;) Я лишь указал как лучше в данном конкретном случае, не уходя в сторону.

Что же касается строгой типизации — то иногда без нее не обойтись, зависит от задачи. Хотя как правило ее прячут глубоко в иерархии кода и она больше нужна для отлова внутренних багов, внешний API лучше оставлять чистым.

Кстати, мне понравился ваш ответ, случайно работу не ищете? А то можно было бы поговорить.
Работы у меня как то никогда недостатка не было =). В данный момент кстати нахожусь в шаге ухода от своего бизнеса (ipi-manager) на работу в Москве. В общем-то и переезжаю уже вот в начале июля туда из питера ). Сложных задач в мире хватает, а я просто кипятком писаю от восторга если что то сложное попадается) Есть и обратная сторона медали — рутина меня уничтожает ;). Дон Кихот, чтож тут ещё добавить)

А вообще я всегда открыт к знакомствам и размышлениям. За чашечкой кофе)
А на основе каких метрик делается вывод о проценте покрытия?
«Каждый метод должен быть протистирован хотя бы одним кейсом»
По-моему не так, а "% строк кода, которые выполнились за время работы всех тест-кейсов".
Еще в coverage.py есть branch coverage, там немного по-другому.
Тогда пардон
На самом деле это ничего не меняет — метрика от этого не становится более говорящей.
Стремиться к полному покрытию как к отдельной ценности — вредно. Обычно, 20% тестов решают 80% проблем, а остальные 80% тестов просто обеспечивают покрытие.

Не знаю как в nose, а в phpUnit есть интересный отчет — пофайловое покрытие, где в каждом файле зеленым подсвечиваются строки, которые выполнялись, а белым — которые нет. Этот отчет очень помогает находить ветви логики, которые забыли покрыть тестами, и низкое покрытие в пакете/файле — индикатор, что стоит взглянуть на этот отчет.
Эх! Не видели вы в своей жизни DO-178B…
Счастливый Вы человек!
Для факториальчиков я могу сделать такое:

pastebin.com/Zxew5kh4

(не на питоне, естественно). По ссылке — формально верифицированный факториал (три версии) на ATS, с небольшим объяснением, что и как.

Давайте что-нибудь более практичное рассмотрим?
интересно, откуда ты взял слово «репозитарий»?
Лучше бы рассказали как полезно кромсать тесты на тесты интеграции, юнит тесты приёмочные тесты… уж куда полезнее чем гнаться за покрытием.

Второй и самый гигантский минус: 100% покрытие можно сделать только для относительно простого кода. Модуль на Си? Ой покрытие вообще не проверить. Какой-нибудь сложный препроцессинг питоновских файлов? Ой и тут не проверить. Тесты у вас 1 час отрабатывают? Поздравляю, покрытие будет считаться часов 10.

Как уже выше писалось выше 20% тестов решают 80% проблем. Если есть 1 большой приёмочный тест, то на нём будет видно вообще практически все возможные касяки а-ля «typo» при вызове функций (ну если например именем кто ошибся =)). Касяк лишь в том, что при этом сообщение об ошибке может быть весьма двусмысленным.И валятся такие тесты не когда что-то одно сломалось, а когда вообще хоть что-нибудь не так. И понять «что» нереально, если только специально не начать копаться уже. А вот сообщения при ошибках самих юнит-тестов будут уже четкими и понятными и будут четко говорить что сломалось. Интеграционные тесты же тестят связки разных кусочком друг с другом (чтоб не забыть все mock-объектики изменить после смены основного объекта).

В-третьих, пока собаку на тестах не съешь, часами отлаживая казалось бы простые вещи — не будешь понимать как писать тесты. Увы, всем надо через это пройти) Это как ходить научится.
А, ну и да, совсем забыл =)
проверьте-ка мне 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 — уже загадка)
> Т.к. гарантируется, что вызовется каждая строчка кода, все несоотвествия кода и API будут обнаружены
уберите этот бред из статьи — я могу к 3м выше ещё примеров 20 намастерить, когда в 1 строке кода гораздо больше 1 логической штуки.
> Полное покрытие помогает при изменении API используемых библиотек и при изменении самого языка программирования (см. пример для Python 3 далее). Т.к. гарантируется, что вызовется каждая строчка кода, все несоотвествия кода и API будут обнаружены.
ещё одна бредятина. Если (куда уж без них) использовать mock-объекты, и если модуль1 вызывает модуль2, при этом модуль2 заменятся mock-объектом, то модуль1 будет вызывать всё и вся на mock-объекте но никак не на модуле2. Поменяете модуль2 — все равно все тесты будут работать. А чтобы все действительно рассыпалось в тестах интеграционные тесты и нужны. Именно они должны отвечать за соответствие API. Сказать что за соотвествие API отвечает 100% коверейдж — бред бред и ещё раз бред.

Я бы на вашем месте вообще убрал фразы в которых встречается слово «гарантируется...» =).
Да йомайо а покрытие с метаклассами вообще бывает такое диковатое… Там и вовсе зачастую 100% не получить в принципе)
А ещё все так любят так любят nose. И преимущества его описывают (хотя against who?)
Но мать вашу там НЕТ распараллеливания для многоядерных машин.

Я терпеть не могу когда начинают что то советовать вообще не говоря об альтернативах. Тот же py.test.
Учитывая что тесты отнимают зачастую больше времени чем написание самого кода. И учитывая что с течением времени «волшебно» переехать с одной системы тестирования на другую крайне затруднительно, я бы например готов был бы через месяц-два использования nose и найдя другую либу которая, оказывается, покруче будет, каждый понедельник вас бы недобрым словом вспоминал а вам бы икалось. Идиотизм.

Все такие расиз**атые советчики вокруг, годик на питоне пописали и уже пишут статьи будто они прям уже коммитеры питоновские и все знают. Скажите, мол «да я просто статью написал, хочешь читай, хочешь не читай». Да, но йопт это не статья это прям учебник какой-то «так делать НАДО, а так НЕ НАДО». Есть статьи иногда, когда читаешь просто как будто беседуешь с человеком, там не грех ему спокойно объяснить что оказывается nose это далеко не единственная штука. А вам… тьфу.
Sign up to leave a comment.

Articles