惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

H
Hackread – Cybersecurity News, Data Breaches, AI and More
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
V
Visual Studio Blog
S
Schneier on Security
月光博客
月光博客
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 司徒正美
T
Threatpost
T
Tenable Blog
IT之家
IT之家
V
V2EX
Apple Machine Learning Research
Apple Machine Learning Research
C
Cisco Blogs
P
Proofpoint News Feed
K
Kaspersky official blog
NISL@THU
NISL@THU
Know Your Adversary
Know Your Adversary
H
Help Net Security
Google DeepMind News
Google DeepMind News
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Y
Y Combinator Blog
F
Fortinet All Blogs
Hugging Face - Blog
Hugging Face - Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Spread Privacy
Spread Privacy
量子位
D
Darknet – Hacking Tools, Hacker News & Cyber Security
Google DeepMind News
Google DeepMind News
AI
AI
美团技术团队
I
InfoQ
S
Secure Thoughts
B
Blog
J
Java Code Geeks
Latest news
Latest news
小众软件
小众软件
H
Heimdal Security Blog
S
Securelist
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
U
Unit 42
M
MIT News - Artificial intelligence
P
Palo Alto Networks Blog
A
Arctic Wolf
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
F
Full Disclosure
The GitHub Blog
The GitHub Blog
O
OpenAI News

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром T-TOPS: Как распутать гордиев узел проекта после выхода в прод (меч не понадобится) Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания Автономность как точка невозврата: кто будет субъектом в цифровом будущем Обучение ИИ в «диких» условиях: как рутинные действия превращаются в датасеты Как измерить LLM для задач кибербеза: обзор открытых бенчмарков Где хранить код? Сравнение GitHub, GitLab и Bitbucket Математика объясняет, почему нормальное распределение встречается повсюду Почему ваш FinOps не работает: 12 тезисов от практиков Как подписать проектную документацию УКЭП с использованием бесплатных лицензий Pilot Адаптивное администрирование Sigla Vision Я грузил уран в бочки, а потом 20 лет строил ИТ в атомной отрасли Чем позвонить с Эвереста? История и обзор спутниковой связи. Часть 2 Как языковая модель помогает контролировать качество инструктажей по охране труда в металлургии Как не передать на desktop свой IP в РКН Анатомия SAP Privileges: как устроено управление правами в macOS MoneyDev: Сказка про три главных слова Обновлённый токенизатор видео K-VAE 2.0 от Сбера Как сделать диспетчеризацию дома на 1284 квартиры почти бесплатно Как мы разогнали железную дорогу Мы дали агентам рутину. Теперь надо решить — что делать с освободившимся временем Токсичный контент, промпт-хакинг и защита ИИ — всё о Guardrails для LLM Умный город начинается с точного взгляда: как «Фалькон Тех» меняет пространство к лучшему Навайбкодил приложение для анализа графов Почему Дюну так интересно читать? Упрощаем работу с рутиной или как стать Гендальфом Белым Деконструкция Go: CPU, RAM и что там происходит. Go Assembler база. Часть 1.1 Какие профессии исчезнут из-за ИИ, а какие появятся? И что с этим делать Как мы построили IT-отдел, где хочется расти: архитектурные встречи, прозрачные метрики и книжные подарки Rufler: Делаем из Claude Code автономный рой через один YAML-конфиг Sing-box и белый список приложений Как построить надёжный обмен сообщениями в микросервисах: лучшие практики для enterprise OpenAI строит MLM-пирамиду, а McKinsey и Accenture помогают ей в этом Дом, который не построил Фишер (Часть 2) «Сверхзвуковой математик» против «Вдумчивого логиста»: битва алгоритмов 3D-упаковки Мультимодальные модели – грубый и дорогой инструмент Разговоры ничего не стоят. Код тоже Проверки физических лиц: с кого начнет ФНС Топ-10 бесплатных нейросетей для создания видео в 2026 году Первые слои кода: как наши решения сегодня определяют архитектуру ИИ на десятилетия Разработка нового статического анализатора: PVS-Studio JavaScript Поиск уязвимостей ПО: базовый минимум или роскошный максимум Почему оценка персонала не работает как инструмент управления Как мы разработали ИИ-ассистента и сократили рутину продуктовой команды на 50% Как я ушел из найма, нажарил косточек и продал на маркетплейсах на 168 млн в год Когда 1С:ERP уже внедрена, а нормального производственного плана всё ещё нет Как я сделал Claude мультимодальным, подключив к нему Qwen Omni Как приглашение на вакансию мечты превращается в атаку Infrastructure as Code: философия и лучшие практики IaC Тестируем Yandex Code Assistant на задаче, в которой нужно хранить секреты nxs-universal-chart v3.0: новое поколение универсального Helm-чарта Callback Injection: Техника, которая отправила Microsoft Defender в глухой нокаут «Все идеи на стол»: митап как способ вывести проект из тупика Сегодня я узнал нечто новое о GPU благодаря багу в своей игре Как заставить LLM ̶ ̶г̶а̶л̶л̶ю̶ ̶ эволюционировать Карта событий как фундамент аналитики: практический кейс для E-commerce Что выбрать для AI: x86, ARM или RISC-V? Дайджест железа за март Роль соматических мутаций в развитии аутоиммунных заболеваний: путь к избирательной терапии Mythos от Anthropic — тревожный сигнал для всех, а не только для банков Guardrails для LLM на Java: как приручить промпт‑инъекции и токсичные ответы Green-VLA: как мы собрали VLA-модель для реального антропоморфного робота и не потеряли обобщение Финансовая гонка вооружений: почему умные люди добровольно в ней участвуют Эра ИИ-агентов наступила: выбираем лучшего цифрового сотрудника # Практический опыт внедрения WinCC Redundancy на производственном предприятии Сделал MVP за 3 дня, а потом неделю прикручивал оплату. Оно того стоило? Физика против Маска: почему Starship V3 может оказаться ещё одной катастрофой Нефть Венесуэлы: крупнейшие запасы в мире, но не крупнейшая нефтяная держава JPA 4. Переосмысление Hibernate Почему зеркальная фотокамера Nikon D5 десятилетней давности идеально подошла для миссии «Артемида-2» Проект «Уровень-Спутник» или как мы сделали платформу для гидрологов «Замедлиться, чтобы ускориться»: почему ИИ повышает цену ошибок в требованиях и архитектуре Как с нуля поднять трафик IT-компании на 1657% при бюджете 55 тыс. и выжить Pixel-perfect Downsampling — идеальная отрисовка 50 миллионов точек без потерь
Никто не любит писать тесты, но ИИ может исправить это
Iuliia Savitskaia · 2026-04-16 · via Все публикации подряд на Хабре

