Как стать автором
Обновить

Комментарии 401

>Статья получилась большой, и я надеюсь, что старался не зря.
Тема на самом деле очень интересная. И хотя выводы не новые, и достаточно очевидно, что платите вы за разработку тестов сегодня, а профит возможно получите потом, но я видел с десяток наверное статей прямо тут, где ультимативно утверждается: «Вы обязаны писать тесты». Ну то есть, очевидно это далеко не всем.

Есть еще несколько аспектов, на которых может быть стоило бы акцентировать внимание:

— вопрос не стоит упрощать до «писать или не писать юнит тесты». Правильная постановка ближе к чему-то такому: «Какой уровень надежности нам нужен, и как лучше его добиться?». То есть, следует рассмотреть разные способы повышения надежности. Тесты — не единственный.
— кстати, а измеряли ли ли мы надежность на сегодня? Мы уверены, что она недостаточная? Умеем ли мы вообще ее мерять?
— тесты не гарантируют отсутствие багов. Поэтому ответ на вопрос: «Стоит ли Василию обеспокоиться написанием юнит-тестов?» вполне может быть иным: нет, не стоит, все равно надежность от этого не вырастет, потому что Василий и тесты писать не умеет тоже. А нужно лучше поискать опытного консультанта, и провести допустим code review.

Ну или даже еще проще и короче: у нас есть команда, и ее возможности ограничены (финансовый аспект, что вы рассмотрели, вообще говоря не единственный, время, как правило, вообще не купишь). В зависимости от вида проекта, квалификации людей в команде и пр. факторов, мы можем потратить деньги и время команды на разные вещи. И не факт, что именно на тесты.
Всё это так, но если вы хотите получить какие-то осмысленные результаты, то нужны какие-то вменяемые исходные данные. Если вы рассматриваете вариант, когда у вас надёжность в компании с «бесконтрольным наймом сотрудников» и без написания юниттестов получается на уровне самых-самых лучших компаний, которые вкладывают в тестирование миллиарды… то тут вряд ли можно говорить о чём-то ином, чем подгонка исходных данных под ответ.
Конечно данные нужны. Я там не зря написал про «умеете ли вы ее мерять?».
— тесты гарантируют, что если я вот прямо сейчас захочу наговнокодить побыстрому рядышком пару k LOC, то завтра все не упадет у всех

Тесты убирают страх написания кода в большом и сложном проекте, коим любой проект становится через неделю написания кода тремя человеками

И у вас нет ресурсов через неделю после начала проекта согласовывать кто и что будет писать в проекте сегодня
Потому Василию стоит обеспокоится написанием тестов сразу, кроме случаев, когда он пишет сам и сразу в /dev/null
Тесты убирают страх написания кода в большом и сложном проекте, коим любой проект становится через неделю написания кода тремя человеками
А может быть не стоит писать код одновременно тремя человеками неделю? А стоит, эта, разбить его на подзадачи?

И у вас нет ресурсов через неделю после начала проекта согласовывать кто и что будет писать в проекте сегодня
Нафига вы вообще посадили на проект, в котором неясно что делать вообще трёх человек?

Не разделяя идею бессмысленности юнит-тестов в принципе я, всё же, склонен согласиться с тем, что часто их пытаются использовать как замену пониманию: нам не нужно знать что делает этот код — у нас же есть юниттесты!

Так вот: это работает плохо. Написание кода тремя людьми в шесть рук — приводит код, достаточно быстро, в состояние, когда никакого понятия о том, что этот код делает — нет вообще ни у кого. И после этого его править становится сложно. А править баги — ещё сложнее…
Как разбивать на подзадачи так, чтоб они никогда не пересекались?

Как разбивать на подзадачи, так чтоб один разраб всегда отвечал только за тот код, который он написал?
Как разбивать на подзадачи так, чтоб они никогда не пересекались?
А кто сказал, что они не должны пересекаться?

Как разбивать на подзадачи, так чтоб один разраб всегда отвечал только за тот код, который он написал?
Разраб не должен отвечать «только за тот код, который он написал». Он отвечает за тот код, который он принял.

А кто его написал — дело десятое. Но у любого компонента должен быть владелец (MAINTAINER или OWNER — в разных проектах по разному), который за него отвечает. И пока он ваш код не «примет» — вы его, в этом компоненте, не увидите.
А кто сказал, что они не должны пересекаться?
вы

Он отвечает за тот код, который он принял.
Как принять 100k LOC?
В таком случае обычно предполагается что на проекте есть условный «тимлид», который сам редко пишет код и занимается в основном интеграциями и планированием.
К сожалению, во многих проектах тимлид слишком занят пилением фич и бесполезными митингами, чтобы быть нормальным owner'ом

ПС ну и патч на 100k LOC это уж слишком, надо разбивать на подзадачи
И как тимлиду принять в одно лицо столько кода?

И еще: как делать рефакторинг без юнит тестов?
Еще раз повторяю — тимлид должен сопровождать изменения на 100к строк, пока эти строки пишутся. Не должно быть ситуации с «неожиданной» кучей кода, которую никто кроме писавшего, не понимает.
Ну и никто не говорит что юнит тестов быть не должно. Речь о том что не должно быть хаоса и надежды на юнит тесты, как на единственное спасение.
Что значит
Не должно быть ситуации с «неожиданной» кучей кода, которую никто кроме писавшего, не понимает.
?

Ето регулярная ситуация при подключении любой либы

Речь о том что не должно быть хаоса и надежды на юнит тесты, как на единственное спасение.
а на что тогда полагаться?
Ето регулярная ситуация при подключении любой либы
А для ядра операционки вы тоже юниттесты пишите? А для Minix'а в ME?

Сторонний код — есть всегда. Либо у него есть кто-то, кто за него отвечает (и имеющиеся там сотни тысяч или миллионы строк вас не касаются), либо вы просто его не используете.

а на что тогда полагаться?
На людей, очевидно. Либо у вас есть люди, знающие что именно они делают, либо нет. В первом случае юниттесты — не так важны, во втором — они вам не помогут.
Сторонний код без тестов используется в крайнем случае

Люди допускают ошибки

На что полагаться?
Сторонний код без тестов используется в крайнем случае
И чего вы этим добиваетесь? Увеличения цены разработки? Есть много других способов.

На что полагаться?
На людей, однако.

Люди допускают ошибки.
Люди также исправляют ошибки — на что юниттесты в принципе неспособны.

Потому если вас устраивает некоторая вероятность ошибки — вам можно полагаться на людей. Если не устраивает — нужны формальные доказательства, за которые так топит 0xd34df00d.

Юнит-тесты же могут лишь являться костылями (хотя, в некоторых случаях полезными, спору нет) в первом случае и не нужны в принципе во втором.
Люди допускают ошибки

Да, а тесты пишут тоже люди. Их наличие никак не гарантирует отсутствие ошибок. Более того, их наличие не так уж существенно снижает вероятность ошибок, т.к. в общем случае тестами в первую очередь покрывается тривиальное поведение, которое и без тестов прекрасно проходит валидацию в ручном режиме. А вот сложные кейсы обычно тестами и не покрываются.
Те вместо написания кода разработчики будут делать ручное тестирование при каждом изменение кода?

А самые дорогостоящие разработчики еще и будут заниматься ручным тестирование чужого кода?

Я наконец-то понял вашу позицию
Те вместо написания кода разработчики будут делать ручное тестирование при каждом изменение кода?

А вам не приходило в голову, что в 99% проектов не нужно делать тестирование всего и вся при каждом изменении кода? Что нормально написанный код не приводит к тому, что изменение функции А ломает функции С, D и Z? И что с другой стороны, если вы меняете алгоритм функции А, то вы должны и тест под неё переписать, а значит, дорогостоящий разработчик будет делать куда больше рутинной работы, чем просто взять и раз эту функцию прогнать вручную.
И что ошибка в нижележащем коде обычно находится сразу по результатам UI-теста, без всяких юнит-тестов, вы тоже не слыхали?
А самые дорогостоящие разработчики еще и будут заниматься ручным тестирование чужого кода?

Вы издеваетесь? Зачем тестировать чужой код вообще? У вас есть библиотека, есть отзывы о её применении. Что вы там тестировать собрались? Опыт её эксплуатации — единственный более-менее достоверный тест. Чужие тесты вам никакой гарантии не дадут, а собственные — дурацкая трата времени и денег.
Как принять 100k LOC?
Попросить того, кто «это» сотворил разбить на кусочки в 100-200 строк и в каждом из них описать — что он делает и зачем.
Хм
Как попросить гугл (или любую корпорацию) сделать для их либ
разбить на кусочки в 100-200 строк и в каждом из них описать — что он делает и зачем
?
Я прошу прощения, а вы, подключая либы от сторонних вендоров, делаете и повторно работу за этих вендоров — рефакторите их код, обвешиваете тестами?
Не подключаю если там нет нормального покрытия тестами
Это, честно говоря, больше похоже на некий фанатизм, чем на осознанное решение.
Нет
Да. Выходит, что здравые основания по выбору библиотеки, т.е. её полезность для вашего проекта, её известность и распространённость могут быть перевешены второстепенным фактором, представляющим собой лишь иллюзию надежности. Ведь вы, с одной стороны, понятия не имеете, насколько тесты написаны качественно, как они покрывают код. Тесты нашлёпаны, формальности соблюдены — можно брать. С другой стороны, отсекаете массу качественных и хороших продуктов, разработчики которых не пишут тесты.
А чем может быть полезна дырявая библиотека?

И опять же.
Вы так топите за ручную проверку кода людьми и при етом не допускаете, что тесты также можно просмотреть и оценить?

Странно, странно
Вы так топите за ручную проверку кода людьми и при етом не допускаете, что тесты также можно просмотреть и оценить?

Я не допускаю, что адекватный, а не пустивший всё на самотёк, заказчик софта будет готов платить деньги команде, которая тратит их на подобную хрень. Давайте ещё тестеров для тестеров нанимать. Чего уж там, раз решили подробно выверять юнит-тесты сторонних библиотек, то вполне логичное продолжение.
тесты гарантируют, что если я вот прямо сейчас захочу наговнокодить побыстрому рядышком пару k LOC, то завтра все не упадет у всех

Нет, они этого не гарантируют ни формально, ни эмпирически.


Тесты убирают страх написания кода в большом и сложном проекте, коим любой проект становится через неделю написания кода тремя человеками

И снова далеко не обязательно.


То есть, понятно, что сферические правильно написанные правильной командой в правильном вакууме тесты действительно помогают, но это совсем другой разговор, и при таких вводных можно рассмотреть и другие способы обеспечения надёжности.

Нет, они этого не гарантируют ни формально, ни эмпирически.
почему проверка функционирования кода не гарантирует его работоспособность?

при таких вводных можно рассмотреть и другие способы обеспечения надёжности.
какие?
почему проверка функционирования кода не гарантирует его работоспособность?

Потому что проверяются только конкретные частные случаи. И формальных доказательств достаточности покрытия тестов я не видел ни разу.


какие?

То же использование систем формальной верификации.

Потому что проверяются только конкретные частные случаи. .
да, проверяется как код должен работать.
Так как тест, проверяющий корректную работу кода не гарантирует, что код будет работать так как он задуман?

То же использование систем формальной верификации.
Что сие есть?
Как оно делается?
Как оно гарантирует корректность?
Так как тест, проверяющий корректную работу кода не гарантирует, что код будет работать так как он задуман?

