АШШумилкин А. О.ОАиП · ПиТПМ
ПиТПМ · Лекция 01

Лекция №1. Методология обеспечения качества ПО. Жизненный цикл дефектов. Принципы тест-дизайна.

3 467 слов12 разделов0 иллюстраций
↓ Скачать Markdown

Введение

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

§1.1 Введение в обеспечение качества (QA) и его место в разработке ПО

Обеспечение качества (Quality Assurance, QA) представляет собой комплексную систему организационных, методологических и технических мер, направленных на гарантию того, что разрабатываемое ПО будет соответствовать установленным требованиям на всех стадиях жизненного цикла.

Важно различать два ключевых понятия, которые часто ошибочно отождествляют:

  • Quality Assurance (QA) — ориентировано на упреждение дефектов через совершенствование процессов разработки.
  • Quality Control (QC) — ориентировано на выявление дефектов в уже готовом артефакте.

В рамках дисциплины «Поддержка и тестирование программных модулей» мы фокусируемся на практических аспектах QC и тестирования, однако понимание контекста QA необходимо для формирования профессионального мышления. QA внедряется на этапе планирования и сбора требований; именно там закладывается фундамент качества, когда стоимость исправления ошибки минимальна.

На графике показано, как стоимость исправления дефекта растёт в зависимости от того, на каком этапе он был обнаружен.

Этапы и их значения:

1. Требования (1 у.е.)

— самый дешёвый момент. Дефект здесь — это просто неточность в тексте документа. Исправить формулировку стоит почти ничего.

2. Проектирование (5 у.е.)

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

3. Кодирование (15 у.е.)

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

4. Тестирование (40 у.е.)

— QC нашёл баг. Теперь нужно: завести задачу, вернуть её разработчику, тот разбирается в старом коде, исправляет, тестировщик перепроверяет. Цикл занимает дни.

5. Эксплуатация (100 у.е.)

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

Совет

Если на этапе требований один человек правит один документ, то на этапе эксплуатации над одним дефектом работают разработчик, тестировщик, DevOps, служба поддержки — и всё это в режиме пожара.

Методология QA базируется на фундаментальном принципе, сформулированном Эдсгером Дейкстрой:

Принцип Дейкстры

«Тестирование может показать наличие дефектов, но никогда не докажет их отсутствие».

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

Современное место QA в разработке определяется тремя стратегическими подходами:

  1. Shift-Left (Сдвиг влево). Активное вовлечение инженеров по качеству на самых ранних этапах (анализ требований, архитектурные решения). Цель — предотвратить появление дефектов в коде из-за неоднозначности или противоречивости требований.
  2. Shift-Right (Сдвиг вправо). Мониторинг поведения системы в реальной эксплуатационной среде (production). Включает сбор телеметрии, A/B-тестирование и анализ логов для выявления сбоев, которые не проявились в тестовой среде.
  3. Непрерывная интеграция и доставка (CI/CD). Качественные практики встраиваются в пайплайн сборки. Каждое изменение в репозитории (commit) автоматически запускает набор регрессионных тестов.

Для понимания практической реализации QA в коде рассмотрим пример написания простого, но качественного с точки зрения тестопригодности метода на C#.

Тестопригодность

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

public class OrderProcessor
{
    public void ProcessOrder(int orderId)
    {
        // Жесткая зависимость от конкретной реализации БД
        var connection = new SqlConnection("...");
        // Обработка заказа...

        // Такой код невозможно протестировать без реальной БД,
        // что делает тест медленным и ненадежным.
    }
}

В данном примере класс OrderProcessor зависит от абстракции (IOrderRepository), а не от конкретной реализации. Это позволяет в тестовой среде (например, с использованием фреймворка Moq) подставить «заглушку» (mock-объект), который имитирует работу базы данных в памяти, что обеспечивает быстрое, изолированное и детерминированное тестирование.

Вывод

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

§1.2 QA, QC и Testing: в чем разница и куда смотреть?

В профессиональной среде термины Quality Assurance (QA), Quality Control (QC) и Testing часто используются как взаимозаменяемые, однако с методологической точки зрения они описывают принципиально разные уровни управления качеством.

Понимание этих различий является обязательным условием для выстраивания корректных коммуникаций внутри команды разработки и грамотного распределения ответственности.

Классическая трактовка, закрепленная в стандартах ISO 9000, разграничивает эти понятия следующим образом:

1. Quality Assurance (QA)

— обеспечение качества. Это процессно-ориентированная деятельность. Ее объектом является не сам программный код, а процесс его создания. QA отвечает на вопрос: «Правильно ли мы строим продукт?». QA включает в себя: аудит требований, выбор методологии разработки, проведение код-ревью, стандартизацию оформления кода, обучение персонала, настройку пайплайнов CI/CD.