Никто не любит писать тесты, но ИИ может исправить это

Средний

9 мин

9.4K

Введение

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

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

Любая инициатива сопровождается вопросом - зачем?

Вопрос «зачем?» часто скрывает другие проблемы и боли разработчиков. Давайте рассмотрим, почему тесты реально не пишут, и как наш подход помог преодолеть эти барьеры.

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

  2. Непонятно, что тестировать и как оценить результат. Отсутствие чёткого понимания, что именно стоит покрывать тестами (вёрстку или бизнес-логику?), и как измерить их реальную пользу. Голые цифры процента покрытия не дают ощущения ценности.

  3. Невидимая работа без признания. Тесты — это работа «под капотом». Она требует времени, но редко приносит аплодисменты и видимый результат, в отличие от новой фичи, что снижает мотивацию.

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

Основная концепция: сместить фокус с написания на ревью.

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

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

Особенности нашего проекта.

Наша команда работает на Vue (3 и старых костылях от Vue 2: Options API, mixins и тд), Vite, Quasar и Vitest. Мы развиваем внутренний интерфейс, насыщенный сложными бизнес-правилами: динамические формы, фичи, включающие/выключающие части страниц, подтягивание клиентских данных из разных ресурсов и детальные проверки доступов. 

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

Практика внедрения: два трека к успеху.

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

Трек 1: Инструкция для разработчика - от настройки до готового теста

Этот трек превращает написание тестов из сложной задачи в простой и повторяемый алгоритм. Ключевая мысль: тесты пишет ИИ, а я выступаю в роли ревьюера.
Давайте рассмотрим пошаговый алгоритм, который изменил отношение нашей команды к тестированию:

Шаг 1. Настраиваем окружение с помощью ИИ.

Первый барьер - «магия конфигов». Делегируем эту задачу ИИ. Даем ему наш package.json и просим подготовить все для тестов.

Пример package.json (Vue 3 + Vite):

{
  "dependencies": {
    "@quasar/extras": "^1.0.0",
    "@quasar/quasar-app-extension-qpdfviewer": "^1.0.0-beta.9",
    "@vue/test-utils": "^2.3.2",
    "axios": "^1.12.0",
    "quasar": "^2.6.0",
    "vue": "^3.3.13",
    "vue-i18n": "^9.3.0-beta.19",
    "vue-router": "^4.0.0",
    "vuex": "^4.0.1",
  },
  "devDependencies": {
    "@babel/preset-typescript": "^7.21.4",
    "@intlify/vite-plugin-vue-i18n": "^3.3.1",
    "@quasar/app-vite": "^1.0.0",
    "typescript": "5.1.6",
  },
  "engines": {
    "node": "^18",
    "npm": ">= 6.13.4"
  }
}