Он не проверяет, что код будет работать так, как нужно, на всём возможном пространстве входов.


Даже если вы проверили, что sort [1, 3, 2] == [1, 2, 3], то вы всё равно упустили случаи вроде


  1. пустого списка,
  2. списка с повторяющимися элементами,
  3. списка с элементами, на которых нет линейного порядка (IEEE754, например),
  4. списка не из чисел.

Что сие есть?

Это вещи от всяких TLA+ (коих я большой не фанат) до языков типа Idris (коих я, наоборот, большой фанат) или Coq.


Как оно делается?

Зависит от конкретного инструмента. В случае того же идриса или кока — пишется код, носящий с собой доказательство корректности в своих типах.


Как оно гарантирует корректность?

Математически.

Он не проверяет, что код будет работать так, как нужно, на всём возможном пространстве входов
что не является задачей юнит тестов, а задачей регресионных тестов

Даже если вы проверили, что
такие юнит-тесты никто не пишет

Зависит от конкретного инструмента. В случае того же идриса или кока — пишется код, носящий с собой доказательство корректности в своих типах.
те написание тестов с помощью того инструмента, который вам нравится, ок
То что не нравится неок

Хммм
что не является задачей юнит тестов, а задачей регресионных тестов

Не понял. А зачем тогда юнит-тесты нужны?


такие юнит-тесты никто не пишет

Это упрощённый пример, призванный проиллюстрировать, почему наличие тестов ничего не гарантирует.


Как вы напишете тесты на функцию сортировки? На какие-нибудь деревья?


те написание тестов с помощью того инструмента, который вам нравится

Нет, это не написание теста. В тесте вы принципиально не можете использовать квантор всеобщности, в тесте вы проверяете только квантор существования.

Не понял. А зачем тогда юнит-тесты нужны?

да, проверяется как код должен работать.


Как вы напишете тесты на функцию сортировки? На какие-нибудь деревья?
а зачем тестировать, то что уже покрыто тестами?

В тесте вы принципиально не можете использовать квантор всеобщности
потому что в реальности у вас нет возможности пройтись по всем значениям в любом случае

Кстати, кто и где использует ваши инструменты для покрытия тестами?
да, проверяется как код должен работать.

Он не проверяет, что код будет работать так, как нужно, на всём возможном пространстве входов

Так что он проверяет-то?


а зачем тестировать, то что уже покрыто тестами?

Мне казалось очевидным, что разговор о той функции сортировки или какому-нибудь алгоритму на графе, который вы сами только что написали.


потому что в реальности у вас нет возможности пройтись по всем значениям в любом случае

Именно!


Но по всем значениям и не надо проходить, если вы используете упомянутые средства формальной верификации. Вы же в математике когда теоремы со значком ∀ доказываете, вы не проверяете вручную все интересующее вас множество, по которому он квантифицирует?


Кстати, кто и где использует ваши инструменты для покрытия тестами?

Для покрытия тестами — нигде, потому что это не тесты.

Те опять вкусовщина — один инструмент у вас дает гарантии, другой нет

Кстати про математику — что там с автоматическим доказательством теорем?
Те опять вкусовщина — один инструмент у вас дает гарантии, другой нет

Ага, математические гарантии. Непонятно, правда, почему вкусовщина, если дело не в конкретных инструментах, а в классах инструментов.


Вкусовщиной бы было, если бы я тут топил за Idris или Agda и против Coq, ибо стили доказательств в идрисне мне больше во вкусу, чем (ИМХО) нечитаемые proof script'ы из вызовов тактик в Coq.


Кстати про математику — что там с автоматическим доказательством теорем?

Неразрешимо в смысле Тьюринга. Что не мешает иметь разрешимый тайпчекинг для достаточно мощных систем типов, что даёт автоматические проверки доказательств теорем.


И полезные эвристики, дающие доказательство для ряда практически полезных случаев, тоже не мешает иметь.

Не в курсе про автопроверку доказательств
Подскажите материалы по данному поводу?

Смотря что конкретно вы хотите узнать.


Если говорить о метатеории, то доказательство разрешимости тайпчекинга в Calculus of Constructions (и Calculus of Inductive Constructions), который позволяет формализовать (почти) всю математику, и разрешимость которого является основанием для заявлений об автопроверке доказательств, есть, но конкретные ссылки мне придётся поискать.


О том, как это вообще выглядит и работает, можно почитать на википедии про всякие пруф ассистанты, или в книге Type theory and Formal Proof (хорошее введение для начинающих без серьёзных метатеоретических изысканий), или в книге Certified programming with dependent types, или в книге Type-driven development with Idris.

Мне нужна статья про
даёт автоматические проверки доказательств теорем

Вторая глава книги «Advanced Topics in Types and Programming Languages» целиком про доказательство разрешимости, рекомендую начать с неё, плюс там неплохая библиография. Плюс вот это. Чтобы понять, какое отношение вся эта ерунда имеет к математике, можно начать с упомянутой выше книги Type Theory and Formal Proof либо даже со статьи в вики, если книга — слишком сложно. Если ATTAPL тоже сложно, то можно открыть другую статью на вики про CoC, увидеть там строчку «The CoC is strongly normalizing» и сделать выводы.


Надеюсь, это вас устроит.

Вполне устроит
Спасибо

The CoC is strongly normalizing


although, by Gödel's incompleteness theorem, it is impossible to prove this property within the CoC since it implies inconsistency.


На практике проблем это не приносит.


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

Меня больше смутил простой пример
The proof of commutativity of addition on natural numbers

Там какая-то ерунда понаписана. Я не так давно развлекался с числами, можно ИМХО проще и яснее:


plusRightZero : (n : Nat) -> n = n + Z
plusRightZero Z = Refl
plusRightZero (S n) = cong $ plusRightZero n

plusRightS : (n, m : Nat) -> n + S m = S (n + m)
plusRightS Z m = Refl
plusRightS (S n) m = cong $ plusRightS n m

plusCommutes : (n, m : Nat) -> n + m = m + n
plusCommutes Z m = plusRightZero m
plusCommutes (S n) m = rewrite plusCommutes n m in sym $ plusRightS m n

Здесь аж три теоремки, последняя опирается на две предыдущих, как всегда в математике. Так что само доказательство искомой теоремы занимает буквально две строки.

Юнит-тесты, ставшие однажды зелёными, становятся регрессионными тестами. Это их основная задача обычно. Иначе нет смысла хранить их в кодовой базе.

— тесты гарантируют, что если я вот прямо сейчас захочу наговнокодить побыстрому рядышком пару k LOC, то завтра все не упадет у всех

Тесты гарантируют лишь то, что если вы захотите наговнокодить пару kLOC, по-быстрому вы это сделать уже никак не сможете :)
И они как-то снизят возможность падения, в зависимости от своего качества. Причем тесты, сделанные для говнокода по той же методике разработки, и работать будут так же.
Изначальный посыл статьи верный: тесты должны быть лишь в том случае, если их разработка дешевле, чем ожидаемые последствия от поиска багов.
Тесты гарантируют лишь то, что если вы захотите наговнокодить пару kLOC, по-быстрому вы это сделать уже никак не сможете :)
почему нет?

Изначальный посыл статьи верный
Я не знаю изначальный замысел статьи
Как я вижу, статья пытается, исходя из вымышленных препосылок, что-то утверждать
почему нет?

Да потому что вы тесты писать будете. Они же не сами сверху падают, как манна небесная. Это ваше рабочее время. Вы можете реализовывать фичи, а можете писать тесты.
Как я вижу, статья пытается, исходя из вымышленных препосылок, что-то утверждать

Посыл статьи — сначала считайте, нужны вам тесты или нет. И есть достаточно высокая вероятность, что арифметика покажет, что не нужны. Ничего хитрого, всё очевидно. Так оно и есть, экономически для множества (и пожалуй, я не ошибусь, если скажу, что даже для большинства) проектов тесты не выгодны, и их создание не окупается никогда.
Вы можете реализовывать фичи, а можете писать тесты.
А кто подтвержит, что фича написана правильно?

Посыл статьи — сначала считайте, нужны вам тесты или нет.
в статье нет основы для таких расчетов. Потому как посчитать — также не ясно
С економической точки зрения дешевле передать всю разработку на аутсорс в Индию
А кто подтвержит, что фича написана правильно?

В 90% проектов с этим успешно справляется команда тестировщиков. Схема «провели UI-тесты и подтвердили, что всё нижележащее отработало согласно ТЗ» вполне жизнеспособна, и за исключением ряда специфических продуктов обеспечивает достаточный уровень качества.
в статье нет основы для таких расчетов. Потому как посчитать — также не ясно

Эмпирическое соотношение давно известно и не является секретом. Умножайте трудозатраты на разработку ровно в два раза, и получите примерный эстимейт для кода, хорошо покрытого тестами.
Давайте отставим функциональные тесты в сторону — будем считать, что они есть, покрывают все ТЗ и ручное тестирование сведено к абсолютному минимуму

И нет
Емпирика также никого не интересует в расчетах, где есть деньги

Я уже не говорю про то, что трудозатраты посчитать крайне сложно

И вернемся к основному вопросу
Как делать частичный рефакторинг, когда у вас есть только функциональные тесты?
И нет
Емпирика также никого не интересует в расчетах, где есть деньги

Оценивая себестоимость разработки ПО, у вас нет вообще никаких данных, кроме эмпирических оценок на основании предыдущего опыта. Вся наша многомиллиардная индустрия работает только так, и никак иначе. А вы говорите «не интересует» :)
Как делать частичный рефакторинг, когда у вас есть только функциональные тесты?

Я не понимаю смысла этого вопроса. Берете и делаете. Вам тесты для выполнения рефакторинга не нужны. Они играют лишь вспомогательную роль, упрощая локализацию возможных ошибок при рефакторинге. У вас есть какие-то сомнения, что получится провести успешный рефакторинг без автоматизированной локализации возможных ошибок?
Как берете и делаете?
Конкретнее
Так некуда уже конкретнее. Возьмите любой известный вам пример рефакторинга, это именно то, что я имею в виду.
Чем дальше, тем меньше конкретики
Жаль
но я видел с десяток наверное статей прямо тут, где ультимативно утверждается: «Вы обязаны писать тесты».

Большинство тех кто пишет такие статьи не покупает разработку, а получает с неё деньги
Да, разумеется. Но заметьте, даже в этом случае, будучи на стороне разработки, я бы все равно подумал, на что мне стоит потратить свое время и чужие деньги — на написание тестов, или на новую функциональность.

По идее не разработчик должен об этом думать, а его менеджер. Качество или скорость выбрать и донести свой выбор до разработчиков.

Я не про должность тут, а скорее про то, что команде разработки, даже если ей оплачивается заказчиком написание и тестов тоже, может быть выгоднее писать новую функциональность. Просто потому, что она виднее, и лучше продается, чем надежность.

Наверное, если заказчик платит за тесты, то надежность и качество для него не на последнем месте.

Я бы на его месте платил за показатели, которые можно померять. А уж как разработчики их достигли — зачем заказчику в это вникать?

Число строк/методов/классов/файлов? Отработанные часы? :)


Принесённый доход/прибыль?

Не обязательно, кстати. Могут быть и другие варианты:
— Заказчик не слишком компетентный в особенностях разработки, и ему просто продали разработку тестов.
— Заказчик не знает, что он платит за тесты. Стоимость их разработки просто тихой сапой включена в общий эстимейт.
И то, и другое встречается нередко.
Хорошая статья, показывающая что есть ложь, есть наглая ложь, а есть статистика.

