Как написать unit-тест проверяющий что метод был вызван?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Unit-тесты для проверки вызова методов в Unity/C#
При написании unit-тестов в Unity (обычно с использованием NUnit и фреймворков для мокинга вроде NSubstitute или Moq) проверка вызова метода — одна из фундаментальных задач. Вот как это правильно организовать.
Основные подходы
В зависимости от архитектуры вашего кода и используемых инструментов, есть несколько способов проверить вызов метода:
- Использование фреймворков для мокинга (предпочтительный способ)
- Ручные моки-заглушки (для простых случаев)
- Проверка побочных эффектов (косвенная проверка)
Пример с NSubstitute (рекомендуемый фреймворк)
Установите NSubstitute через Package Manager или добавьте в *.csproj. Далее создайте тест:
using NSubstitute;
using NUnit.Framework;
public class DamageServiceTests
{
// Интерфейс или абстрактный класс, который мы подменяем
public interface IHealthSystem
{
void ApplyDamage(int amount);
}
// Класс, который тестируем
public class DamageService
{
private IHealthSystem _healthSystem;
public DamageService(IHealthSystem healthSystem)
{
_healthSystem = healthSystem;
}
public void DealCriticalDamage()
{
// Логика расчета урона...
int calculatedDamage = 100;
_healthSystem.ApplyDamage(calculatedDamage);
}
}
[Test]
public void DealCriticalDamage_Should_Call_ApplyDamage_WithCorrectValue()
{
// Arrange
var mockHealthSystem = Substitute.For<IHealthSystem>();
var damageService = new DamageService(mockHealthSystem);
// Act
damageService.DealCriticalDamage();
// Assert
// Проверяем, что метод был вызван ровно один раз с аргументом 100
mockHealthSystem.Received(1).ApplyDamage(100);
// Альтернативные проверки:
// mockHealthSystem.Received().ApplyDamage(Arg.Any<int>()); // Любое значение int
// mockHealthSystem.Received().ApplyDamage(Arg.Is<int>(x => x > 50)); // С условием
// mockHealthSystem.DidNotReceive().ApplyDamage(Arg.Any<int>()); // Отрицательная проверка
}
}
Пример с ручной заглушкой (Manual Mock)
Если не хотите использовать сторонние библиотеки:
using NUnit.Framework;
public class ManualMockTests
{
public interface ISaveSystem
{
void SaveGame(string data);
}
public class GameManager
{
private ISaveSystem _saveSystem;
public GameManager(ISaveSystem saveSystem)
{
_saveSystem = saveSystem;
}
public void CompleteLevel()
{
// Какая-то логика...
_saveSystem.SaveGame("LevelCompleted");
}
}
// Ручной мок-класс
public class MockSaveSystem : ISaveSystem
{
public int CallCount { get; private set; }
public string LastCallArgument { get; private set; }
public void SaveGame(string data)
{
CallCount++;
LastCallArgument = data;
}
}
[Test]
public void CompleteLevel_Should_Call_SaveGame_Once()
{
// Arrange
var mockSaveSystem = new MockSaveSystem();
var gameManager = new GameManager(mockSaveSystem);
// Act
gameManager.CompleteLevel();
// Assert
Assert.AreEqual(1, mockSaveSystem.CallCount, "Метод должен быть вызван ровно 1 раз");
Assert.AreEqual("LevelCompleted", mockSaveSystem.LastCallArgument);
}
}
Ключевые принципы для качественных тестов
- Инверсия зависимостей — тестируемый класс должен зависеть от абстракций (интерфейсов), а не от конкретных реализаций
- Изоляция тестов — каждый тест должен быть независим от других и от внешних состояний
- Четкие названия — имена тестов должны точно отражать проверяемое поведение (паттерн
MethodName_Scenario_ExpectedResult) - AAA-паттерн — четкое разделение на этапы Arrange-Act-Assert
Особенности для MonoBehaviour в Unity
Для MonoBehaviour-классов подход аналогичен, но нужно учитывать:
using UnityEngine;
using NSubstitute;
using NUnit.Framework;
public class PlayerControllerTests
{
public class PlayerController : MonoBehaviour
{
private IAudioService _audioService;
public void Initialize(IAudioService audioService)
{
_audioService = audioService;
}
public void PlayJumpSound()
{
_audioService.PlaySound("jump");
}
}
[Test]
public void PlayJumpSound_Should_Trigger_AudioService()
{
// Arrange
var mockAudioService = Substitute.For<IAudioService>();
var player = new GameObject().AddComponent<PlayerController>();
player.Initialize(mockAudioService);
// Act
player.PlayJumpSound();
// Assert
mockAudioService.Received(1).PlaySound("jump");
}
}
Распространенные ошибки и решения
-
Ошибка: Тестирование внутренних приватных методов.
Решение: Тестируйте только публичный API. Если нужно проверить приватный метод — пересмотрите дизайн класса. -
Ошибка: Проверка вызова метода Unity (например,
Transform.Translate).
Решение: Создайте абстракцию над Unity-компонентами через интерфейсы. -
Ошибка: Использование реальных зависимостей в тестах.
Решение: Всегда подменяйте внешние зависимости моками.
Использование мокинга для проверки вызовов методов делает тесты быстрыми, стабильными и изолированными, что является краеугольным камнем качественной unit-тестируемости кода в Unity-проектах.