2. Quality Control (QC)

— контроль качества. Это продуктно-ориентированная деятельность. Ее объектом является готовый артефакт (модуль, сборка, релиз). QC отвечает на вопрос: «Соответствует ли готовый продукт заявленным спецификациям?». QC не предотвращает ошибки, а выявляет расхождения между ожидаемым и фактическим поведением системы.

3. Testing

— динамическое тестирование. Это конкретная практическая деятельность по запуску исполняемого кода на наборе тестовых данных с целью обнаружения дефектов. Testing отвечает на вопрос: «Что происходит с системой в заданных условиях?». Тестирование является подмножеством QC, но не исчерпывает его полностью.

Для наглядного понимания разницы обратимся к примеру на C#. Рассмотрим метод, вычисляющий скидку в зависимости от суммы заказа.

Пример кода для анализа (метод CalculateDiscount):

csharp

public class DiscountService
{
    // Требование: скидка 10% при сумме > 1000, иначе 0%
    public double CalculateDiscount(double orderAmount)
    {
        if (orderAmount > 1000.0)
        {
            return orderAmount * 0.1; // Логическая ошибка!
            // метод возвращает сумму скидки,
            // но по названию должен возвращать процент
        }
        return 0.0;
    }
}

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

Роль Testing (динамическое тестирование). Тестировщик создает набор тестовых случаев, запускает метод и сравнивает фактические результаты с ожидаемыми:

csharp

[Test]
public void CalculateDiscount_OrderAmountGreaterThan1000_Returns10Percent()
{
    // Arrange
    var service = new DiscountService();
    double input = 1500.0;
    double expected = 10.0; // Мы ожидаем именно 10%, а не 150

    // Act
    double actual = service.CalculateDiscount(input);

    // Assert
    // Тест упадет, так как фактический результат — 150.0
    Assert.AreEqual(expected, actual);
}

Тест упал. Дефект зафиксирован. Это работа Testing — показать наличие несоответствия.

Роль QC (контроль качества). QC-инженер не ограничивается запуском метода. Он выполняет статический анализ кода и требований:

  1. Анализ кода выявляет, что у метода отсутствует проверка на отрицательные значения входного параметра (orderAmount < 0). Это потенциальный Failure.
  2. Анализ требований показывает, что спецификация неоднозначна: в ней не указано, что считать скидкой — абсолютное значение в денежных единицах или процент. QC инициирует запрос к бизнес-аналитику на уточнение требования и вносит это уточнение в документацию.
  3. QC проверяет, соответствует ли именование метода корпоративному стандарту C# (PascalCase соблюден, но семантически название CalculateDiscount вводит в заблуждение).

Таким образом, QC охватывает как динамическую проверку (Testing), так и статическую проверку артефактов, еще до этапа выполнения кода.

Роль QA (обеспечение качества). QA-инженер действует на уровне мета-процесса. Он анализирует саму ситуацию: почему ошибка с возвратом денег вместо процента была допущена в принципе? QA задает системные вопросы:

  • Есть ли у нас в проекте чек-лист для код-ревью, который включает проверку соответствия типов возвращаемых значений?
  • Требуется ли при написании бизнес-логики обязательное покрытие модульными тестами до слияния ветки (pull request)?
  • Проводилось ли обучение разработчиков по работе с десятичными типами (double vs decimal) для денежных расчетов?

Ответная реакция QA — внедрение нового правила: «Отныне все методы, возвращающие денежные величины, должны использовать тип decimal, а не double, и должны быть задокументированы в XML-комментариях».

ХарактеристикаQA (Обеспечение качества)QC (Контроль качества)Testing (Тестирование)
ОбъектПроцесс разработкиГотовый продукт (код, доки)Исполняемая программа
Временной фокусПроактивный (до появления кода)Реактивный (после создания кода)Реактивный (во время выполнения)
Главный вопросКак предотвратить ошибки?Соответствует ли это спецификации?Падает ли это на тестовых данных?
Тип действийАудит, регламенты, обучениеИнспекции, статический анализЗапуск, ввод данных, сравнение результатов
Пример в C#Правило: использовать decimal для валютПроверка наличия обработки ArgumentOutOfRangeExceptionВызов метода с -100 и наблюдение за исключением

Профессиональный рост

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

§1.3 Классические принципы тестирования (7 принципов ISTQB)

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