Рассмотрим первый случай. Вероятность сбоя возьмем равной 33%, то есть инцидент, способный завалить их сайт надолго может произойти примерно раз в три года, что не так уж плохо.

Один инцидент раз в три года? Ну это примерно частота, с которой падают такие сервисы, как GMail или YouTube. Сервисы типа Steam или там Deutsche Bank — падают в разы чаще. И, разумеется, все эти компании вбухивают хорошие деньги в свои сервисы и в юниттестирование.

Сказать, что вы получаете вероятность сбоя за год 33% (без юниттестирование, напоминаю) — это примерно как рассмотреть вариант «мы наняли 500 водителей-такжиков по объявлению, ну а они, конечно, без тренировок и дополнительных затрат ездят чуть получше, чем Шумахер… ну… большинство из них».

То есть посыл статьи прост: если вы наняли супергениев, которые, по сравнению со средними программистами Гугла и Яндекса — как Эйнштейн по сравнению с трёхлетним ребёнком (работники Гугла — тут трёхлетним дети, это если вдруг, кто не понял)… то да, вам, видимо, юниттестирование не нужно.

Вот только… где вы берёте супергениев, которые без всяких тестов дают такую же надёжность, как у лучших компаний в мире со всякими многоуровневыми тестами, SRE и прочим… секретом не поделитесь?

Без такого «секретного места» ценность статьи, извините, близка у нулю.
Ну статья всего лишь о том, что перед тем как что-то делать стоит это попытаться просчитать… как мне кажеться — это разьяснение программистам как мыслят их заказчики и директора…
Ну если их «заказчики и деректора» верят в то, что можно достичь уровня «пяти девяток» (а час в три года — это очень близко) без очень существенных затрат — то дальнейшие разговоры будут весьма непростыми. И вообще неясно — стоит ли их вести.
без очень существенных затрат

Нормальные — не верят.Но таки хотят цифр, выраженных в денежных знаках…
Кстати, почему вы думаете, что youtube падает раз в три года? Скажем для меня, как для пользователя, его надежность выглядит совсем не так — примерно раз в неделю я наблюдаю, как отдельные видео не грузятся, и вместо них лишь сообщение об ошибке на черном фоне. Так что я бы не преувеличивал надежность гугловских сервисов.
Так что я бы не преувеличивал надежность гугловских сервисов.
Вот только по статистике — они надёжнее Netflix'а и многих других.

Кстати, почему вы думаете, что youtube падает раз в три года?
Можете посмотреть хотя бы по газетным заголовкам.

Скажем для меня, как для пользователя, его надежность выглядит совсем не так — примерно раз в неделю я наблюдаю, как отдельные видео не грузятся, и вместо них лишь сообщение об ошибке на черном фоне.
Ну это вообще для подобных сервисов норма. Речь не идёт о выпадании чего-то для отдельных посетителей же в вашем сообщении. А о полном падении сервиса.
Можете посмотреть хотя бы по газетным заголовкам.

Когда раз в два дня какое-нибудь видео зависает — в газетах о таком рядовом событии не пишут.

Если бы вы читали статью, то заметили бы, что речь идёт не о «зависающих видео, а о, цитирую:
инцидент, способный завалить их сайт надолго
К YouTube применяем те же критерии.
НЛО прилетело и опубликовало эту надпись здесь
Обратите внимание, в примере речь идет о крупной компании с выручкой 25 — 50 миллиардов. Разумеется, у нее есть и тестировщики и грамотная стратегия для деплоя и автоматизированные тесты помимо юнит тестов.

Кроме того, ничто не мешает вам попробовать подставить в модель свои данные и это здорово, потому что мы уже переходим с уровня верю — не верю к конкретным показателям.
Разумеется, у нее есть и тестировщики и грамотная стратегия для деплоя и автоматизированные тесты помимо юнит тестов.
В таком случае нужно сравнивать не с затратами времени на юниттесты, а с затратами времени на другие виды тестов. Если у вас 30% времени тратится на униттесты и ещё 20% на функциональные, но функциональные покрывают 90% проблем, а юниттесты 99% — то эффект от юниттестов, тем не менее, гораздо выше, чем от функциональных. И лучше убрать как раз функциональные, которые, вроде как, дешевле.

Писать нужно те тесты, которые больше подходят для компонента, который разрабатываете.

В сложных системах код отдельных фрагментов, которые могут быть покрыты юнит-тестами, местами достаточно примитивен. Поэтому юнит-тесты вырождаются до набора моков, проверяющих, что "А дёргает Б, после чего дергается В и Г" (я эту мысль пытался раскрыть в "Unit-тестирование в сложных приложениях"). Так что лучше как-раз таки оставить функциональные тесты, т.к. юнит-тесты не выявляют имеющихся проблем, а только свидетельствуют о том, что проверяемый фрагмент кода работает так, как думает, что так и должен работать этот фрагмент, программист, его написавший (это особенно характерно для TDD). Т.е., 99% покрытие кода юнит-тестами не говорит о том, что код работает правильно, а всего лишь о том, что тесты обнаружат изменения в покрытом коде, если он будет изменён без соответствующего изменения тестов.


Интеграционные и функциональные тесты более сложны для написания, но они выгоднее, чем юнит-тесты, т.к. проверяют (должны проверять) сценарии, критические для приложения, и их соответствие заданным требованиям.

А как делать рефакторинг без юнит-тестов?

Изменяете код и проверяете, что он отрабатывает так, как вы ожидаете. Если в вашем проекте вы не можете делать это без юнит-тестов, то рефакторьте с юнит-тестами. Если можете проверить без юнит-тестов — рефакторьте без.

Вы же понимает, что ето не ответ?

Вы же понимаете, что до появления юнит-тестов рефакторинг как-то делали?

Как?

Проверяя работоспособность программы вручную, разумеется.


Мы таким как раз недавно занимались на унаследованном проекте. Примерно полгода занял рефакторинг, еще месяц — слияние изменений, и еще месяц — проверка. Но это не точные цифры, потому что параллельно две задачи решались.

Сочувствую

Сочувствие требовалось бы, если бы так и продолжили поддерживать тот проект без рефакторинга...

В этой статье написано:


Первое, что придется сделать для обеспечения качества, это забрать функцию контроля у разработчиков и нанять человека, который будет за него отвечать.

По мере разрастания проекта, времени на ручное тестирование будет оставаться все меньше и меньше…

Отсюда логически вытекает необходимость автоматизировать работу тестировщиков…

В таких случаях приходится частично вернуть разработчикам функцию контроля качества и попросить их написать интеграционные тесты.

а затем змея начинает кусать свой хвост:


На практике чаще всего написанием юнит-тестов для собственного кода занимается сам разработчик.

Т.е., мы приходим к тому, от чего уходили — разработчик пишет проверочные тесты для того, что сам разрабатывает. Причём не проверку совмещения отдельных частей в единое целое (интеграционные), а проверяет каждую букву своей пьесы (юнит). При этом кол-во проверок равно кол-ву букв, а размерность алфавита не ограничена. Даже если разработчик неверно написал букву, он не сможет проверить её неправильность тестом, т.к. и тест он напишет неправильно. Он просто неправильно представляет себе, как это должно работать.


Функциональный тест может помочь выявить проблему, если проверка пишется по заданной спецификации. Но юнит-тесты должны проверять только то, что отдельный метод работает так, как задумал его работу программист. И если программист представляет себе неверно функции отдельного метода, то и юнит-тест для него он также напишет неверно.


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


До появления юнит-тестов рефакторинг делали невзирая на наличие тестов, довольствуясь ручной проверкой, затем проверку стали автоматизировать, затем появились функциональные и интеграционные тесты, а потом решили получить 100% code coverage и вышли на unit-тесты.


Простой вопрос, считаете ли вы, что полноценное юнит-тестирование должно на 100% покрывать код проекта?


Если вы не гонитесь за 100% code coverage unit-тестами, то мы с вами, скорее всего, имеем в виду одно и то же под разными названиями (вы называете это юнит-тестами, я — функциональными тестами).

Проблемы высокоуровневых тестов основных три:


  • сложно задать нужное состояние системы, чтобы протестировать конкретное управляющее воздействие
  • сложно проверить, что у системы нужное состояние после тестируемого управляющего воздействия
  • они медленные

Тем не менее, они что-то проверяют, в отличие от. А что проверяют юнит-тесты?

Юз-кейсы конкретного модуля.

Модуль в данном контексте это что — функция, класс, скрипт, набор скриптов? Как задаётся нужное состояние модуля, чтобы протестировать конкретное управляющее воздействие?

В зависимости от языка и архитектура понятия модуля могут сильно меняться. Насколько я знаю общая практика для ООП-языков: модуль — класс, тестируется вызов одного метода. Создаём инстанс класса, приводим его к нужному состоянию различными способами, вызываем метод, проверяем состояние.

Возможно ли функциональное тестирование отдельного класса? Если да, то чем функциональное тестирование отдельного класса отличается от юнит-тестирования отдельного класса?

По сути юнит-тестирование является подвидом функционального тестирования, если делить всё тестирование на функциональное и нефункциональное. Просто так исторически сложилось, что функциональным тестированием называется высокоуровневое тестирование функциональности всей системы "в сборе" или близко к тому.

Вот я и считаю, что достаточно функционального тестирования для проверки работоспособности модуля/приложения. Юнит-тестирование избыточно. Оно создаёт ложное ощущение контроля ситуации, не предоставляя таковой по сути. Нам не нужно проверять всё, нам нужно проверять только то, что действительно важно, на что есть спецификация (требования).

Юнит-тесты по сути формализованные требования к модулю. Нам не нужно проверять, что модуль соответствует требованиям к модулю?

Кто формализует требования к модулю в виде юнит-тестов? Разработчик, который пишет сам модуль? TDD подразумевает, что да. Если разработчик неправильно понимает, что должен делать его модуль, он создаст неправильный тест для которого напишет неправильный код. В результате, неправильный юнит-тест отработает правильно и покажет, что неправильный код работает, как и задумывалось. При рефакторинге придётся переделывать и код, и юнит-тест к нему.


Вы выше написали, что юнит-тестирование — подвид функционального. Если требования к модулю, которые проверяет юнит-тест, пишутся не самим программистом в качестве среды для разработки кода модуля, а являются следствием преобразованния функциональных требований, такое юнит-тестирование (как подвид функционального) я считаю правильным и достойным существования. Как только юнит-тестирование превращается в создание среды для разработки отдельного модуля, то тут уже начинаются фантазии разработчика.


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


Я сказал, что мог, и больше не могу ничего добавить к.

Не соглашусь, что смысла нет, даже если тесты отражают только видение разработчика о требованиях к модулю. Банально на код-ревью сначала посмотреть тесты и понять, что совсем не то, что ожидалось разработано, чем по коду пытаться понять что собственно программист пытался реализовать и отвечает ли реализация реальным требованиям.

Это всё проблемы не тестов, а архитектуры. Например, высокоуровневый тест берущий ToDoMVC и проверяющий, что если добавить 2 задачи, одну завершить, а потом потыкать по фильтрам, то список задач будет правильно перефильтровываться:


// Создали инстанс компонента в тестовом контексте
const app = $mol_app_todomvc.make({ $ })

