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

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

НЛО прилетело и опубликовало эту надпись здесь

А откуда появились названия школ?

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

Насчет Детройта/Чикаго не уверен, а Лондонская почти наверняка из-за книжки «Growing Object Oriented Software, Guided by Tests”. Авторы книги – британцы, авторы библиотеки JMock, и они продвигали подход с моками.

Что интересно, конкретно про тесты и моки мне не очень понравилось, что они пишут, мне понравилось другое. А именно что они начинают с ужасного кода, плохо разбитого, и последовательно применяя SRP (single responsibility principle), они приходят к аккуратному коду. Для меня эта книжка была скорее про SRP, чем про тесты.

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

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

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

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

Хотя, текущая статья не укладывается в такую теорию, здесь C#, судя по коду. Но кто знает, может ее автор пришел в C# из динамических языков. :)

Конечно для каждого теста свой экземпляр контекста.

В моём коде так-то статически типизированный тайпскрипт используется. Но подсунуть чего-то не того легко и со статической типизацией. Например, число Пи там, где ожидается значение от 0 до 1.

Вопрос, что понимать под моками - все test douibles или как у Фаулера:

  • Mocks are what we are talking about here: objects pre-programmed with expectations which form a specification of the calls they are expected to receive.

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

Ну и ссылки по теме:

Знакомый материал, только вчера дочитал книгу автора "unit testing principles practices and patterns".

Спасибо за статью на русском, ещё бы книгу на русском выпустили бы.

Книга шикарна. Крайне всем рекомендую, особенно мне зашла концепция широкого и глубокого кода.

Владимир, спасибо!

P.S. Но всё равно я адепт лондонской школы =D.

Небольшое уточнение (в плане не возражения, а прояснения мысли из статьи).
Лондонский стиль юнит-тестирования ведет к методологии TDD (Test-Driven Development) по схеме «снаружи внутрь» (outside-in): вы начинаете с тестов более высокого уровня, которые задают ожидания для всей системы.

Не совсем так. Этот стиль вообще не навязывает направление разработки — можно разрабатывать как сверху вниз (outside-in), так и снизу вверх: ведь ничто не мешает использовать для тестирования классов более высокого уровня не разработанные ранее классы нижнего уровня, а их имитаторы(mock).
Собственно, далее по тексту самой статьи сказано именно это — но это сказано позже.

А вот «классический» стиль, если оставаться в рамках TDD, т.е. писать тесты до кода, как правильно сказано в статье, фактически навязывает разработку «снизу вверх». А это не всегда приемлемо. Если логика работы на верхнем уровне неочевидна, то при разработке «снизу вверх» нередко возникает необходимость менять спецификации интерфейсов к классам нижнего уровня (а то и выбрасыать эти классы целиком). То есть, часть труда пойдет на выброс.

Если логика работы на верхнем уровне неочевидна

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

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

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

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

То есть получается - прощай, Agile, и здравствуй, Waterfall, так? Вы точно уверены, что это плюс?

Т.е. вы считаете что ничего не нужно делать с тем что кто-то пишет код снизу-вверх?

В комментарии я этого даже не касался. Я всего лишь написал про ограничение "классического" стиля TDD, которые он накладывает. Вы согласны с тем, что он действительно такие ограниченния накладывает - ну, если не следовать практике поэтапного проектирования и разработки AKA waterfall, которую в наше время не только лишь все разработчики могут себе позволить?

Не DDD вообще, а ваше его восприятие, вот это

он обязывает сначала разработать доменную модель.

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

Вы определитесь, либо для вас из DDD следует waterfall(что не так), либо вы умудряетесь совмещать DDD с разработкой снизу-вверх(что есть оксюморон), либо считаете что DDD(и выражение разработка сверху-вниз) это не про "сначала разработать доменную модель".

Ну, я не знаю, умею ли я "работать по DDD" вообще, потому что ни разу не пробовал ;-) Но это не важно.
Важно, что для написани программы с испоьзованием "классического" TDD плюс DDD ещё до написания рабочего кода потребуется довольно трудоемкий этап проектирования и разработки специификаций модулей нижнего уровня - ведь без таких спецификаций невозможно написать для них тесты, не так ли?
А что это тогда, если не поэтапная разработка, AKA waterfall?

