JavaRush /Курсы /Spring Test /Где живут реальные поломки 🧠

Где живут реальные поломки 🧠

Spring Test
1 уровень , 1 лекция
Открыта

1. Начинать нужно с действий пользователя 💉

Очень соблазнительно строить карту рисков «по учебнику»: controller, service, repository, дальше как-нибудь разберёмся. Это лучше, чем ничего, но слишком сухо. Пользователь не живёт внутри ваших пакетов. Он делает действия: читает статью, создаёт черновик, отправляет текст на ревью, пытается изменить вложение, получает отказ в доступе. Если начинать не с действий, а сразу с папок, легко потерять смысл и начать тестировать архитектурные ярлыки вместо реального поведения.

Для проекта ContentHub это особенно важно из-за трёх разных контуров доступа. У нас есть public-контур для чтения опубликованных статей, editor-контур для авторской работы с материалами и admin-контур для модерации и публикации. Одно и то же доменное действие может звучать похоже, но риск у него будет разный в зависимости от зоны.

«Посмотреть статью» для public означает фильтр по PUBLISHED. «Посмотреть статью» для editor может означать доступ к собственному черновику. «Посмотреть статью» для admin может включать служебные поля и расширенный контекст. Если этого различия не видеть, карта рисков сразу становится плоской.

2. Один flow уже показывает, сколько здесь дыр

Давайте возьмём типичный сценарий без деталей реализации. Сначала editor создаёт DRAFT. Потом отправляет статью на ревью. Дальше admin либо одобряет её, либо отклоняет. После одобрения статья становится видимой в public API. На бумаге это почти линейный бизнес-процесс. Внутри backend-а — целая цепочка проверок, выборок, маппинга данных, ограничений доступа и внешних вызовов .

Уже на одном этом маршруте видно несколько разных классов багов. Editor может отправить на ревью статью не из того статуса. Admin может опубликовать материал в обход модерации. Репозиторий может вернуть запись без фильтра по статусу. Public endpoint может отдать 500 там, где клиент ждёт 404. Security может перепутать роли, а ownership-проверка — авторов. Внешняя модерация может таймаутить или вернуть странный вердикт. Всё это разные поломки, хотя снаружи они вписаны в один и тот же продуктовый flow.

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

3. Entity несёт в себе сразу несколько осей риска

Полезно один раз посмотреть на доменную сущность не как на «просто entity», а как на носитель рисков. У статьи в ContentHub есть поля, которые участвуют сразу в нескольких правилах: slug, автор, статус, категория, контент, дата публикации. Одно поле — и несколько потенциальных источников поломок.

class Article {

    // Участвует в HTTP-контракте (URL/параметр) и в uniqueness-правилах в базе/домене
    private String slug;

    // Нужен для owner-based access: кто именно владеет статьёй
    private String authorUsername;

    // Ключевое поле workflow: из какого состояния можно перейти в какое
    private ArticleStatus status;

    // Влияет на фильтрацию/поиск/выдачу (и часто на доступность в разных контурах)
    private String categoryCode;
}

slug участвует в HTTP-контракте и в требованиях к уникальности. authorUsername нужен для owner-based access. status определяет и workflow, и публичную видимость. categoryCode влияет на фильтрацию и поисковую выдачу. То есть статья — это не «одна сущность, один риск». Это узел, где пересекаются правила видимости, доступа, данных и публичного поведения.

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

4. Controller-риск — это риск контракта

На слое контроллеров чаще всего ломается не сама бизнес-идея, а договор с внешним миром. Путь, метод, параметры, status code, форма успешного ответа, форма ошибки, правила валидации — всё это живёт на HTTP-границе. Поэтому controller-риск лучше формулировать не как «сломался контроллер», а как «клиенту отдали не тот контракт».

Типичный пример для public API: endpoint по slug нашёл статью, но не проверил, что она опубликована. Или не нашёл статью и вернул 500, потому что необработанное исключение вылетело наверх. Или принял request без обязательного поля, потому что забыли валидацию. Во всех трёх случаях приложение может идеально стартовать. Поломка станет видимой только тогда, когда внешний клиент упрётся именно в эту границу.