// Ввели текст в инпут добавления задачи
app.Add().value( 'test title' )
// Закоммитили добавление задачи
app.Add().event_done()

// Добавили ещё одну задачу
app.Add().value( 'test title 2' )
app.Add().event_done()

// Щёлкнули по чекбоксу завершения задачи
app.Task_row(1).Complete().event_click()

// Проверили, что изначально показываются обе задачи
$mol_assert_like( app.List().sub() , [ app.Task_row(1) , app.Task_row(2) ] )

// Перешли по ссылке "только завершённые"
app.$.$mol_state_arg.href( app.Filter_completed().uri() )
$mol_assert_like( app.List().sub() , [ app.Task_row(1) ] )

// Перешли по ссылке "только активные"
app.$.$mol_state_arg.href( app.Filter_active().uri() )
$mol_assert_like( app.List().sub() , [ app.Task_row(2) ] )

// Перешли по ссылке "все задачи"
app.$.$mol_state_arg.href( app.Filter_all().uri() )
$mol_assert_like( app.List().sub() , [ app.Task_row(1) , app.Task_row(2) ] )

Исполняется всего 3 миллисекунды.

Это вместе с ответом сервера? :)

Это на мокнутом сервере.

Создание моков вы не учитываете?

Пускай, тем не менее юнит-тесты на условной модели отработают быстрее чем на полном приложении.


Кстати, забыл ещё один минус высокоуровневых тестов — сложность локализации ошибки. Вот первый ваш ассерт вернёт ошибку, где будете искать её причину?

Не факт, в юнит тестах специфичные моки не будут оптимизированы, ибо исполняются лишь один раз. Да и покрытие у юнит-тестов меньше. А вместе с интеграционными тестами они займут времени куда больше.


Пройдусь дебаггером по шагам. Можно подумать юнит-тесты каким-то чудесным образом могут сказать в каком именно месте функции ошибка.

DRY при написании юнит-тестов никто не отменял. Как и оптимизацию по скорости путём переиспользования (главное, чтобы изолированность тестов не нарушалась).


Юнит-тесты скажут в какой функции ошибка :) При хороших подходах к коду и тестам.


Ещё один минус высокоуровневых — не понятно кто и когда их должен писать, если несколько человек пилят одну фичу, ну там один UI, другой модель/стор, третий и/о сервисы и т. п.

Каждый свою часть тестирует.

Мокая остальные? Чем тогда это отличается от юнит-тестов? :)

Не мокая остальные.

Как UI разработчик будет тестировать, если стор ещё не готов, не мокая его?

Для этого есть MockDrivenDevelopment. Сначала делаем мок, чтобы зависимый код можно было разрабатывать. И не спеша пишем нормальную реализацию, заменяя моки.

Когда-то такой подход назывался "нисходящее программирование"...

"нисходящее" имеет негативные коннотации и звучит как какой-то бородатый легаси. А вот WhateverDriverDevelopment звучит как best pactice.

Ну примерно так и выглядит юнит тестирование. Замокали все зависимости и тестируем один модуль.

Вы доказать-то что пытаетесь?

Ну, собственно, для того тесты и нужны чтобы проверять, что код отрабатывает так, как ожидается. Конкретно юнит-тесты — обычно самый быстрый способ проверки. То есть наоборот, если не можете проверить с юнит-тестами, то тогда придётся как-то проверять без них. Вплоть до выкатки на продакшен и ожидания багов.

Отлично!


Каждый раз, когда я рефакторю код на хаскеле, интеграционные тесты сразу зелёные после того, как код протайпчекался.

А тем кто не пишет на функциональных языках или языках со статической типизацией?

Не знаю, я стараюсь минимизировать взаимодействие с языками без строгой статической типизации.

Ну вот

Что «ну вот»? Вы спросили, как можно делать рефакторинг без юнит-тестов, я ответил, как можно делать рефакторинг без юнит-тестов.

Как?

Берёте и делаете. Потом исправляете ругань тайпчекера, как исправили — считайте, сделали. Можно? Можно.


Вот как юнит-тесты помогают делать рефакторинг, мне больше непонятно, ибо сами эти юниты (как нас учит википедия, нетривиальные функции) же меняются, значит, надо обновлять тесты.

Юнит-тесты должны быть тривиальны
Тесты пишутся перед изменением функционала

По сути наш разговор свелся к тому, что, по вашим словам, стабильную разработку на ЯП кроме особенных вести нельзя.

И как бы мне не хотелось все писать на кложуре, пока не получается
Юнит-тесты должны быть тривиальны

Что это значит?


Тесты пишутся перед изменением функционала

А если вы не меняете функциональность, а производите совершенно тупой и дубовый рефакторинг?


По сути наш разговор свелся к тому, что, по вашим словам, стабильную разработку на ЯП кроме особенных вести нельзя.

Смотря что называть стабильностью. Какие-то формальные гарантии, да, можно получить только на особых языках. На этом моём любимом хаскеле — только очень базовые, к слову, но то такое.


И как бы мне не хотелось все писать на кложуре, пока не получается

Clojure тут ну совсем никак не поможет, но неважно.

Вы совсем не знакомы с тдд воркфлоу?

Тогда и хаскель никак вам не помогает или вы запутались?
Вы совсем не знакомы с тдд воркфлоу?

Type-driven development? Знаком.


А если чуть серьёзнее, то и в случае TDD я представляю, что люди делают.


Но давайте обсудим на примере. Вот пишете вы сами лично функцию сортировки, скажем. Timsort там какой-нибудь. Какие тесты вы на неё пишете?


Тогда и хаскель никак вам не помогает или вы запутались?

Помогает чуть меньше, чем другие языки, ибо избавляет меня от более узкого класса ошибок. Даже со всеми современными GADTs, DataKinds, TypeFamilies и прочим этим вот всем.

Но давайте обсудим на примере. Вот пишете вы сами лично функцию сортировки, скажем. Timsort там какой-нибудь. Какие тесты вы на неё пишете?

  1. краевые случаи: пустой список, список из одного элемента, упорядоченный список, обратно упорядоченный список, список из одинаковых элементов;


  2. некоторое количество случайных перестановок;


  3. некоторое количество случайных списков с повторяющимися элементами;


  4. большой список (для проверки быстродействия);


  5. несколько тестов с некорректными компараторами, чтобы проверить отсутствие повреждений памяти, если язык этого не гарантирует.


И вот представьте себе, вы предложили сделать пять автоматических тестов для одной функции, которая в общем-то пишется раз и навсегда, без каких-либо изменений. При этом тесты, судя по всему, в несколько раз будут превышать сложность самой функции. Надо ли это делать вообще?

А вы что, предлагаете вообще ни разу не запускать написанную функцию?


Ну хорошо, на том же Идрисе это возможно, но там на построение строгого доказательства работы как бы не больше времени потрачено будет. А что делать на "обычных" языках программирования?

Да не предлагаю, конечно. Но написание теста как минимум подразумевает, что есть потребность проверять этот код многократно. Я бы просто проверил работоспособность функции, а остальное оставил бы на откуп функциональным/интеграционным тестам. Если там где-то вылезет проблема с отображением данных, например, по производительности/потреблению памяти, тогда бы уже погружался в детали. Я не сторонник делать значительный объем работы просто так, «на всякий случай», вдруг оно там понадобится. Потому что опыт показывает, что практически никогда не понадобится, а в тех редких случаях, когда понадобится, можно доделать потом. Запустить проект раньше и усовершенствовать его, когда он уже работает и приносит деньги, это выгоднее, чем делать его сразу правильно, но запускать позже (естественно, при условии, что вы запускаете его в работоспособном виде, а не совсем кривую поделку). Это касается и избыточной заложенной в проект масштабируемости, и избыточных тестов, и т.п.
И естественно, вышенаписанное — это общий случай. Если эта функция сортировки будет являться краеугольным камнем в моём приложении, то и подход к ней будет иной.
Но написание теста как минимум подразумевает, что есть потребность проверять этот код многократно.

Так ведь в процессе отладки функции её именно что нужно проверять многократно.


Я бы просто проверил работоспособность функции

Так для того тесты и пишутся


Я не сторонник делать значительный объем работы просто так

В чем заключается значительный объём работы? В том, чтобы написать один класс и десяток заголовков методов? Содержимое тестов сюда точно не входит: его придется писать в любом случае, функцию же проверить надо, пусть даже и однократно (ха-ха).

В чем заключается значительный объём работы? В том, чтобы написать один класс и десяток заголовков методов?

Так не только заголовки ведь. Вам нужно подготовить тестовые наборы данных для каждого случая, входные и выходные для сравнения, продумать, какие assert'ы нужны, прописать их. Проверить всё это, т.к. тесты — такой же самый код, и там тоже могут быть ошибки. Это всё такая же работа, как и написание самой функции, причем достаточно объемная.
Я бы просто проверил работоспособность функции

Я имею в виду, в целом, а не обкладывать её тестами с краеугольными случаями со всех сторон.
Вам нужно подготовить тестовые наборы данных для каждого случая

А при проверке работоспособности функции это все, как бы, не нужно?


и выходные для сравнения

Не обязательно. При правильном построении исходных данных корректность сортировки можно проверить без "эталонных" выходных.


Тесты в пунктах 4 и 5 можно оставить и вовсе без проверки результата: не падает и ладно.


Проверить всё это, т.к. тесты — такой же самый код, и там тоже могут быть ошибки.

Отдельно тесты проверять не нужно. Во-первых, тесты должны быть значительно проще проверяемого алгоритма — тогда и ошибок в них будет заведомо меньше. Во-вторых, первый же запуск тестов проверит заодно и их.


Я имею в виду, в целом, а не обкладывать её тестами с краеугольными случаями со всех сторон.

А кому нужна функция сортировки, работающая "в целом"?

А при проверке работоспособности функции это все, как бы, не нужно?

Нет, я же делаю не сферическую функцию в вакууме. В приложении у меня будет набор входных данных там, где она будет вызываться. А в юнит-тесте его не будет.
Не обязательно. При правильном построении исходных данных корректность сортировки можно проверить без «эталонных» выходных.

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

И тем не менее, вы не можете обеспечить то, что тесты у вас будут значительно проще проверяемого алгоритма. Это вообще не от вас зависит, а объективно от требований к алгоритму. И вы должны будете проверять и алгоритм и тесты.
А кому нужна функция сортировки, работающая «в целом»?

Например, тем, кто платит деньги за разработку и ждет от вас результат. Вы можете гарантировать работоспособность с вероятностью 99% за N денег и M времени, и с вероятностью 99.9% за N*2 денег и M*2 времени. Если ваш софт управляет не штуками вроде обсуждаемых по соседству Боингов-737, а относится к подавляющему большинству других приложений, частный случай отказа которых никого не убьет, и даже не нанесёт сколь-нибудь существенных материальных убытков, то первый вариант куда более логичен.
Ну вы же понимаете, что массив данных сам себя на сортировку не проверит. Что-то должно быть, или опорные данные для сравнения, или хотя бы проход по массиву для проверки его на отсортированность.

А в чем проблема прохода по массиву для проверки упорядоченности? Этому проходу всё ещё не нужен "эталонный" результат.


И тем не менее, вы не можете обеспечить то, что тесты у вас будут значительно проще проверяемого алгоритма.

Могу. Если тесты оказываются сложнее чем алгоритм — я обычно просто не пишу их. Но в случае с сортировкой это не так.


Вы можете гарантировать работоспособность с вероятностью 99%...