Необязательно сразу реализовывать ВСЮ доменную область(это зачастую невозможно). Нужно отталкиваться от необходимого, от того какой минимум нам нужен здесь и сейчас для решения конкретной проблемы бизнеса. Берешь домен, дробишь на субдомены, определяешь агрегаты(вместе с Entity, VO), и далее для реализации поведения уже юзаешь TDD.При таком подходе можно спокойно работать по agile. Да, соглашусь с тем что за счет этапа стратегического проектирования - DDD сильно буксует в начале(что попахивает waterfall), но потом уже ничто не мешает реализовывать фичи итерационно.

Необязательно сразу реализовывать ВСЮ доменную область... Берешь домен, дробишь на субдомены,

Адепты DDD, насколько мне известно (но могу и ошибаться) это называют Bounded context. То есть там это тоже как-то предусмотрено.

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

PS Мой любимый подход - разработка, ведомая здравым смыслом. Но, к сожалению, он совершенно не масштабируется. Потому что метрика под названием "здравый смысл" формально неопределима, а без метрик нынешние менеджеры (особенно - эффективные) управлять не умеют.

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

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

видимо да.

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

Как вы себе ответили на вопрос в начале статьи?

Как тест может быть зеленым если в моей системе еще нет реализованной функциональности для которой я пишу этот тест? У меня как раз под рукой есть нужный пример из пет проекта:

class Calendar:
    def __init__(self, user: User, uow: UnitOfWork):
        self._user = user
        self._uow = uow

    def add_subscription(self, subscription: Subscription) -> None:
        pass
    ...

в этом примере у меня есть агрегат Calendar, так же у меня есть абстракция UnitOfWork и entities User и Subscription со всеми нужными полями(которые создавались на этапе проектирования). Пришло время писать поведение для системы. Я хочу реализовать метод add_subscription. Пишу для него тест:

def test_add_subscription(calendar: Calendar, subsription: Subscription):
    calendar.add_subscription(subscription)

    assert len(calendar.get_subscriptions()) == 1

запускают тест, он красный. Делаю так чтобы тест стал зеленым, т.е. пишу реализацию add_subscription:

class Calendar:
    def __init__(self, user: User, uow: UnitOfWork):
        self._user = user
        self._uow = uow

    def add_subscription(self, subscription: Subscription) -> None:
        with self._uow as uow:
            uow.subscriptions.add(subscription)
            uow.commit()
    ...

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

То есть вы выбрали вариант "этого не может быть", понятно.

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

Тут еще много чего не протестировано помимо того что вы перечислили, и логика неправильная. Может быть потому что это упрощенный игрушечный неполный пример кода который был нужен для демонстрации того что тест не может быть пройден без кода для которого этот тест написан?
Судя по статье, вы с чего-то вдруг решили что "TDD адепты" заставляют разрабов следовать слепо какому-то алгоритму. В то время как TDD это не про алгоритм действий, а про подход к разработке. Никто не запрещает вам добавлять тесты к уже существующему коду.

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

Более того, вы соглашаетесь в своей статье(раздел Когда TDD полезен : когда Заранее известен контракт) с моим посылом про:

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

Поэтому я вообще не понимаю с чем вы тут спорите.

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

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

Человек, не понимающий, что он делает, одинаково бесполезен,

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

Но я больше речь не про пользу, а про отсутсвие вреда для кода: сотрудник, слепо следующий наилучшим практикам этого вреда может нанести меньше, чем человек, этих наилучших практик не ведающий, разве не так?

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

Есть философский вопрос - а надо ли вообще добавлять тесты к уже имеющимся? Чисто теоретически, ответ на него неоднозначен, хотя бы потому, что тестирование служить доказательством отутствия ошибок не может. Варианты разрешения неоднозначностей возможны разные.
Например есть такая методология - Bug Driven Development ;-) В рамках этой методологии программа считается безошибочной, если в ней не не найдено ни одной ошибки, и доработка же программы с целью устранения ошибок производится исключительно по факту их обнаружения заказчиком (отсюда и название методологии). По свидетельствам очевидцев, эта методология нередко применяется в разработках для госов и т.п. структур.
И очевидно, что в рамках этой методологии избыточное тестирование просто-напросто вредно: оно понижает шанс сдать написанную программу заказчику и получить причитающиеся за нее деньги. Так что, как видите, перфекционизм - он не всегда в реальной жизни уместен ;-).

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