Промт для ИИ: «Вот мой package.json. Настрой, тестовое окружение на Vitest для Vue3. Подготовь список npm-зависимостей (vitest, jsdom, @vue/test-utils@1), команду для установки, конфиг для vite.config.js и скрипты для package.json (test:unit, test:coverage).»

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

Необходимые библиотеки:

  npm install -D vitest jsdom @vue/test-utils @vitest/coverage-v8

Блок в package.json:

"scripts": {
  "test": "vitest --environment happy-dom",
  "coverage": "vitest run --coverage  --environment happy-dom"
}

Блок в vite.config.js:

test: {
    globals: true,
    setupFiles: ['./tests/unit/setupTests.ts'], //о нем далее
    environment: 'happy-dom',
    coverage: {
      reporter: ['html', 'lcov'],
      exclude: [
        '**/extras/pdf**' 
      ]
    }
  }

Шаг 2. Генерируем первый тест с помощью ИИ.

Барьер «чистого листа» - самый сильный. Вместо того чтобы думать, с чего начать, мы даем ИИ код компонента и просим написать тест.

Промт для ИИ: «Напиши unit-тесты для этого Vue 3 компонента на Vitest, используя Vue Test Utils. Вот код UserProfile.vue. Протестируй computed-свойство greeting, метод loadUserData (замокай API) и условный рендеринг для админа. Используй структуру ААА. Вот код компонента UserProfile.vue: »

<template>
  <div>
    <h1>{{ greeting }}</h1>
    <p v-if="user.isAdmin">Доступ: Администратор</p>
    <button @click="loadUserData">Загрузить данные</button>
  </div>
</template>
<script>
import { api } from '@/api';
export default {
  props: { userId: { type: Number, required: true } },
  data() { return { user: null }; },
  computed: {
    greeting() {
      if (!this.user) return 'Привет, Гость!';
      return `Привет, ${this.user.name}!`;
    },
  },
  methods: {
    async loadUserData() {
      try {
        this.user = await api.fetchUser(this.userId);
      } catch (e) {
        this.user = { name: 'Error', isAdmin: false };
      }},
  },
};
</script>

В результате мы получаем 90% готового файла *.spec.js, что убирает главный ступор - «Я не знаю, как использовать эту библиотеку».

Шаг 3. Запускаем и исправляем.

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

import { describe, it, expect, vi } from 'vitest';
import { mount } from '@vue/test-utils';
import UserProfile from './UserProfile.vue'; //исправляем импорт
import { api } from '@/api';

// Мокаем (имитируем) модуль API, чтобы контролировать его поведение в тестах
// Это позволяет нам не делать реальные сетевые запросы
vi.mock('@/api', () => ({ //этот мок можно вынести в файл setupTests.ts'
  api: {
    fetchUser: vi.fn(),
  },
}));
describe('UserProfile.vue', () => {
  it('должен отображать приветствие для гостя, если данные пользователя не загружены', () => {
    // Arrange (Подготовка)
    const wrapper = mount(UserProfile, {
      props: { userId: 1 },
    });
    // Act (Действие)
    const greetingText = wrapper.find('h1').text();
    // Assert (Проверка)
    expect(greetingText).toBe('Привет, Гость!');
    expect(wrapper.find('p').exists()).toBe(false); // Сообщение для администратора не должно отображаться
  });
  it('должен загружать данные пользователя и отображать приветствие для него', async () => {
    // Arrange (Подготовка)
    const mockUser = { name: 'Иван', isAdmin: false };
    // Настраиваем мок-функцию, чтобы она возвращала тестового пользователя
    api.fetchUser.mockResolvedValue(mockUser);    
    const wrapper = mount(UserProfile, {
      props: { userId: 2 },
    });
    // Act (Действие)
    await wrapper.find('button').trigger('click'); // Кликаем на кнопку для загрузки данных    
    // Assert (Проверка)
    expect(api.fetchUser).toHaveBeenCalledWith(2); // Проверяем, что API был вызван с правильным userId
    expect(wrapper.find('h1').text()).toBe('Привет, Иван!');
  });
  it('должен отображать сообщение для администратора, если у пользователя есть права', async () => {
    // Arrange (Подготовка)
    const mockAdmin = { name: 'Анна', isAdmin: true };
    api.fetchUser.mockResolvedValue(mockAdmin);    
    const wrapper = mount(UserProfile, {
      props: { userId: 3 },
    });
    // Act (Действие)
    await wrapper.find('button').trigger('click');
    // Assert (Проверка)
    const adminMessage = wrapper.find('p');
    expect(adminMessage.exists()).toBe(true); // Проверяем, что элемент <p> существует
    expect(adminMessage.text()).toBe('Доступ: Администратор'); // Проверяем его текст
  });
  it('не должен отображать сообщение для администратора, если у пользователя нет прав', async () => {
    // Arrange (Подготовка)
    const mockUser = { name: 'Петр', isAdmin: false };
    api.fetchUser.mockResolvedValue(mockUser);    
    const wrapper = mount(UserProfile, {
      props: { userId: 4 },
    });
    // Act (Действие)
    await wrapper.find('button').trigger('click');
    // Assert (Проверка)
    expect(wrapper.find('p').exists()).toBe(false); // Элемент <p> не должен существовать
  });
});