… а потом коллеги будут неделю искать баг в каком-нибудь безобидном месте, просто потому что не будут ожидать подвоха от функции сортировки, а когда найдут — заменят timsort на условный "пузырёк", потому что сроки давно вышли, а фиксить-то баг надо. В итоге все время, потраченное на реализацию сортировки, уйдет впустую.


И все из-за того что я пожалел получаса на написание теста.

И все из-за того что я пожалел получаса на написание теста.

А вы не напишете и не отладите все те тесты за полчаса. Вы реально потратите на них больше времени, чем потратили на ту сортировку. А ведь мы говорим только об одной функции, а сколько их ещё там будет. Полноценное покрытие тестами в среднем удваивает сроки разработки для большинства проектов. Выгода появляется лишь там, где очень сложное поведение и много внутренних взаимосвязей, либо что-то с высокой стоимостью отказа. Тесты однозначно нужны, если вы пишете трансляторы, движки СУБД и тому подобный софт, или какие-то платные сервисы, работающие в режиме 24х7. Но если вы относитесь к 99% остальных программистов, которые пишут бизнес-приложения, сайты/интернет-магазины, мелкие аппликухи для смартфонов и т.д., то расходы на юнит-тесты у вас не окупятся, даже со всеми теми возможными «коллеги неделю баг искали». Поэтому тут здравый подход, как по мне, следующий: заказчик платит — делаете. Вам-то только лучше, вам за это заплатили. Заказчик не платит, или пишете для себя и за свой счет, пишите без юнит-тестов и не переживайте, вы ничего ровным счетом не потеряете.
А вы не напишете и не отладите все те тесты за полчаса.

Если ошибок не будет — напишу и отлажу. А если будут — значит, тесты были написаны точно не зря.


А ведь мы говорим только об одной функции, а сколько их ещё там будет.

И правда, сколько подобных (т.е. алгоритмических) задач у меня будет в типичном проекте? Моя личная статистика — в среднем 0,1 на проект...


Выгода появляется лишь там, где очень сложное поведение

А у сортировки, что, простой алгоритм?


Напомню, свою сортировку нужно писать только в том случае, когда находящаяся в стандартной библиотеке почему-то не подходит. Это автоматически отметает все простые алгоритмы.

А в чем проблема прохода по массиву для проверки упорядоченности? Этому проходу всё ещё не нужен "эталонный" результат.

Вам потребуется убедиться, что алгоритм не выкидывает дубликаты и не добавляет их, что он пользуется компаратором, в него переданным, а не случайно подвернувшимся под руку operator<, и так далее. Где-то в процессе вам придётся в тестах научиться подсчитывать и группировать элементы согласно предикату, что уже начинает плохо пахнуть.

Вам потребуется убедиться, что алгоритм не выкидывает дубликаты и не добавляет их

Ещё один проход по массиву. Ну прямо очень сложно...


что он пользуется компаратором, в него переданным, а не случайно подвернувшимся под руку operator<

Ну, на том же C# это попросту невозможно, если алгоритм обобщенный. На плюсах можно структуру-обертку сделать без оператора <. Или можно использовать компаратор, который не совпадает с естественным порядком, например x * 3 % n < y * 3 % n


Где-то в процессе вам придётся в тестах научиться подсчитывать и группировать элементы согласно предикату, что уже начинает плохо пахнуть

В языке с мутабельными массивами нет необходимости делать так сложно.

Это хорошо, прямо жаль стало, что вы не sim3x.


Только вот как теперь доказать, что этого набора тестов достаточно?


Это уж я заметаю под ковёр вопрос об определении достаточности, он тоже интересный.


Ну и да, я не зря timsort взял. В алгоритмах я не оч шарю, но ЕМНИП конкретно timsort переключается на принципиально другой алгоритм сортировки для малых массивов. Не зная, что внутри функции, вы не сможете определить, что надо потестировать массивы разного размера, и где проходит эта самая граница разности, и не возникнет ли там спецэффектов где-то.


Проще доказательство написать, ей-богу.

Только вот как теперь доказать, что этого набора тестов достаточно?

Тесты — не про доказательства. Тесты проверяют, что не наделано глупых ошибок.


Не зная, что внутри функции, вы не сможете определить, что надо потестировать массивы разного размера, и где проходит эта самая граница разности

Тут согласен, надо достать эту константу из алгоритма и учесть ее в тестах.


Проще доказательство написать, ей-богу.

Не уверен. На языках с формальной верификацией придется долго объяснять компилятору тривиальные вещи (вы и сами на это жаловались), а на остальных ваше "доказательство" тоже может содержать ошибки.

Тесты — не про доказательства. Тесты проверяют, что не наделано глупых ошибок.

Ну тут смотря как к ним относиться. Если так — то да, конечно, согласен. Если же ожидать от тестов каких-то гарантий (что утверждалось кем-то сильно выше по треду неделю назаду) — ну, сами понимаете.

Проще доказательство написать, ей-богу.
если проще, то напишите

Делать рефакторинг с другими типами тестов.

Как делать рефакторинг с помощью функциональных тестов, если наружу у данного блока торчит одна ручка?

например?

Пример блока с одной ручкой.

Если наружу у модуля торчит только одна ручка — то тем проще. Нужно описать подробно, что она делает и все юниттесты выкинуть за ненадобностью.

И да — я это вполне проделывал.
Отлично

Так как проверить корректность его работы?
Сравнением результатов работы со спекой на эту самую единственную ручку, очевидно.
Как?
Конкретнее пожалуйста
Я, честно говоря, не понимаю, в чём тут проблема. Если наружу торчит одна ручка, это вообще идеальная ситуация. Вы сразу увидите, что блок работает не так. А чтобы локализовать проблему, возникшую при рефакторинге, просто возьмите вместо юнит-тестов глаза и мозг. Вы же не делали рефакторинг с закрытыми глазами, случайно тыкая по клавишам? Значит, вы прекрасно знаете, что изменилось, и что могло поломаться. Ситуация, когда есть какой-то код, настолько сложный, что при рефакторинге вы понятия не имеете без тестов, что отвалилось, мне сдаётся совершенно надуманной. Хотя бы по той причине, что если вы не в курсе, как этот код работает, у вас есть ровно 0 аргументов за его рефакторинг и 100% против.
Вы же не делали рефакторинг с закрытыми глазами, случайно тыкая по клавишам?
В том-то и дело, что очень часто люди именно так и делают рефакторинг. Просто рандомно переносят строчки из одного места в другое, запускают набор тестов и «убеждаются, что всё работает».

Конечный результат — куча странных багов, которые никто не может поправить, так как откуда они возникают — непонятно, а как программа в принципе работает — не знает никто. То есть вообще никто.

Ситуация, когда есть какой-то код, настолько сложный, что при рефакторинге вы понятия не имеете без тестов, что отвалилось, мне сдаётся совершенно надуманной.
Вы слишком хорошего мнения о «комбинаторных программистах». Я наблюдал ситуацию, когда проект был нашинкован в лапшу с помощьё Guice и ответ на вопрос «откуда тут вообще берётся e-mail заказчика» не знал никто. Соотвественно попытка исправить баг, при котором почта улетала «не туда» — превращалась в тот ещё квест. При этом юниттестов было полно, всё было отлично покрыто ими. Для того Guice и прикручивался.

Хотя бы по той причине, что если вы не в курсе, как этот код работает, у вас есть ровно 0 аргументов за его рефакторинг и 100% против.
Наоборот — это было преподнесено как достижение: нам не нужно знать что и как тут работает, потому что у нас покрытие тестами больше 95%. Тот факт, что при этом, почему-то, никто не может баг исправить, за разумное време, никого совершенно не смущало.
Хотя бы по той причине, что если вы не в курсе, как этот код работает, у вас есть ровно 0 аргументов за его рефакторинг и 100% против.

Как раз наоборот. :) Если не можешь понять что делает код — его нужно рефакторить. А если понятно, то особо и рефакторить незачем.

Я тут бы поспорил. Мы же все как художники, вечно недовольны работой своих предшественников, и только ищем повод, чтобы её переписать :)
А это такая себе ловушка, которая обычно создает куда больше проблем, чем решает.

Это да. Но я больше о производственной необходимости, а не о повышении самооценки :)

Ситуация, когда есть какой-то код, настолько сложный, что при рефакторинге вы понятия не имеете без тестов, что отвалилось, мне сдаётся совершенно надуманной.

Бывает объективно сложная бизнес-логика, в которой лень и некогда разбираться. Бывает 100500 краевых случаев, любой из которых может легко сломаться.

Бывает. Но тут как раз второй вопрос — зачем её вообще рефакторить, если вам банально с её работой некогда разбираться?

Чтобы рефакторить некоторую часть не обязательно разбираться в ней всей.

От функциональных отказываться нельзя никогда

Они всегда покрывают весь проект, тк пишутся по юзкейсам
Проблема с функциональными — они могут просто отдать вам некорректный результат. Но вот сузить область, где именно ошибка лежит они чаще всего не смогут

Юнит тесты позволяют быстрее изменять существующий код, этот эффект в расчетах не учтен.
Кроме того, тестируемый код приходится писать менее связанным с другими частями проекта, что косвенно улучшает архитектуру.


Уменьшение вероятности ошибки далеко не единственный результат юнит-тестирования, строить выводы о миллиардных убытках на выбранной модели весело, конечно, но не совсем корректно.

Буду признателен, если поделитесь статистикой, подтверждающей это.

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

Но ведь именно это главный аргумент сторонников TDD, которые были упомянуты в самом начале.
Если кратко резюмировать их логику — написание тестов отдельно/после кода требует сильно больше времени и сил через правильное написание кожа и тестов в параллель.
Те результаты статьи совершенно правильные — написание тестов классическим образом — пустая трата времени и денег.
Быть может их просто как-то по другому писать надо?

Основные плюсы юнит-тестов вы так и не озвучили.
Дело в том что в отличии от других уровней контроля качества, юнит-тесты лежат максимально близко к коду. Если человек пишет юнит-тесты, то это непременно влияет на процесс разработки.
Вы говорите насколько написание юнит-тестов увеличивает затраты на задачи,
но не говорите насколько они уменьшаются при наличии ранее написанных тестов.
Можно вспомнить хотя бы про то, что наличие юнит-тестов позволяет не бояться вносить изменения в код, а так же про то что юнит-тесты заставляет программиста писать код лучше (кривой код очень сложно покрывать юнит-тестами).
А еще юнит-тесты можно применять в качестве документации к модулям системы, у новых участников команды не будет возникать вопросов как использовать тот или иной компонент.
А еще… Да много еще чего.
Короче говоря, юнит-тесты это больше про процесс разработки, а не про контроль качества.
Оглядываясь назад в те времена когда я не писал тесты, мне иногда кажется что я был очень смелым.
Но на самом деле тут должна быть картинка «Слабоумие и отвага» с Чипом и Дейлом.
Сегодня выполняя задачу без тестов я чувствую себя лгуном, так как говорю что задача сделана, а гарантий этого я не даю даже самому себе.

Да вы и сейчас не даёте никаких гарантий, ибо юнит-тесты не гарантируют, что всё в сборе будет работать.