Но главное, все это не имеет никакого отношения к джунам и их ошибкам.

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

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

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

то что у вас есть работающий тест на сценарий, не дает НИКАКИХ оснований считать что в других похожих сценариях он так же будет работать даже если схожесть кода 99%. Ваша проблема в том что вы думаете не исходя из абстракций, а исходя из реализаций. Т.е. и код вы пишете скорее всего снизу-вверх, от этого и ваши проблемы с тем что тесты нужно переписывать постоянно при изменении кода. Если вы пишите тесты для вещей которые априори будут постоянно меняться то и тесты вы будете постоянно переписывать, это называется хрупкими тестами.Есть хорошая статья с наглядным примером на эту тему.

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

https://qaevolution.ru/testovaya-dokumentaciya/test-dizajn/texnika-analiza-klassov-ekvivalentnosti/

https://page.hyoo.ru/#!=2jggfw_at1ily

Раз вы перешли на личности...Спасибо за ссылки на свои же блоги, из них я узнал что вас отовсюду выгоняют ***ми тряпками. И вместо того чтобы брать пользу из тейков ваших критиков вы еще дальше загоняетесь вглубь в свои шизо-теории. Буквально к каждому вашему выступлению вам в комментах пытаются открыть глаза на то что ваши тейки про тесты - на грани бреда.
А еще забавно было почекать проекты гения тестирования, открываю первые несколько случайных и во всех баги :) Чет совсем слабо с тестированием у вас ваших же проектах:

Что самое забавное, у вас указано 53 проекта, однако 90% из этого это hello word'ы и идеи для проектов(какие-то совершенно безумные зачастую). НО! Есть все же 4 'готовых продукта':
1. "Самописный поисковик". Который на самом деле юзает апи гугла(в самом описании вы об этом пишете но во всех остальных местах называете самописным)
2. Список "токсичных" сайтов. Это просто список с проукраинскими сайтами. Мда...оч. крутой проект;
3. Стартовая страница для браузера (судя по всему не работает)
4. Сайт со списком софизмов. Теперь понятно почему вы ведете диалог именно таким образом, не отвечая по существу а переходя на личности.

Ну что тут можно сказать, очень впечатляет(нет). Сильно сомневаюсь что вы когда-нибудь работали в какой-либо коммерческой продуктовой команде потому что с такими скилами и уровнем честности вас бы выгнали в течении недели.
Ну и в целом, я был абсолютно прав с самого начала, вы просто не умеете писать хороший код судя по вашим репам(все что там есть это crud-ы). Поэтому у вас и горит с того что вам приходится постоянно переписывать тесты. Повторюсь, просто научитесь программировать для начала, а уже потом можете давать советы остальным по TDD(судя по вашим же комментам на youtube все что вы о нём знаете это описание из Википедии) и так далее. Потому что TDD просто не работает с плохим кодом. Более того TDD нужен как раз для того чтобы вынуждать нас писать хороший код(но вы не читали книжек поэтому у вас какие-то свои выводы о том зачем нужен TDD)

  • artist.hyoo.ru и lingua.hyoo.ru использовали сторонний апи с нейронками. Гайки закрутили - апи работать перестало.

  • speculant.hyoo.ru - артефакт с хакатона. Логика блокировки кнопки старта при невыбранной профессии была далеко не самая приоритетная в реализации.

  • search.hyoo.ru использует кастомизированный индекс гугла, как и многие другие поисковики.

  • board.hyoo.ru прекрасно работает. Вы видимо не поняли о чём оно. Бывает.

  • jin.hyoo.ru - а тут можете узнать где и над чем мне довелось поработать.

отправил в прод недотестированный код

с чего вы это взяли?

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

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

Наверное с этого:

Я хочу реализовать метод add_subscription. Пишу для него тест

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

Начинаю рефакторинг если он нужен

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

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

Это предположение базируется на слишком широком допущении, кмк :)

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

А по-моему как-раз наоборот, если не будет большого графа классов, то либо проект уж очень маленький, либо просто он написан так, что не соответствует принципам SOLID, особенно первой букве - single responsibility.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий