После запуска вложенного теста родитель должен решить, продолжать ли сценарий. Что именно сообщает булево значение, возвращаемое t.Run, и как его правильно интерпретировать?
t.Run возвращает true, если вложенный тест завершился успешно или был пропущен, и false, если он завершился с ошибкой. Это значение позволяет родительскому тесту принять решение о дальнейшем управлении сценарием, но само по себе не отменяет уже зафиксированный провал.
Обычные тесты долгое время запускались как независимые функции, поэтому результат каждой функции определялся самим тестовым фреймворком. По мере появления групп тестов и table-driven tests понадобился механизм, который не только давал отдельные имена и отчёты для кейсов, но и позволял программно получить результат вложенного теста.
t.Run решает обе задачи: запускает subtest в изолированном контексте *testing.T и возвращает родителю сведения о его результате. Это удобнее, чем пытаться передавать статус через внешние переменные.
Если родительский тест игнорирует возвращаемое значение t.Run, он не может отличить успешный subtest от проваленного на уровне управляющей логики. Сам родитель при этом всё равно будет помечен как проваленный, если вложенный тест вызвал Error, Fail или аналогичный метод.
Риск возникает, когда после subtest нужно выполнить условные действия: прекратить зависимые проверки, выбрать запасной путь диагностики или корректно агрегировать несколько результатов. Неверная трактовка значения может привести либо к лишним ошибкам, либо к преждевременному прекращению полезной диагностики.
Возвращаемое значение отражает итог выполнения функции subtest: false означает, что внутри него зафиксирована ошибка. true означает успешное завершение либо пропуск теста, поэтому это значение нельзя трактовать как «тело точно выполнилось без пропуска».
t.Run дожидается завершения subtest, прежде чем вернуть результат. Если subtest вызвал t.Parallel, его выполнение может быть отложено правилами планировщика тестов, но t.Run всё равно возвращает значение только после завершения этого subtest.
Минимальный пример управления сценарием:
Здесь passed используется только для управления дальнейшими действиями. Если первый subtest провален, родительский тест уже считается неуспешным; return лишь предотвращает проверки, которые зависят от невыполненного условия.
Не следует без необходимости превращать каждый false в немедленное завершение родителя. В table-driven тесте часто полезнее запустить все независимые subtests и получить полный отчёт. Ранний выход оправдан, когда дальнейшие проверки логически бессмысленны или могут породить вторичные, вводящие в заблуждение ошибки.
В интеграционном тесте сначала проверяется подготовка тестовой схемы, затем несколько проверок запросов. Команда рассматривала два варианта: всегда запускать все проверки, получая много вторичных ошибок, или немедленно завершать родительский тест при провале подготовки, теряя дополнительную диагностику независимо работающих проверок.
Выбранным решением стала проверка результата t.Run для подготовки и условный выход только при её провале. Остальные проверки запускались отдельными subtests, если подготовка успешна. Это отделило первичную причину сбоя от зависимых ошибок и сохранило полный отчёт там, где проверки действительно независимы.
false необходимость немедленно завершить родительский тест?Нет. false только сообщает о провале subtest; решение о завершении принимает код родителя. Немедленный выход уместен для зависимых шагов, но для независимых проверок он уменьшает объём диагностики.
t.Run, если subtest был пропущен?Пропущенный subtest считается успешным с точки зрения возвращаемого значения: t.Run возвращает true. Поэтому булево значение отвечает на вопрос о наличии ошибки, а не на вопрос о том, выполнялась ли основная проверяемая логика.
t.Run для итогового решения?Да. Родитель может сохранить результаты нескольких независимых subtests и выполнить общую реакцию после их завершения. При этом нельзя вручную подменять стандартную семантику: если subtest уже помечен как проваленный, родительский тест останется проваленным независимо от того, какое пользовательское действие будет выполнено позже.