Гарантия это про несение ответственности, а не про полное отсутствие багов.
Я конечно догадался что вы про интеграционные тесты. Просто на низком уровне интеграционное и модульное тестирование не так уж и сильно отличается. Если я мокаю в тесте одну из зависимостей с сохранением интерфейса и проверкой передаваемых в нее параметров, это все еще модульный тест или уже интеграционный?
Я читал вашу статью и хотя в целом я поддерживаю разделение элементов системы на модули и компоненты, концептуально это не играет роли, сотрите из своей схемы модули и компоненты превратятся в те же модули. К тому же, ваша схема неверная, у вас связи от модуля идут только к одному компоненту, в реальной жизни это не так. К примеру вызов библиотечной функции можно считать обращением к модулю и вряд ли вы захотите использовать библиотеку не покрытую тестами.
Я это к тому, что если считать компоненты независимыми черными ящиками со строгим контрактом, то ваши компонентные тесты вполне можно считать модульными, а если знать о наличии модулей, то эти тесты по определению интеграционные/функциональные.

Вот вы говорите прочитали статью, но продолжаете называть компоненты модулями и на этом основании доказывать полезность модульных тестов. Модуль — единица кода (обычно — один файл или его часть). Компонент — единица функциональности (например, класс со всеми своими транзитивными зависимостями).

И продолжаю называть. Потому как неспроста никто однозначно не говорит что же такое модуль в модульном тестировании. Вы правы что модуль это часть приложения которую можно протестировать независимо, но нужно еще определить независимо от чего именно его нужно тестировать.
К примеру, нужно ли тестировать систему независимо от работы стандартной библиотеки языка.
Просто вы для себя в качестве модулей выбрали так называемые «компоненты» (и я тоже). Что при этом мокать это всегда интересный вопрос, как правило при принятии решения учитываются время прогона теста, сложность подготовки окружения для зависимости, наличие модульных тестов для зависимости.

У модуля вполне однозначное определение — это кусок исходника, а не часть приложения. И стандартную библиотеку тоже над мокать.Тем более, если эти стандартных библиотек больше чем одна. Многие понимают бессмысленность этого действа, поэтому для стандартной библиотеки делают исключение. Некоторые делают такое исключение и для сторонних библиотек. И лишь немногие идут до конца и понимают, что их библиотеки от не их библиотек ничем принципиально не отличаются и перестают мокать всё подряд. Однако уже с первой незамоканной зависимостью тест по факту перестаёт быть модульным. Но по инерции люди продолжают называть модульными тесты, где не замокана ни одна зависимость.

Окей, we need to go deeper.
Не нужно ли при написании «истинного» модульного теста еще мокнуть реализацию языка программирования, ведь от нее тоже много чего зависит? И если мы этого не делаем, то наш модульный тест по факту опять интеграционный.
И даже если мы выполним эту задачу (хоть это и невозможно), можно пойти еще глубже и начать думать что для «истинного» модульного теста нам еще нужно мокнуть «железо» на котором этот тест запущен.

Мы это с ним уже обсуждали. Модульных тестов не бывает. А так как их не бывает то и модулей не бывает (см его статью — модуль это то что можно протестировать отдельно). Ссылки на Фаулера с sociable unit tests не катят.

А давайте не утрировать?
Называть компонентные тесты интеграционными тоже не корректно.

У вас свое собственное определение компонентов. Обычно это — единица развертывания

А вы уверены, что это не у вас собственное определение?
Собственно суть компонента в том, что его можно запустить независимо от соседних компонент и компонент выше уровнем.

Да, наверное надо было погуглить. Помню, что в uml на deployment diagram именно компоненты.
Откуда вы взяли про уровни?


Возьмём определение отсюда?
https://en.m.wikipedia.org/wiki/Component-based_software_engineering#Software_component

Ну и последнее, написание юнит-тестов не всегда замедляет решение задачи, если задача это не какая-то элементарная вещь, то новые юнит-тесты начинают работать еще до завершения текущей задачи и тем самым ускоряет ее решение, хотя тут вы вероятно мне не поверите.
Это все отлично. Только вот можете ли вы описать эту выгоду в денежных еденицах? Не «значительно лучше» и т.п. что есть качественная оценка… А количественная, например — внедрение юнит тестов позволит нам решить эту задачу, нанаяв всего 7 человек, вместо 15 при условии неизменного срока разработки? Или внедрив юнит тесты мы уменьшим время простоя нашего сайта с 48 часов в год до 8 и это принесет нам ххх дополнительной прибыли, которая будет больше, чем затраты на внедрение и поддержку юнит тестов…
Данную оценку вполне можно дать при достаточности исходных данных… Но их как правило достаточно не бывает.
Хотя вру, бывает — когда заранее известно что разрабатываемая система временная и будет выкинута или не будет никак дорабатываться. К примеру, системы-костыли необходимые только на переходный период изменения какого-то бизнес-процесса, мосты между legacy-системами, всякие хакатоны и т.п.
Прошу заметить, что я не утверждаю что в этих системах не нужны тесты, я говорю о том, что «стоимость» написания или ненаписания тестов тут можно как-то оценить.
Если же система предполагает развитие, во что она потом превратится и нужны ли ей будут тесты большой вопрос, может быть бюджет на систему закончится еще до ее внедрения…
Без понимания «выгоды отказа от тестов в денежных единицах» и вы не способны достаточно внятно подтвердить точку зрения ненужности или вредности юнит тестов. Это проблема обоюдная, как у противников так и у сторонников покрытия тестами.
Без точных количественных оценок обе стороны будут просто гадать и делать субъективные оценки.

А точных оценок в большинстве случаев сделать нельзя. Юнит-тесты именно как тесты лишь уменьшают вероятность того, что ошибки программиста выйдут на другие уровни жизненного цикла продукта: тестирование, приёмка, опытная эксплуатация, эксплуатация. С одной стороны мы не знаем ни исходную вероятность обычно, ни насколько она уменьшится. С другой, такой подход игнорирует другие роли юнит-тестов в жизни продукта, как-то: формализация функциональных требований, документация и прочий шаринг знаний, возможное улучшение архитектуры.

>Если человек пишет юнит-тесты, то это непременно влияет на процесс разработки.
Э… нет. Ну то есть, может и влияет — но не обязательно. Я много раз наблюдал код, написанный коллегами, который допустим нарушает принцип единственной ответственности. Например, один и тот же метод сначала строит список каких-то объектов (файлов на диске), а потом этот список обрабатывает (сортирует, фильтрует, ищет самый новый файл). На этот метод написаны «юнит тесты», которые сначала создают какие-то файлы, потом вызывают тестируемый метод, а потом проверяют правильность результата. Логика обработки — весьма мутная.

Понятно, что значительная часть теста занята тем, что не нужно собственно для тестирования. Да, кривой код, очень сложно покрывать тестами. Покрыт кое-как. Ни одного бага никогда этими тестами не словили. При этом в коде были проблемы, которые проявлялись в интеграционном тестировании и в проме, но их не удавалось локализовать.

В процессе рефакторинга модуль был разбит на две части: получение списка файлов, и его обработка. Старые тесты отправились на помойку, так как фактически ничего не проверяли. Логика стала значительно более прозрачной, и гибкой.

Уже в процессе review и рефакторинга выявлено пара багов в той части, которая реально отвечает за обработку — скажем, самый новый файл нужно было искать по другому признаку, не дате модификации с точки зрения ОС, а имени файла и другим метаданным.
Именно такой рефакторинг был проведен с прицелом на то, чтобы итоговый код можно было тестировать. Но новые тесты я писать не стал, потому что проблем больше не наблюдается, а код стал тривиален настолько, что у меня теперь есть уверенность в его правильности.

Мораль? Да в общем все та же, что и автор озвучил. Нужно измерять. Нужно считать. Нужно выбирать те инструменты, которые более эффективны в текущих условиях. Качество кода по большому счету улучшается тогда, когда над ним думают головой. Тесты же — инструмент, а не панацея.
Признаю, некорректно выразился. Под тестами я конечно имел ввиду тесты которые написаны нормально, как правило это те тесты где очевидны условия, действия и результат. От таких тестов как в описанной ситуации конечно больше вреда чем пользы, особенно если они не только не находят баги, но и начинают «мигать».
При этом стоит заметить, что описанный «юнит»-тест уже не совсем подходит под определение, ведь на самом деле он тестирует 2 функции в интеграции, ну и соответственно начинает иметь признаки интеграционного со всеми вытекающими плюсами и минусами.
Ну да. С поправками на то, что это не гарантии, а инструменты, против соображений в целом у меня никаких возражений и не было.

Инструменты обеспечения гарантий :)

Ну вы знаете… Если у вас отличная архитектура и "чистый код", то что вы будете проверять юнит-тестами? Там и интеграционные тесты не нужны… Теоретически...

Собственно логику. Что чистый код реализует то, что нужно.

А это разве не интеграционные/функциональные (или какие угодно, но только не юнит-) тесты?


У вас же спека написана не в терминах юнитов, а в терминах поведения всей системы.

Какая спека? Тесты для меня (любые) — тоже своего рода спека. Масштаб, уровень только отличается. Юнит-тесты — спека на юниты :)

Но ведь «нужно»-то определяется в конечном итоге какими-то внешними требованиями (бизнеса или кого-то такого). Он про никакие юниты ничего не знает и знать не хочет.

Обычно из придётся в терминах предметной области. Юниты бизнес логики должны быть тоже в терминах предметной области.

*тз пишется

Но спека на них — продукт моей работы как программиста, а не внешней (относительно меня) потребности.


Если я пишу компилятор языка, то спека — это поведение всего компилятора на всём входе, например, а не особенности работы кусочка парсера.

Приведите пример кусочка спеки на компилятор, пожалуйста. Насколько я знаю обычно язык описывается отдельно, а кодогенерация — отдельно.


Не "a + b должно приводить к генерации add ax, bx, а "a + b это сложение, сложение должно приводить г генерации add ax, bx". Человеку неудобно будет работать со спекой, где грамматика и кодогенерация будет в одной большой куче.

Ну, стандарт C++20 является таковой спекой.


А если серьёзно, то да, например, куском спеки может быть «если в коде написано a + b, то при передаче коду конкретных чисел он возвращает результат их сложения, если нет переполнения».


При этом, конечно, вы можете написать тесты и на грамматику. Но, по собственному опыту написания околокомпиляторных вещей, тесты пишутся на весь парсер целиком, а не на отдельный комбинатор, отвечающий, не знаю, за парсинг объявлений функций.

Зависит от уровня спеки. На практике очень часто встречается, что бизнес-спек нет вообще, хоть в "аджайл-стартап" окружениях, хоть "на постсовком заводе" — это вопрос бизнес-культуры. А специфицировать ли создаваемые технические артефакты — вопрос культуры инженерной. И тесты — один из способов спецификации.

Обычная история — даже самый качественный инструмент, примененный неправильно, не решает проблемы, а только создает новые.
Ну да, можно и так упростить. Хотя я скорее имел в виду слегка другое — что инструмент у нас есть не один, и иногда следует выбрать вместо молотка отвертку.
Дело в том что в отличии от других уровней контроля качества, юнит-тесты лежат максимально близко к коду.

Ну, например, система типов ещё ближе. И она тоже может быть документацией.

Да, у меня (и многих до меня, и многих после, я уверен) был уже где-то тут комментарий о том, что достаточно гибкая система типов позволяет вообще не писать некоторые классы юнит-тестов, потому что начинает включать эти классы по факту своего (системы типов) существования. Точнее, предоставляет централизованную реализацию таких тестов.

Какие классы юнит-тестов позволяет не писать?