Важно помнить и ещё одну вещь. Controller часто показывает дефект не того слоя, который его породил. Public endpoint может вернуть черновик не потому, что сам контроллер «плохой», а потому что сервис или репозиторий отдали ему неподходящие данные. Поэтому карта рисков не занимается поиском виноватого. Она помогает понять, где именно нужно сделать поведение наблюдаемым, чтобы потом не стрелять наугад.

5. Service-риск — это смысл приложения

Service-layer — это место, где ваш backend перестаёт быть просто web-обёрткой над базой и начинает вести себя как продукт. Здесь живут правила: из какого статуса можно куда перейти, кто может редактировать материал, когда нужно обращаться к внешней модерации, что делать с результатом модерации, когда статья становится публичной. Именно поэтому service-риск обычно связан с нарушением инвариантов, а не с формой JSON.

class ArticleWorkflowService {

    void submitForReview(Article article) {
        // Критичное бизнес-правило: на ревью можно отправлять только черновик
        if (article.getStatus() != ArticleStatus.DRAFT) {
            // Ошибка именно доменного инварианта, а не “техническая проблема контроллера”
            throw new IllegalStateException("INVALID_STATUS");
        }

        // Изменение статуса — точка риска: дальше всё поведение будет зависеть от этого значения
        article.setStatus(ArticleStatus.IN_REVIEW);
    }
}

Даже в таком коротком коде сразу видно, что именно является предметом риска. Не маршрут URL, не способ сериализации и не SQL-диалект базы. Риск в том, что правило перехода может быть забыто, ослаблено или случайно изменено во время рефакторинга. И тогда backend будет делать «технически допустимое», но продуктово неправильное действие.

Сервисы часто ещё и склеивают несколько слоёв сразу. Например, они читают статью из репозитория, проверяют ownership, вызывают moderation client, обновляют статус и сохраняют результат. Поэтому одна и та же сервисная операция может стоять на стыке бизнес-риска, data-риска, security-риска и integration-риска. Это не делает карту сложнее ради сложности. Это просто честное описание того, как реально устроен обычный backend-проект.

6. Data-риск — это не только про сохранность

Когда речь заходит о слое данных, новички часто видят только CRUD. Но большая часть дорогих багов здесь вообще не про save(). Они про выборки, сортировки, ограничения и соответствие модели реальной схеме. Public API может показывать лишние записи не потому, что контроллер плохой, а потому что запрос вернул слишком широкий набор данных. Editor может не увидеть свою статью из-за странного WHERE. Два одинаковых slug могут появиться из-за дыры в ограничении уникальности. И всё это — риски слоя данных.

interface ArticleRepository {

    // Опасный/универсальный метод: легко забыть добавить фильтрацию по статусу на уровне запроса
    Optional<Article> findBySlug(String slug);

    // Более “честный” контракт для public-видимости: статус фиксируется в самом запросе
    Optional<Article> findBySlugAndStatus(String slug, ArticleStatus status);
}

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

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

7. Security-риск идёт поперёк слоёв 🔒

У безопасности есть неприятная особенность: она редко укладывается в один пакет и очень часто маскируется под «обычный бизнес-баг». Editor не может отредактировать статью — это может быть корректный запрет по статусу, а может быть ошибочный запрет по ownership. Анонимный пользователь получил доступ к admin-действию — это уже провал role-based access. Симптомы похожи на обычные поломки поведения, а цена намного выше.

Для ContentHub удобно сразу разделять две ветки security-риска. Первая — role-based access: кто вообще имеет право заходить в public, editor и admin-зоны. Вторая — owner-based access: может ли конкретный editor работать именно с этой статьёй. Они не заменяют друг друга. Можно идеально разделить роли и всё равно забыть проверить владельца ресурса. Можно честно проверить владельца и при этом случайно открыть служебный endpoint анониму.

