| Археология автотестирования: SUnit, прародитель JUnit |
| 08.06.2026 00:00 |
|
Михаил Ланкин, автор статей команды ТестОпс Меня зовут Михаил, я технический автор, работаю с инструментами тестирования в команде ТестОпс. В какой-то момент мне стало интересно — а как получила распространение мысль о том, что разработчикам тоже надо писать тесты? У меня было смутное представление о некотором тёмном «раньше», и условно-ограниченно-просвещённом «сейчас», когда мысль о том, что тестирование не должно жить отдельно от разработки, кажется, стала нормальной. Мостик между этими двумя мирами — автотесты, они нужны и тестированию, и разработке. Фреймворк JUnit сознательно писали как можно более простым — в первую очередь для того, чтобы сделать его повседневным инструментом для разработчиков. Люди, работавшие с первыми фреймворками автотестирования, стали также авторами подходов экстремального программирования (XP) и разработки через тестирование (TDD) — т. е. подходов, настаивающих на том, что тестирование — это не «обязаловка», а интегральная часть разработки. С учётом этого, я решил заняться «археологией» автотестирования: посмотреть на прародителя современных фреймворков xUnit, SUnit для Smalltalk. Я хотел потрогать его руками, а также понять, что двигало его автором. В результате получилось довольно интересное путешествие, которым я хотел бы с вами поделиться. Вначале я посмотрю на то, что из себя представляло автоматизированное тестирование в 1990-е. Чтобы понять, что добавил SUnit, попробую запустить на нём несколько примитивных тестов. А потом посмотрю, что можно наскрести по сусекам интернета о мотивации создателей и пользователей. Как они пришли к тому, что барьер между разработкой и тестированием надо преодолеть? Сам я не был участником этого процесса (годами не вышел), так что придётся опираться на вторичные источники. Небольшое пояснение. Немного странно говорить об «археологии» фреймворка в живом, пускай не слишком распространённом языке. Но меня интересует именно первая версия этого фреймворка, написанная в 1994 году, поэтому прошу не обижаться тех, кто им по-прежнему пользуется. Что уже было в 1990-еЧто из себя представляло автоматизированное тестирование в 1990-е годы? Инструменты тогда были такие: Инструменты записи и воспроизведения. Тестировщик выполняет действия с тестируемой системой, инструмент записывает действия и потом воспроизводит. К 1990-м это была уже опробованная технология, которая особенно хорошо работала для старых систем, без графических интерфейсов (благодаря тому, что взаимодействие с пользователем было очень простым). Вот пример такого инструмента. Скриптовые инструменты. Здесь тестировщик уже не записывает действия, а пишет скрипт, который управляет системой. Когда в 1990-е говорили "автоматизированное тестирование", имели в виду в первую очередь такие инструменты, управляемые скриптами. Общее у всех этих инструментов было то, что разработчики ими не пользовались. Это были большие и сложные системы, работа с которыми требовала слишком много дополнительных усилий. Тесты, написанные разработчиками. Судя по всему, в объектно-ориентированных языках тогда уже существовала практика писать тесты; я не знаю, насколько она была распространена, но важно то, что она существовала в совершенно отдельном мире от инструментов контроля качества. Чего ещё не былоНе было инфраструктуры, которая позволила бы связать эти системы воедино, автоматически запускать любые тесты — и чёрного ящика, и белого. Тесты создавались специалистами по тестированию, передавались автоматизаторам, которые писали специальные скрипты, и всё это происходило при минимальном вмешательстве со стороны разработчиков. Что изменил SmalltalkПочему именно в Smalltalk возник первый фреймворк для автотестов? Smalltalk — интересный и во многом экспериментальный язык. Он создавался для образовательных целей, и последовательно реализовал принципы объектно-ориентированного программирования. Для нас важно то, что в Smalltalk задолго до Agile одним из главных принципов стала инкрементальная разработка. Это выглядело так:
У этого процесса есть существенные недостатки.
На Хабре уже высказали гипотезу, что SUnit вырос органически как попытка закрыть эти недостатки. Как именно он это сделал? Засучим рукава и попробуем запустить тесты, которые Кент Бек, автор SUnit, описал в 1994 году. Что SUnit умел в 1994-мЧтобы это сделать, вначале я установлю Smalltalk. Из его современных реализаций можно собрать целый зоопарк; я выбрал Pharo (основанный на Squeak, основанный на Smalltalk-80). Pharo Launcher скачивается здесь. И здесь мы сталкиваемся с отличительной чертой Smalltalk: это одновременно и язык программирования, и IDE, и операционная система.
В Smalltalk нет разделения между кодом и данными: всё является объектами, в том числе классы. Хранятся эти объекты в образах (images), моментальных снимках системы наподобие дампов памяти. Каждое приложение — это такой образ. Создаю новый образ в Pharo Launcher:
Меня попросят выбрать версию Pharo. В археологических интересах, конечно, хотелось бы запустить нечто как можно более раннее. Но даже самая ранняя из доступных здесь версий Pharo — 2.0 — это 2013 год, на 19 лет позже нужного нам года. Так что не будем пытаться залезать так далеко назад. Вместо этого запустим тесты из статьи Кента Бека с сегодняшним SUnit — благо, он по-прежнему интегрирован в Pharo. Начну с того, что создам тест. Для этого открою системный браузер:
Он покажет все классы в системе — и предоставляемые изначально, и написанные разработчиком. Тут можно, например, увидеть внутрянку SUnit:
Чтобы добавить новый тест, я очищаю окно исходного кода, ввожу там новый код, и нажимаю Ctrl+S (принять): Здесь я пользуюсь современным синтаксисом Pharo, чтобы создать новый класс ('SetTestCase'), дочерний от 'TestCase': так в SUnit создают новые тесты. А 'package' указывает пакет класса (в моём случае пакет создаётся с нуля). Теперь можно добавить тестовый метод, в котором будет код нашего теста. Выбираю свой пакет
Когда в окне классов выбран класс, система понимает, что мы добавляем код к этому классу. Что тут написано? Общий принцип синтаксиса в Smalltalk такой: объект — сообщение объекту — аргумент сообщения Например: Здесь множество В коде я определяю метод Настройка: декларирую локальную переменную Запускается тест просто: щёлкаю правой кнопкой мыши по классу
ФикстурыПеременную Чтобы вынести переменную Ctrl+S, очищаю окно исходного кода и создаю там метод Поскольку empty теперь — переменная инстанса, специально декларировать её тут уже не нужно. Наконец, убираю создание ОшибкиПрекрасно! Теперь пришло время ближе познакомиться с ошибками. SUnit с самого начала различал два типа ошибок:
Иногда бывает нужно убедиться, что тестовый код вызывает ошибку. В диалекте SUnit, с которым я работаю здесь, это делается через оператор Здесь происходит вот что.
СюитыЧтобы запускать несколько тестовых классов одновременно, SUnit позволяет объединять их в сюиты. В отличие от тестовых классов, сюиты создаются не наследованием от TestSuite, а просто созданием нового объекта этого класса. Я это сделаю в отдельной песочнице (playground) — временном рабочем пространстве. Щёлкаю левой кнопкой мыши в любой точке интерфейса, выбираю Browse > Playground. Ввожу там следующий код: Здесь я делаю следующее:
Щелкаю Do it all:
Песочница выполняет весь этот код, и показывает результат в новом окне:
Это — не какой-то специализированный интерфейс SUnit, в песочнице возможность вот так изучать результаты выполнения предоставляется для любого кода; собственно, это нормальный цикл разработки на Smalltalk. Результат здесь — объект класса TestResult. В нём тесты классифицированы по категориям:
Кроме того, есть данные времени выполнения тестов. Всё это было в TestResult уже в 1994 году. Впечатления после раскопокИтак, что мы имеем?
Самое главное — в отличие от других инструментов тестирования, существовавших в то время, всё это доступно на языке разработки, и максимально удобно для ежедневного использования при написании кода. Вот, собственно, и всё. Предельно простая функциональность, и первоначальная её реализация поместилась на страницах довольно короткой статьи. Для особой аутентичности наверное можно было бы за вечер-другой повторить этот фреймворк самому и запустить на [симуляторе Smalltalk-80]. Про JUnit Мартин Фаулер сказал, что «история разработки программного обеспечения не знает другого случая, когда столь много людей были обязаны столь многим столь малому количеству строк кода». Это ещё в большей степени относится к SUnit. Чего добивался авторЭтот довольно простой код стал несущей стеной для современного тестирования. Рассчитывал ли его автор на что-либо подобное? По его собственным словам — однозначно нет. Он решал простую задачу, быстро запустить тесты, и решал тем методом, который был максимально органичным для Smalltalk. Smalltalk — «радикально» объектно-ориентированный язык. По словам Кента Бека:: «Когда я проектировал первую версию xUnit, я применил один из моих обычных приёмов: превратить что-то в объект, в данном случае превратить всё рабочее пространство в класс. Тогда каждый фрагмент кода из пространства оказывается представлен методом (с префиксом «тест» в качестве примитивной аннотации)». Всё это было сделано буквально «на коленке», перед встречей с клиентом, когда нужно было объяснить, как лучше всего запускать тесты на Smalltalk. Решение было настолько простым, что о какой-либо его ценности Кент Бек даже не задумывался. Как насчёт идей Agile, экстремального программирования или разработки через тестирование, к которым автор фреймворка пришёл впоследствии? Они как-то повлияли на этот фреймворк? Тут не всё однозначно. В черновике книги «Smalltalk Best Practice Patterns», от 1996 года (т. е. уже после создания SUnit) даётся пример разработки на Smalltalk с применением всех описанных в книге паттернов. Там следы TDD найти сложно. Напротив, всё идёт по классической схеме Smalltalk: вначале пишем код, потом для проверки запускаем выражение, которое вызывает этот код. Правда, надо сказать, что уже в 1997 году в Chrysler Кент Бек организовал команду по принципам экстремального программирования. Ещё раньше на него произвёл впечатление разработчик, показавший, что тестирование не замедляет, а, наоборот, ускоряет написание софта (об этом написано здесь). И тем не менее, кажется, что влияние этих идей на создание фреймворка если и было, то только косвенное. Кажется, что всё было гораздо проще: я хочу запустить тесты, как мне удобнее всего это сделать? Параллельные наработкиИнтересно, что параллельно Беку похожие инструменты создали ещё несколько людей. В 1996 году Джерард Месарош написал механизм для автоматического запуска тестов для фреймворка событий, над которым он тогда работал — тоже на Smalltalk. По его словам, это тоже было сделано из чисто практических соображений, сэкономить на времени запуска тестов. Другой интересный проект был создан в начале 1990-х в фирме Taligent (созданной совместно IBM и Apple), на C++. Организатор тестирования там понял, что нагрузку по юнит-тестированию нужно возложить на разработчиков. Частью его решения было написать собственный фреймворк для юнит-тестирования, который по структуре на самом деле напоминал SUnit — хоть и возник независимо от него. Вот другой случай — Дэйв Фарли упоминает о том, что писал «какие-то юнит-тесты» в начале 1990-х годов. Правда, это был небольшой эпизод, и, когда он познакомился с JUnit, то перешёл полностью на него. Но побуждение всё равно важное. Что здесь интересно. Бек, Месарош и Фарли все так или иначе причастны к разновидностям Agile. И для всех трёх работа с тестовыми фреймворками предшествовала этому движению. Настолько, что, по словам Фарли, «я не понимал по-настоящему экстремального программирования, пока не увидел JUnit». ИтогиПережив пик популярности в первой половине 1990-х годов, интерес к Smalltalk сошёл на нет после 1996 года; в первом квартале 2024 года Smalltalk использовался в 0,031% пулл-риквестов на GitHub. Коммерческие применения Smalltalk остались довольно нишевыми, и волну хайпа на веб-технологии он не поймал. Зато Smalltalk популяризовал многие вещи, ставшие потом индустриальными стандартами: графические интерфейсы — и фреймворки для автоматизированного тестирования. Вернёмся к первоначальному вопросу: как получила распространение мысль о том, что разработчикам тоже надо писать тесты? Через написание примитивного по меркам времени инструмента, подсказанного повседневной практикой разработки. Эта практика пришла из экспериментального языка Smalltalk; и, судя по всему, она просто удачно совпала с потребностью времени в разработке малыми итерациями. |