Большинство тестов на принимаемые и возвращаемые значения — уже в большинстве статически типизированных языков. При увеличении гибкости можно переносить в систему типов гарантии самих значений.


https://habr.com/ru/post/431250/#comment_19426486

Обычно тесты на типы принимаемыемых и возвращаемых значений не пишут. Да и сами функции/методы типы не проверяют специально чтобы выбросить TypeError, как правило. В лучшем случае явное приведение, а чаще неявное, по сути undefined behavior.


Ну и некоторые динамически типизированные языки довольно строго типизированные и TypeError выбросят при каких-то ошибках типов. Писать на эти кейсы тесты — проверять средства языка на ошибки.

Обычно тесты на типы принимаемыемых и возвращаемых значений не пишут.

В таком случае, вы верите, что там нет багов.


по сути undefined behavior

Верно, а если у функции есть диапазоны значений, на которых она не определена, то эти диапазоны следует фиксировать и в документации, и тестами. Потому что никогда не известно, когда очередной джуниор с горящим взором (или я) решит часть диапазонов "починить", и поломает какой-нибудь хитрый патч, который как раз на неопределённость и рассчитывает.


Для сравнения, для функции


getLength: Iterable[any] => PositiveInt

из моего примера теми самыми средствами языка (которые мы не тестируем) гарантируется 100% определённость в рантайме, потому что проверки системы типов не позволяют коммитить в рантайм код, который мог вызвать неопределённое поведение. И "хитрых патчей" с ней в центре тоже не возникнет, что, опять же, благо.

В таком случае, вы верите, что там нет багов.

Нет, не верю. Любые средства проверки на баги лишь уменьшают мою оценку вероятности их наличия. Я верю, что баги есть всегда, просто не всегда их получается обнаружить. Как в моём коде или коде коллег, так и сторонних средствах от средств проверки на баги до железа.


Верно, а если у функции есть диапазоны значений, на которых она не определена, то эти диапазоны следует фиксировать и в документации, и тестами.

В идеальной вселенной следует, да. В реальной хорошо если все области определения из требований документированы и протестированы.


Потому что никогда не известно, когда очередной джуниор с горящим взором (или я) решит часть диапазонов "починить", и поломает какой-нибудь хитрый патч, который как раз на неопределённость и рассчитывает.

А вот тут проблема в другом — не должно быть таких патчей без покрытия тестами. Использование неопределённого ранее поведения (таковым я считаю любое поведение не покрытое тестами) должно сопровождаться фиксацией этого поведения. Лучше явной, но пойдёт и неявное в виде тестов кода джуниора.


В целом, я ничего не имеею против систем типов. Сам стараюсь использовать типизацию где могу, пока это не усложняет написание и, главное, чтение кода. Главный недостаток мощных систем типов в коде для меня — это сложно, а значит долго и дорого, как разрабатывать, так и потом развивать и поддерживать. Особенно в поддержке/развитии — рано или поздно, замотивированный "эффективным менеджером" разработчик (я — рано), начнёт делать явные приведения в статике, фейковые тайпгварды в рантайме и т. п. чтобы уложиться в приемлемые сроки, когда чужая (в том числе прошлого себя) проверка на типы не даёт выполнить задачу при наличии веры в то, что передаешь-обрабатываешь правильно, просто объяснить это тайпчеккеру не получается быстро из-за того, что объявления типов занимают больше места чем сам код и являются по сути write-once кодом.


Вот из вашего примера: я люблю пустые коллекции и обязательно где-то приведу свой условный NonNegativeInt к вашему PositiveInt или наоборот.

рано или поздно, замотивированный "эффективным менеджером" разработчик (я — рано), начнёт делать явные приведения в статике

Это будет ваш технический долг, только и всего. Далее остаётся только надеяться, что ваш менеджмент недостаточно эффективен, чтобы оптимизировать до нуля выплаты технического долга. Но даже если и оптимизирует, это сам принцип неверным не делает, это просто значит что менеджмент недостаточно хорошо разбирается в области и не знает, чем оборачивается большие долги в проектах.

Сколько стоят юнит тесты?

они бесценны, это часть процесса программирования, а не включаемая опция.

Их можно пропустить, если цена ошибки небольшая… можно просто делать смоке тесты бещ формальностей.

Их можно пропустить, если цена ошибки небольшая…

А можете привести формальную методику вычисления стоимости ошибки пожалуйста?

Да ну вот, например:

Спасибо. Замечательно! А какую из этих методик лично вы или ваша команда используете для оценки рисков в проекте? А если не секрет во сколько обходится сама оценка рисков?

Управление рисками и FMEA. Да, на анализ рисков ресурсы тратятся, сколько не могу точно сказать.
Все риски, отслеживаются, делаются митигейшен планы, смотрится вероятность, категория, влияние, тип… Для критических компонентов, есть отдельные требования, тесты и специальное ревью — Safety Review.
Но нам без этого никуда, потому что это должно быть по процессу для разработки устройств и софта по стандарту IEC 61508.

Если же говорить про риски, то, их определяют еще до начала проекта, когда проект даже не начался, а только смотрится его необходимость для бизнеса, в том числе проводится симуляция, т.е. что будет, если пролетим с прогнозом в меньшую строну, в большую. Что будет, если риск сработает. Но обычно только 3 кейза смотрится, типа все плохо, все нормально(по плану), и все будет очень хорошо (Хотя это не всегда хорошо, потому что, например, производство может не справиться..) Это все делается в системе, так что, главное данные правильно занести и риски определить. На основании симуляции, делается вывод какие шаги процесса можно пропустить. А шагов там, если не соврать более 100, и понятно дело ни в одном проекте еще все 100 шагов никто не делал :)
Например, вот для софта на этапе еще до кодирования и разработки архитектуры:
Анализ влияния на безопасность
Требования к программному обеспечению
Пир ревью
Требования к Программно-аппаратному интерфейсу
Пир ревью
Спецификация цифрового протокола
Пиир ревью
План модульных тестов
Пир ревью
План системного тестирования
Пиир ревью
План тестирования критических к безопасности компонентов
Пиир ревью

Процессы программирования разные бывают. И программирование, использующее тесты, определенно отличается в цене от программирования, их не использующего. Вопрос только в какую сторону.


Так что пусть это и не опция, но свою цену они имеют.

Несомненно, цену имеет всё, так же как и соблюдение принципа единой ответственности, и продумывание алгоритмов, и соблюдение правил оформления кода, и именование переменных, методов, классов, старание писать небольшие и понятные функции с небольшим числом параметров, и многое другое. И теоретически, каждый из этих аспектов, можно вырвать из процесса и оценить. Хотя я, всё еще считаю, что тестирование своего кода, стоит наравне со всем перечисленным, приходит с опытом, влияет на то, как пишется код и выстраивается работа в принципе, и не может быть опцией. А тут еще и мой личный опыт подтверждает, что без трансформации разработки от тестирования, мы бы просто не подняли ряд проектов как таковых, либо буксовали в постоянно разборе полетов прикрывая дырявость системы хорошо составленным контрактом, проглатывая при этом желание заказчика развивать систему дальше (в рамках отдельных контрактов). И судя по оценкам моего предыдущего сообщения и минус карме, я в этом как-то сильно заблуждаюсь.
И судя по оценкам моего предыдущего сообщения и минус карме, я в этом как-то сильно заблуждаюсь.

А вы хорошо знаете тех, кто вас минусовал?

Вопрос только в какую сторону.

А там нет универсальной стороны, что самое интересное. Зато есть очень многоарная функция цены от длительности разработки, качества исполнителей, качества постановки задачи, кривой обучения исполнителей деталям требования и "что заказчик действительно имел в виду", погоды, да и один Яхве знает чего ещё. Нелинейная. И при этом большая часть её аргументов становятся исчислимыми только в некрологе проекта, да и точный вид уравнения неизвестен пока никому (или очень хорошо скрываем).

Я согласен. Причем на мой взгляд, это одна из самых приятных частей процесса программирования. Ибо тест представляет собой проектировочный чертеж, черновик, технический набросок части кода, которая будет затем написана.


Это процесс осмысления и размышлений, воплощенный в исполняемом коде, понятном компьютеру.

Хочу еще добавить пример. Firmware для промышленных датчиков, цена ошибки: если компания делает 100000 датчиков в год по 300 доларров за штуку, то пропущенный баг, который мог быть отловлен на этапе юнит тестирования, принесет только прямых затрат на 30 млн долларов. А если еще посчиать репутационные убытки и падение продаж из за этого в следующих годах… то и все 100 млн долларов и возможно полный крах компании. Поэтому, юнит тесты для ПО таких датчиков нужны 100%.

Может просто нужны нормальные интеграционные тесты? А то я вот систематически наблюдал (как у коллег, так и у сторонних организаций) довольно расслабленное отношение к тестированию полного комплекса "в полях" при постоянной демагогии на тему "смотрите, как у нас хорошо покрыт этот вот наш модуль". Последствия некоторых таких ситуаций разбираются иногда по десять лет (!), уже после окончания продажи комплекса.

Слои юнит тестирования не исключают друг друга.
Тем же датчикам надо ещё физические тесты устраивать, когда жестокая реальность(кривой пайки) потопчется по полностью протестированному коду.

Дело в том, что это два разных вида тестирования.

Юнит тестирование — это белый ящик. Сам разработчик тестирует свой код, знает как он работает, просто проверяет, что делает, то, что он задумал… все функции. И он может отловить ошибки, например, при разных входных данных для какой-нибудь функции. К примеру, расход зависит от давления, функция расчета расхода на входе получает давление… и для её проверки можно передать на вход хоть что… скжаем +- бесконечность, NAN, какой-нить запредельный минус, и так далее, и посмотреть в реальности, как ваша функция это отработает. Плюсом, вы защищаете свой код, от недотепы, который через год полез в вашу идеальную функцию я начал там добавлять свой овнокод, при этом поломав расчет. Юнит тесты сразу покажут вам это… потому что не пройдут.

Системное тестирование — это черный ящик, тестировщик понятия не имеет как работает программа, он просто смотрит, что при подаче давления столько-то, на выходе датчик выдает расход столько-то и это укладывается в погрешность расчета. Он не может подать ни бесконечность, ни NAN, и о понятия не имеет как считает функция…

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

Возможно пример с датчиками всего лишь достаточно прост, и в его случае в принципе всего два уровня — юнит-тесты и системное тестирование. Я говорил про более общую ситуацию.

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

Количественная оценка средних временных затрат на написание юнит-тестов (около 30%) также как и оценка положительного эффекта, который они дают в дальнейшем (в виде снижения затрат на последующую модернизацию и развитие проекта) была у Роберта Мартина в Clean Architecture. Не уверен, но вроде бы он там ссылался на исследования.
На мой взгляд, это главная задача юнит-тестов — поиск регрессии. И этот эффект проявляется в будущем, а, значит, также должен быть учтён, дисконтирован и приведён к настоящему моменту. По аналогии с NPV.

Типы избавляют от необходимости писать 90% юнит-тестов. При этом влияние на качество кода они оказывают не ниже, а документация из типов намного лаконичнее и понятнее.

Какой процент тестов они делают ненужным, зависит от системы типов. Кое-где и все сто процентов покрываются.

Именно по этому самые глюченный системы были написаны на строго типизированных языках.

Возможно, вы имели в виду: «на статически типизированных языках».


Потому что, опять же, не все системы типов одинаково полезны. Сравнивать Coq/Idris и C++, например, не стоит.