Шаг 4. Приводим к стандарту с помощью ИИ.

Первые тесты часто бывают хаотичными. Чтобы поддерживать порядок, мы используем ИИ для рефакторинга.
Промт для ИИ: «Отрефактори этот тест по нашим правилам: структура ААА с комментариями // Arrange, // Act, // Assert; используй beforeEach для создания чистого состояния; названия тестов должны следовать шаблону should [result] when [condition].»

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

Трек 2: Инструкция для тимлида - от инициативы до культуры.

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

Шаг 1. Делаем работу видимой: «публичные победы».

Тесты - «невидимая работа». Ваша задача - сделать ее видимой.
Что делать: На дейли, ретро и демо просите разработчиков упоминать статус тестов: «задача готова, пишу тесты», «тесты для компонента X написаны». Это вводит написание тестов в общее информационное поле и придает ему легитимности.

Шаг 2. Интегрируем в CI/CD: «эффект светофора»

Подключите запуск тестов в пайплайн.
Что делать: На первом этапе не блокируйте Merge Request, если тесты упали. Главное - сделать результат видимым. Зеленая галочка от CI становится символом качества и стабильности, а красная - сигналом, который нельзя игнорировать.

Шаг 3. Включаем в цели и техдолг: создаем мотивацию.

Сделайте написание тестов частью рабочих целей.
Что делать: Включите метрики покрытия или просто количество написанных тестов в квартальные/годовые цели разработчиков. Задача «покрыть тестами старый модуль X» становится привлекательным способом легко закрыть часть техдолга и личных KPI.

Шаг 4. Фиксируем результат: набираем критическую массу.

Когда покрытие достигло значимого уровня (у нас это было 50%, цифра может быть любой, главное предотвратить стагнацию), закрепите успех.
Что делать: Настройте Quality Gates в SonarQube или аналогах. Установите правило: «Покрытие нового кода не должно быть ниже n%». Это предотвратит откат назад и закрепит новую норму.

Шаг 5. Внедряем политику по упавшим тестам: «не храним мертвечину»

Упавшие тесты, которые никто не чинит, демотивируют и создают шум.
Что делать: Внедрите простое правило: «Если тест упал и его сложно починить за 15-20 минут - удаляйте его». Лучше потом написать новый, актуальный тест, чем тратить часы на отладку устаревшего. Это сохраняет тестовую базу чистой и полезной.

Что стало лучше: Результаты внедрения.

Мы не просто получили красивую цифру в SonarQube. Мы добились конкретных улучшений:

  • Уверенность в рефакторинге: раньше изменение общей функции было минным полем. Сейчас, если после правок тесты зеленые, мы на 80% уверены, что ничего не сломали.

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

  • Упрощение код-ревью: ревьюер видит, что компонент покрыт тестами, и может сфокусироваться на архитектуре и стиле кода.

Ответ на главный вопрос: Так почему никто не любит писать тесты?

Так удалось ли нам исправить нелюбовь к тестам? Частично. Мы не заставили команду полюбить их. Любовь - чувство иррациональное. Вместо этого мы устранили ключевые барьеры, которые эту нелюбовь вызывали:

  • Страх и неизвестность («Я не знаком с этой библиотекой», «что тестировать») - убрали с помощью ИИ, который дает готовую точку старта.

  • Ощущение бесполезной траты времени («фича нужна вчера», «работа, которую не видят») - убрали, сделав процесс быстрым, а результат видимым.

  • Высокий порог входа («конфиги, моки») - снизили, начав с простейших тестов.

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

Заключение

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

Наша статистика и итоги:

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