Семь принципов ISTQB формируют концептуальный каркас профессионального мышления. Они учат:

  • Смирению (мы не можем проверить всё)
  • Аналитике (ищем опасные модули)
  • Адаптивности (меняем тесты и подходы под проект)
  • Системности (смотрим не только на код, но и на ценность продукта для пользователя)

§1.4 Понятие «Дефект» (Bug, Error, Fault, Failure)

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

Стандарт IEEE 1044-2009

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

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

1. Error (Ошибка человека)

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

2. Fault (Дефект / Баг)

— это статическое несоответствие программного артефакта установленным требованиям или ожидаемому поведению. Fault возникает в момент, когда Error материализуется в коде, документации или конфигурации.

3. Failure (Сбой / Отказ)

— это наблюдаемое отклонение фактического поведения работающей системы от ожидаемого. Failure возникает, когда исполняемый код активирует существующий Fault в определенных условиях.

Данная цепочка имеет критическое практическое значение: один Fault при определенных условиях может порождать множество Failure, и наоборот — несколько Fault могут быть причиной одного наблюдаемого сбоя.

Рассмотрим метод обработки платежа:

csharp

/// <summary>
/// Класс для расчета комиссии банковского перевода
/// Требование: комиссия составляет 2.5% от суммы, но не менее 50 рублей
/// </summary>
public class CommissionCalculator
{
    public decimal Calculate(decimal amount)
    {
        // Fault: разработчик допустил ошибку в условии проверки минимума.
        // Вместо amount >= 2000 (так как 2.5% от 2000 = 50) он написал:
        if (amount <= 2000) // ОШИБКА: должно быть >= 2000, а не <=
        {
            return 50.0m; // Возвращает фиксированную комиссию для ВСЕХ сумм ≤ 2000
        }
        return amount * 0.025m;
    }
}

Теперь рассмотрим, как Error разработчика превращается в Fault в коде, а затем — в Failure при выполнении:

csharp

// Клиентский код, использующий класс
var calculator = new CommissionCalculator();

// Ситуация 1: сумма 5000 рублей (комиссия 125 рублей)
// Вернет 125.0m - корректно
decimal commission1 = calculator.Calculate(5000m);

// Ситуация 2: сумма 1500 рублей (комиссия должна быть 50 рублей)
// Вернет 50.0m - корректно
decimal commission2 = calculator.Calculate(1500m);

// Ситуация 3: сумма 2500 рублей (комиссия должна быть 62.5 рубля)
// Вернет 50.0m - FAILURE!
decimal commission3 = calculator.Calculate(2500m);
// Система вернула 50 рублей вместо 62.5. Наблюдаемый сбой произошел.

Классификация дефектов

Дефекты классифицируются по серьезности и приоритету.

Серьезность

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

УровеньОписаниеПример
BlockerДальнейшее тестирование невозможно; система не запускаетсяОшибка компиляции, падение приложения при старте
CriticalКритическая бизнес-функция не работаетНевозможность оформить заказ, ошибка авторизации
MajorЗначительная функция работает с ограничениямиНеверный расчет скидки для определенной категории
MinorНезначительное отклонение от спецификацииОпечатка в тексте интерфейса
TrivialКосметический дефектНестандартный отступ между элементами

Приоритет

— это бизнес-характеристика, определяющая очередность и срочность исправления дефекта.

Важно

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

Помимо серьезности и приоритета, дефекты могут классифицироваться по:

  • Типу: функциональные, нефункциональные (производительность, безопасность, удобство использования), логические, синтаксические.
  • Фазе возникновения: ошибки требований, ошибки проектирования, ошибки кодирования, ошибки конфигурации.
  • Стабильности воспроизведения: воспроизводимые (100% повторяются при одних и тех же условиях), невоспроизводимые (зависят от состояния среды, таймингов, гонок потоков).

§1.5 Жизненный цикл дефекта (Bug Life Cycle)

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

Статусы жизненного цикла:

1. New

— начальный статус, дефект зарегистрирован тестировщиком.

2. Assigned

— дефект назначен конкретному разработчику.

3. Open (In Progress)

— разработчик приступил к исправлению.

4. Fixed (Resolved)

— разработчик завершил исправление и внес изменения в репозиторий.

5. etest

— тестировщик выполняет проверку исправления.

6. Verified (Closed)

— тестировщик подтвердил, что дефект устранен.

7. Reopened

— проблема сохраняется, дефект возвращается разработчику.

8. Rejected

— разработчик отклоняет дефект (не является ошибкой).

9. Duplicate

— аналогичный дефект уже зарегистрирован.