Ну и интересно, пишут ли прошивки для Curiosity или самолётов на нетипизированных языках типа JS или Python.

Насколько я знаю Curiosity написан на state-charts, которые вообще не так чтобы язык программирования, зато надежности дает — огого! Boingи вроде летают на Rational Tau, которые в ядре своем — SDL диаграммы.

Окей — в Rust например система типов достаточно ясно дает понять, когда ты не прав. Но то никоим образом не защищает от того, что бы просто делаешь что-то не так чтобы очень правильно.

В ещё более продвинутых языках можно ещё большим количеством средств гарантировать, что вы делаете то, что нужно.


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

Для автомобилей пишут на JS


Ну и что-то мне подсказывает, что довольно много прошивок различных космических аппаратов было написаны или на нетепизированных ассемблерах или на очень слаботипизированном Си.

Для автомобилей пишут на JS

Прям прошивки, управляющие критическими системами типа двигателя или тормозов, а не магнитолой?


Ну и что-то мне подсказывает, что довольно много прошивок различных космических аппаратов было написаны или на нетепизированных ассемблерах или на очень слаботипизированном Си.

Во-первых, вопрос доступных средств и ресурсов, во-вторых — ну так и умели эти железки чуть меньше, чем современные. Curiosity тот же вон до какой-то степени себе может путь прокладывать, про это доклад на CppCon (кажется, 2014) был.

Деталей не знаю, но доступ к датчикам автомобиля (скорость, обороты, открытые/закрытые двери, уровень топлива в баке и т. п.) точно был.


P.S. Python — строго типизированный язык, кстати.

Деталей не знаю, но доступ к датчикам автомобиля (скорость, обороты, открытые/закрытые двери, уровень топлива в баке и т. п.) точно был.

А конкретные марки автомобилей не знаете?


Ну и если оно отвечает за рендеринг пердящего интерфейса на HUD (продублированный обычными инструментами) — это один разговор, если за считывание этих датчиков и запись данных в CAN — другой.


P.S. Python — строго типизированный язык, кстати.

Это неважно. Важно, что он не статически типизированный.

Знаю, но не скажу. :) Но скорее второй вариант.


Ну вот говорят, что если язык динамически типизирован, то нужно писать кучу тестов на типы. Даже если согласиться, тчо действительно нужно, то в сильно типизированных языках это количество значительно меньше.

Python сильно типизированный язык, правда динамически.

Очень интересно почитать когда система типов покрывает 90 и особенно 100% тестов.

Типы могут помочь найти некоторые ошибки, хорошим примером был бы Mars Climate Orbiter когда накосячили с единицами измерения. Система типов не очень поможет с ошибками в логике (написал < вместо > ) либо расчетах (неправильный знак, константа, логика) кроме случаев неправильной размерности единиц.

Система типов может дать ложное чувство что раз собралось то правильно работает, в итоге код на питоне но покрытый тестами (без них в питоне никак) может быть надежнее.
Очень интересно почитать когда система типов покрывает 90 и особенно 100% тестов.

Банальный пример ­— когда вы пишете что-то типа функции сортировки или какой-нибудь структуры данных, вы можете в (достаточно выразительных) типах гарантировать, что все нужные вам инварианты выполняются. А в тестах не можете, кстати, по крайней мере, с такой же степенью уверенности.


Ну и я бы посмотрел на тесты, гарантирующие, что в во всей цепочке используемых вами библиотек из npm или pip или чего угодно нет майнера биткоинов или прочей вирусни. Или что ваш наколенный интерпретатор просто типизированного лямдба-исчисления всегда завершается. Или что ваш локфри-алгоритм не имеет рейс кондишонов, ABA и прочих прелестей.


Система типов не очень поможет с ошибками в логике (написал < вместо > ) либо расчетах (неправильный знак, константа, логика)

Если вы про результаты расчётов можете сформулировать какие-то теоремы, то поможет.


Система типов может дать ложное чувство что раз собралось то правильно работает, в итоге код на питоне но покрытый тестами (без них в питоне никак) может быть надежнее.

Может, конечно. Но это уже, похоже, вопрос субъективного опыта.

Или что ваш локфри-алгоритм не имеет рейс кондишонов, ABA и прочих прелестей.

А не посоветуешь, что почитать про то, как такое верифицировать? В Software Foundations, например, так далеко не заходят.

Известных применений формальных методов и я не знаю. Правда, я не делал полноценного обзора литературы, но поковырять эту тему у меня в TODO.

Крайне редко приходится писать юнит-тесты на типы на js и php. И обычно там, где типы не помогут: на границах тестируемой системы. А по опыту работы с TS — документация из типов так себе, как только выходит за уровень скалярных типов, классов/интерфейсов и простейших дженериков. Иной раз кучу времени тратишь на то, чтобы просто скомпилироаалось, делая "фейковые" проверки на типы в рантайме. Банальная работа с json restish ендпоинте может превратиться в боль, если делать честные проверки.

Банальная работа с json restish ендпоинте может превратиться в боль, если делать честные проверки.

Либо мы доверяем серверу — и тогда никаких "фейковых" проверок не требуется, достаточно приведения типа. Либо не доверяем — и тогда вся эта "боль" необходима по условию задачи.

Да просто тип описать, соблюдя инварианты, чтобы к нему привести уже может быть больно. А ещё больше боли прочитать то, что описал.

А что у вас там за типы такие болезненные?

TypeScript. Одни наследуемые фабричные методы чего стоят.

type Rule<T> = (value: T) => bool;

class Validator<T> {
  private constructor(private rule: Rule<T>) {}

  public static create(this: {new(): Validator<T> }, rule: Rule<T> ): Validator<T> {
    return new this(rule)
  }

  public static createAlwaysValid { this: { create(): Validator<T>}}: Validator<T> {
    return this.create((value: T) => true )
  }
}

(писал без IDE)
здача сделать наследника StringValidator extends Validator чтобы фабричные методы возвращали тип StringValidator

Я не очень понял зачем вам отдельные объекты для валидаторов. Тем более создаваемые через фабрики.

Ну не буду же копипастить рабочий код. Что в голову пришло. Замените Validator на любой другой класс.

А на любом другом классе вам нужно принимать тип this в качестве статического параметра:


static make< Instance >( this : { new() : Instance } ) : Instance {
    return new this
}

Я так и сделал в create. Беда в том, что конструкция { new(): Instance } ) говорит компилятору ровно то, что говорит: new вернёт инстанс конкретного класса. Если не переписывать create/make в наследниках, то для тайпчекера их вызов будет возвращать инстанс родителя, а не наследника. Если где-то нужно использовать не new а create/make, то нужно его указывать вместо new как тип this. Ну нет в тайпскрипте (до 3.0 100%, сейчас 90%) простого способа указать, что new или статический метод класса возвращает инстанс того класса, на котором вызван, а не накотором определён. В моих терминах — нет в тайпскрипте аналога static и даже self типов PHP.

Вы невнимательно посмотрели мой код :-) Обратите внимание на место объявления статического параметра.

Первый параметр статического метода. Не вижу особой разницы, кроме того, что у вас Instance обычный типа, а у меня Validator — дженерик. Разве ваш код выдержит наследование без переопределения make в наследнике?


Ну и вторую проблему как решать, если в статическом методе нам нужен не new, а другой статический метод, а лучше все :)

В яваскрипте нет методов. А this определяется в момент вызова функции. Тайпскрипт учитывает это и выводит тип this из того, что находится слева от точки. Так же как и аргументы справа в скобочках. Надо только позволить ему это сделать с помощью статического аргумента.

Давно есть https://www.ecma-international.org/ecma-262/5.1/#sec-4.3.27


Плохо он учитывает в случае методов возвращающих инстанс класса, которому метод принадлежит. Нет конструкции, позволяющей указать в классе, что возвращается инстанс класса, в контексте которого метод вызван. Псевдопараметер this в таких случаях костыль, причём очень трудоёмкий костыль.


Причём в javascript всё с типами в таких кейсах нормально.

У меня много тут проблем. Одна из них: this тут не имеет тип класса.
Не работает


    static make< This >( this : new() => This ) {
        return new this
    }
    static make2< This >( this : new() => This ) {
        return this.make()
    }
}

Вот спасибо! Занёс в снипеты в раздел "это нужно просто запомнить". Согласитесь, что конструкция неочевидная и в мануале ничего об этом нет (по крайней мере найти не понятно как). Я верю, что по частичкам из разных глав как-то можно собрать, но для частого случая это как-то перебор.

Формально да, обычное, но дело не в том как работает. Дело в том, что в голову должен прийти такой вариант комбинации минимум четырёх концепций, да ещё в условиях когда в мануале для этого указана только две https://www.typescriptlang.org/docs/handbook/generics.html#using-class-types-in-generics

Всего-то возвращаемый тип добавить (: This)...

ну конечно же не избавляют. тесты проверяют логику работы, а не просто, соответствие типам. + дают возможность переосмысления написанного кода. разумеется они замедляют скорость разработки в моменте. но они дают возможность регрессивного тестирования всего написанного функционала. что касается типов, то они, в особенности приправленные дженериками всегда будут увеличивать когнитивную сложность кода.
За 13 лет работы я прошел этапы восприятия юнит-тестов от «что это вообще» до «без некоторого разумного покрытия юнит-тестами модуль не считается разработанным».
На том и стою.
Вот только не надо обобщать.

Во-первых, есть типы, и языки со строгой типизацией. А также другие инструменты, типа статического анализа, или code review например.

Во-вторых, бывают разные модули. Скажем, вполне реальный пример — модуль, который генерирует SQL запросы, SELECT нетривиального вида. Примерно с 10 разных видов. Попробуйте написать assert на то, что получившийся запрос является правильным, а я послушаю.
Попробуйте написать ассерт на то, что MS Word работает правильно??

В вашем случае это не один ассерт, а интеграционное или функциональное тестирование. Юнит-тестами надо покрывать каждый метод (функцию). Я верю, что у вас этот модуль не в одной функции на 100500 строк.
Этот модуль — 50 строк на каждый вид запросов, т.е. всего примерно 500.

>В вашем случае это не один ассерт, а интеграционное или функциональное тестирование.
Я именно это и хотел продемонстрировать. Это вполне замкнутый модуль, и довольно небольшой. И мы ни с кем тут не интегрируемся непосредственно, на выходе просто строка запроса. Но написать сюда нормальный юнит тест, чтобы не ставить рядом четыре разных базы данных — довольно проблематично. Поэтому тестируется он только в составе всего приложения.

Плохая архитектура? Ну да, наверное, местами. А где взять хорошую, если мы реально работаем с базами, с разными, и должны учитывать их специфику, которая выражается в том числе в разном синтаксисе запросов?
Генератор sql запросов — интерфейс.
Генерация sql запроса для разных баз выполняется разными имплементациями генератора.
Каждая имплементация имеет свои собственные тесты.
Как результат — все хорошо с тестами.
>Как результат — все хорошо с тестами.
Да? Ну так я жду от вас ассерт, что созданный запрос содержит именно нужные колонки, например. И нужные условия внутри where.
Возможно мы с вами по разному понимаем генератор sql-запросов.
Ниже VolСh пример привел, не понимаю, почему он плохо переживает минимальные изменения.
Потому что лишний пробел, или регистр одной буквы, или поменять поля местами, этот тест сломает. В основе теста же сравнение строк, откуда и вытекает адовая хрупкость.