Роль тестирования в эпоху программирования с помощью ИИ

Роль тестирования в эпоху программирования с помощью ИИ

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

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

Однажды, используя Plugin, который помогает в Workflow разработки с ИИ, я снова обратил внимание на TDD (Test-Driven Development). Использованный мной Plugin назывался Superpowers и довольно активно применял TDD при реализации функциональности. С самим тестовым кодом я был знаком, но никогда на практике не применял TDD как метод разработки. Раньше я писал тесты для проверки результата после реализации, тогда как в TDD тесты размещаются на более раннем этапе процесса разработки. Разбираясь в этом различии, я заинтересовался такими вопросами, как «Как применять TDD во frontend?» и «Почему ИИ-ассистенты для программирования и TDD так хорошо работают вместе?»

1. Написание тестового кода и разработка с использованием TDD — это разные вещи

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

요구사항 확인
    ↓
코드 구현
    ↓
테스트 코드 작성
    ↓
구현 결과 검증

TDD меняет этот порядок на противоположный. Сначала требования, которые ещё не реализованы, выражаются в виде тестов. Состояние, в котором тесты завершаются с ошибкой, называется RED. Затем пишется минимальный код, необходимый для прохождения текущих неуспешных тестов и достижения состояния GREEN, после чего выполняется REFACTOR для упорядочивания структуры, пока тесты продолжают проходить.

요구사항
    ↓
RED      : 실패하는 테스트 작성
    ↓
GREEN    : 테스트를 통과시키는 최소 구현
    ↓
REFACTOR : 동작을 유지하며 코드 정리
    ↓
다음 요구사항으로 반복

Например, если требование звучит как «Доставка бесплатна при сумме заказа не менее 10 000 вон», тест можно написать до реализации логики расчёта стоимости доставки.

@Test
void 만원_이상_주문하면_배송비는_무료이다() {
    int deliveryFee = calculateDeliveryFee(10_000);

    assertThat(deliveryFee).isEqualTo(0);
}

Если calculateDeliveryFee ещё не существует, тест завершится с ошибкой. Затем вы создаёте минимальную реализацию, удовлетворяющую тесту, и добавляете следующее требование в виде ещё одного теста. Важно не то, существует ли тестовый код, а то, где тесты расположены в процессе разработки.

2. Тесты превращаются из инструментов проверки в исполняемые требования

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

구현 후 검증 : 구현 → 테스트 → 결과 확인
TDD          : 요구사항 → 테스트 → 구현 → 결과 확인

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

Список тестов, написанных таким образом, становится списком небольших требований, которым должна соответствовать функциональность. Тесты больше не являются документацией, описывающей текущую реализацию; они становятся исполняемой спецификацией, выражающей поведение, которое предстоит реализовать.

3. Что же следует тестировать во frontend?

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

처음 렌더링했을 때 무엇이 보이는가
버튼을 클릭하면 화면이 어떻게 변하는가
입력값이 잘못되었을 때 어떤 메시지가 나타나는가
요청 중 Loading 상태가 표시되는가
성공 / 빈 데이터 / 오류 상태가 올바르게 표현되는가

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

Storybook
→ 컴포넌트의 여러 상태를 시각적으로 확인하고 문서화

Unit / Component Test
→ 정해진 동작이 계속 유지되는지 자동으로 검증

Изучая модульное тестирование frontend, я также систематизировал своё понимание того, какие разные роли выполняют такие инструменты, как Vitest, Testing Library, user-event и jsdom.

Vitest
└─ 테스트 파일 실행, expect, mock / spy, 성공·실패 판정
   └─ Testing Library
      ├─ 컴포넌트 렌더링과 DOM 탐색
      └─ user-event : 클릭, 입력 등 사용자 행동 재현

jsdom
└─ Node.js 테스트 환경에 가상의 DOM 제공

Vitest запускает тесты и определяет их результаты. Testing Library отображает компоненты React или Vue и помогает находить элементы с точки зрения пользователя. user-event воспроизводит такие действия, как нажатие и ввод текста, а jsdom предоставляет среду для запуска тестов на основе DOM без запуска настоящего браузера.

В частности, Testing Library рекомендует находить элементы по ролям и именам, которые могут распознать пользователи, а не по CSS-классам или внутренней структуре компонентов.

screen.getByRole('button', { name: '로그인' });

Иными словами, проверяется, «существует ли кнопка, которую пользователи могут распознать как кнопку входа», а не «существует ли класс с именем login-button». Это означает, что тесты frontend также могут сосредоточиться на поведении, наблюдаемом пользователями, а не на деталях внутренней реализации.

4. Red → Green → Refactor во frontend работает так же

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

it('증가 버튼을 누르면 숫자가 1 증가한다', async () => {
    const user = userEvent.setup();
    render(<Counter />);

    expect(screen.getByText('현재 값: 0')).toBeVisible();
    await user.click(screen.getByRole('button', { name: '증가' }));
    expect(screen.getByText('현재 값: 1')).toBeVisible();
});

Если функциональность увеличения ещё не существует, это состояние RED. После добавления состояния Count и поведения при нажатии, когда тесты проходят, достигается состояние GREEN; затем структура упорядочивается на этапе REFACTOR. Инструменты и объекты тестирования на backend и frontend различаются, но базовый цикл TDD остаётся одинаковым.

5. Почему ИИ-ассистенты для программирования и TDD так хорошо работают вместе?

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

Они делают условия завершения понятными

Если есть только запрос «Реализовать функциональность входа», может быть непонятно, когда считать работу завершённой. Успешная компиляция кода, вызов API и отображение экрана — это разные условия завершения.

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

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

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

코드 작성
   ↓
테스트 실행
   ↓
결과 관찰
   ↓
코드 수정
   ↓
다시 테스트

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

Они помогают сохранять небольшой объём каждой реализации

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

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

После завершения реализации они остаются защитной сеткой

Применение TDD не отменяет ценность тестов проверки, которые вы традиционно использовали. Тесты, направлявшие реализацию, становятся регрессионными тестами, проверяющими, не нарушилось ли существующее поведение при последующем изменении или рефакторинге кода.

Иными словами, «проверка после реализации», которую я использовал при работе с ИИ для программирования, и TDD — не столько альтернативы друг другу, сколько части процесса, в котором тесты распространяются на более ранние этапы разработки. До реализации они определяют поведение; во время реализации задают направление с помощью успешных и неуспешных результатов; а после реализации остаются защитной сеткой, которая оберегает существующее поведение.

6. Заключение

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

Поскольку стоимость создания с помощью ИИ как Production Code, так и Test Code продолжает снижаться, возможно, важнее не то, сколько кода можно быстро создать, а то, как мы определяем и постоянно проверяем, соответствует ли созданный код задуманному нами поведению. С этой точки зрения я считаю, что тесты останутся важным инструментом разработки в эпоху программирования с помощью ИИ.

Спасибо за внимание.

Hhkk

Site footer