10. Deferred (Won't Fix)

— исправление откладывается на будущие версии.

Для формализации жизненного цикла в коде используется перечисление статусов:

csharp

/// <summary>
/// Статусы жизненного цикла дефекта
/// </summary>
public enum BugStatus
{
    New, Assigned, Open, Fixed, Retest, Verified,
    Closed, Reopened, Rejected, Duplicate, Deferred
}

public class BugReport
{
    public BugStatus Status { get; private set; }

    public bool TransitionTo(BugStatus newStatus)
    {
        // Проверка допустимости перехода согласно жизненному циклу
        if (Status == BugStatus.New && (newStatus == BugStatus.Assigned || newStatus == BugStatus.Rejected))
        { Status = newStatus; return true; }
        if (Status == BugStatus.Assigned && (newStatus == BugStatus.Open || newStatus == BugStatus.Rejected))
        { Status = newStatus; return true; }
        if (Status == BugStatus.Open && newStatus == BugStatus.Fixed)
        { Status = newStatus; return true; }
        if (Status == BugStatus.Fixed && newStatus == BugStatus.Retest)
        { Status = newStatus; return true; }
        if (Status == BugStatus.Retest && (newStatus == BugStatus.Verified || newStatus == BugStatus.Reopened))
        { Status = newStatus; return true; }
        if (Status == BugStatus.Verified && (newStatus == BugStatus.Closed || newStatus == BugStatus.Reopened))
        { Status = newStatus; return true; }
        if (Status == BugStatus.Reopened && (newStatus == BugStatus.Assigned || newStatus == BugStatus.Open))
        { Status = newStatus; return true; }
        return false; // Недопустимый переход
    }
}

Распределение ролей

  • Тестировщик отвечает за создание дефекта в статусе New, его проверку в статусе Retest и окончательное закрытие в Verified или Closed.
  • Разработчик отвечает за анализ, исправление и перевод дефекта в Fixed.
  • Менеджер по тестированию участвует в назначении дефектов (переход New → Assigned), принятии решений о Rejected и Deferred.
  • Продакт-менеджер определяет приоритеты и может влиять на решение об откладывании дефектов.

§1.6 Атрибуты хорошего баг-репорта

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

Хороший баг-репорт

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

№АтрибутОписаниеПример корректного заполнения
1IDУникальный идентификаторBUG-1247
2SummaryКраткое, но информативное описаниеПри вводе отрицательной суммы метод CalculateDiscount возвращает 0 вместо выброса исключения
3Steps to ReproduceПоследовательность действий для воспроизведения1. Открыть приложение Calculator<br>2. Ввести -100<br>3. Нажать «Рассчитать скидку»
4Actual ResultФактическое поведение системыМетод возвращает 0.0. Исключение не выбрасывается.
5Expected ResultОжидаемое поведение системыДолжен выбрасываться ArgumentOutOfRangeException
6EnvironmentОкружениеWindows 10 Pro (x64), .NET 6.0, Chrome 120
7SeverityТехническая характеристикаMajor
8PriorityБизнес-характеристикаHigh
9AttachmentsСкриншоты, видео, логиscreenshot_bug_1247.png
10Additional ContextДополнительная информацияДефект воспроизводится только при отрицательных значениях. Требование: сумма заказа должна быть строго больше 0.

Типичные ошибки новичков

  • Использование расплывчатых формулировок («что-то не так», «вроде бы ошибка»)
  • Смешивание нескольких дефектов в одном отчете
  • Отсутствие шагов воспроизведения
  • Указание фактического результата, который невозможно проверить
  • Игнорирование указания окружения

§1.7 Введение в тест-дизайн: зачем нужны техники

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

От интуиции к инженерии

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

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

Главная цель тест-дизайна — не увеличить количество тестов, а повысить их информативность. Один хорошо спроектированный тестовый случай может найти больше дефектов, чем сто случайных тестов.

Все техники тест-дизайна делятся на три группы:

1. Техники черного ящика (Black-box techniques)

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

2. Техники белого ящика (White-box techniques)

— тестирование основано на анализе внутренней структуры кода: логики ветвлений, циклов, путей выполнения.

3. Техники серого ящика (Grey-box techniques)

— промежуточный подход, при котором тестировщик имеет частичный доступ к внутреннему устройству системы (базам данных, API, логам).

§1.8 Техника «Черный ящик» (Black-box testing)

Черный ящик (Black-box testing)

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

Эквивалентное разбиение (Equivalence Partitioning)

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

Пример: метод проверки возраста для доступа к контенту (требование: доступ от 18 до 65 лет включительно).

csharp

public bool IsAgeValid(int age)
{
    if (age < 18) return false;
    if (age > 65) return false;
    return true;
}

Классы эквивалентности:

  • Валидный: 18 ≤ age ≤ 65
  • Невалидный: age < 18
  • Невалидный: age > 65

Анализ граничных значений (Boundary Value Analysis)

Основан на эмпирическом наблюдении, что наибольшее количество дефектов концентрируется на границах классов эквивалентности. Для границы A-B проверяются: A-1, A, A+1, B-1, B, B+1.

ЗначениеТипОжидаемый результат
17 (A-1)Невалидный, чуть ниже границыfalse
18 (A)Валидный, сама границаtrue
19 (A+1)Валидный, чуть выше границыtrue
64 (B-1)Валидный, чуть ниже границыtrue
65 (B)Валидный, сама границаtrue
66 (B+1)Невалидный, чуть выше границыfalse

Таким образом, 6 тестов вместо 97 потенциальных значений (от 0 до 100).

Таблицы принятия решений (Decision Tables)

Используются, когда результат зависит от комбинации нескольких условий (обычно 3–5).

Пример: выдача кредита зависит от возраста, дохода и наличия просрочек.

Условие / Правило12345678
Возраст ≥ 18ДаДаДаДаНетНетНетНет
Доход ≥ 30000ДаДаНетНетДаДаНетНет
Нет просрочекДаНетДаНетДаНетДаНет
РешениеОдобренОтказОтказОтказОтказОтказОтказОтказ

Попарное тестирование (Pairwise Testing)

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

Пример: интернет-магазин. Товары имеют параметры: Бренд (A, B, C), Размер (S, M, L), Цвет (Красный, Синий, Зеленый).

Полное число комбинаций: 3 × 3 × 3 = 27. Попарное тестирование сокращает до 9 тестов:

ТестБрендРазмерЦвет
1ASКрасный
2AMСиний
3ALЗеленый
4BSСиний
5BMЗеленый
6BLКрасный
7CSЗеленый
8CMКрасный
9CLСиний

Тестирование на основе состояний (State Transition Testing)

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

Пример: платежная система. Заказ может находиться в состояниях: Новый → Оплачен → Отправлен → Доставлен.

Текущее состояниеСобытиеНовое состояниеОжидаемый результат
НовыйОплата прошлаОплаченДеньги списаны, статус изменен
НовыйОтмена заказаОтмененЗаказ удален
ОплаченПодтверждение складаОтправленТрек-номер присвоен
ОплаченОтмена заказаВозвратИнициирован возврат средств
ОтправленДоставка подтвержденаДоставленЗаказ завершен
ДоставленЛюбое событиеДоставленИзменения невозможны

Ограничения черного ящика

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

§1.9 Техника «Серый ящик» (Grey-box testing)

Серый ящик (Grey-box testing) представляет собой промежуточный подход к тестированию, сочетающий элементы черного и белого ящика. Тестировщик имеет частичный доступ к внутреннему устройству системы (базам данных, API-интерфейсам, логам выполнения), но не погружается в детали реализации на уровне исходного кода.

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

Рассмотрим суть серого ящика на простом примере:

csharp

public bool CreateOrder(Order order)
{
    if (order.TotalAmount <= 0) return false;
    _repository.Save(order);
    return true;
}

Подход черного ящика: вызвать метод, получить true и считать тест пройденным.

Подход серого ящика: вызвать метод, получить true, а затем дополнительно проверить базу данных:

csharp

// Проверка внешнего поведения (черный ящик)
bool result = service.CreateOrder(order);
Assert.IsTrue(result);

// Проверка внутреннего состояния (серый ящик)
var savedOrder = repository.GetById(order.Id);
Assert.IsNotNull(savedOrder);
Assert.AreEqual("Иванов", savedOrder.CustomerName);

Именно эта вторая проверка отличает серый ящик от черного. Без нее дефект (например, поле CustomerName сохранилось как null) остался бы незамеченным.

Сценарии применения серого ящика

  • Интеграционное тестирование
  • Тестирование баз данных (целостность данных)
  • Тестирование API и веб-сервисов
  • Тестирование безопасности
  • Тестирование миграций данных

Требования к специалисту

Серый ящик требует от тестировщика дополнительных знаний: необходимо понимать структуру баз данных, уметь читать логи, знать основы SQL и форматы обмена данными (JSON, XML).

§1.10 Сравнение подходов и где они применяются

ХарактеристикаЧерный ящикСерый ящик
Доступ к кодуНетЧастичный
Доступ к БД/APIНетДа
Уровни тестированияСистемное, приемочноеИнтеграционное
Требуемые знанияТребования, спецификацииSQL, логи, API
Обнаруживаемые дефектыНесоответствие требованийОшибки интеграции, целостности данных
СложностьНизкаяСредняя

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

Главное правило

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