Вот почему security-карта начинается не со слова Spring Security. Она начинается со слова «кто может» и продолжается вопросом «при каких условиях». Это важный сдвиг оптики. И он потом очень сильно помогает выбирать тесты без хаоса.

8. Integration-риск — на границе с внешним миром

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

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

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

9. Вся карта на одном маршруте

Если собрать всё вместе, ContentHub перестаёт выглядеть как абстрактный набор слоёв и начинает читаться как маршрут прохождения запроса и доменного действия. Именно здесь карта становится по-настоящему полезной: вы начинаете видеть не только «где лежат классы», но и «где живёт шанс поломки».

flowchart LR
    %% Входная точка: любой внешний клиент (браузер, мобильное приложение, Postman)
    Client["Клиент / frontend / Postman"] --> Controller["Controller: HTTP-контракт"]

    %% Контроллер передаёт управление в доменную/прикладную логику
    Controller --> Service["Service: workflow и правила"]

    %% Сервис ходит в слой данных за чтением/записью
    Service --> Repository["Repository: запросы и сохранение"]
    Repository --> DB[(Database)]

    %% Security “прошивает” несколько слоёв: проверки могут быть и на границе, и внутри сервиса
    Security["Security: role + owner rules"] -.-> Controller
    Security -.-> Service

    %% Внешние границы: здесь живут timeouts, падения контрактов и недоступность
    Service --> Moderation["Moderation client"]
    Service --> Storage["File storage"]
    Service --> Notification["Notification sender"]

А теперь тот же взгляд в более практической форме — на одном сценарии «submit → approve → public read».

Шаг сценария Где чаще всего живёт риск Что именно ломается Как это выглядит снаружи
POST /submit controller + service неверный статус, пропущенная валидация, запрет не сработал клиент получает не тот status code или допускается недопустимое действие
проверка владельца service + security нет ownership-check editor меняет чужую статью
смена статуса на IN_REVIEW service сломан workflow статья идёт по неправильной ветке процесса
вызов модерации integration таймаут, неверный контракт, странный вердикт submit зависает или переводит статью не туда
одобрение администратором service + security разрешили публикацию из неверного статуса или не тому пользователю нарушен процесс публикации
public read по slug repository + controller нет фильтра по PUBLISHED, неверный 404/500, сломан JSON аноним видит лишнее или получает сломанный контракт

Как только такая таблица оказывается перед глазами, backend перестаёт быть «чёрным ящиком». А это и есть цель карты рисков. Она не заменяет тесты, но делает выбор теста осмысленным. Без неё уровни проверок будут выглядеть случайным набором техник. С ней — как нормальная инженерная система.

10. Типичные ошибки при построении карты рисков 🚧

Ошибка №1: строить карту только по пакетам, а не по действиям пользователя.
Так вы быстро получите аккуратную, но бесполезную схему. Полезная карта начинается с product flow и только потом ложится на controller, service, repository и внешние границы.

Ошибка №2: складывать всё в одну корзину «service-логика».
Часть багов действительно живёт в сервисах, но HTTP-контракт живёт на web-границе, а выборки и ограничения — в слое данных. Чем раньше вы перестанете называть всё подряд «сервисной проблемой», тем точнее будут ваши будущие тесты.

Ошибка №3: не разделять role-based и owner-based доступ.
Они звучат похоже, но ломаются по-разному. Один дефект открывает endpoint не той роли, другой — не тому владельцу ресурса. Это разные риски, и их нельзя склеивать.

Ошибка №4: считать внешние интеграции «просто технической деталью».
Именно на технических границах очень любят жить неприятные сбои, очень похожие на production. Интерфейс делает код чище, но не убирает сам источник риска.

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

1
Задача
Spring Test, 1 уровень, 1 лекция
Недоступна
Кодовый каркас и карта рисков по действиям пользователя
Кодовый каркас и карта рисков по действиям пользователя
1
Задача
Spring Test, 1 уровень, 1 лекция
Недоступна
Программная карта рисков для submit-flow
Программная карта рисков для submit-flow
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