Я новичок в модульном тестировании и пишу модульные тесты для приложений .NET C#. Для создания макетов я использую пакет MOQ NuGet в своем тестовом проекте. У меня есть интерфейс с двумя свойствами:
public interface IUserInput
{
public int FirstInput { get; set; }
public int SecondInput { get; set; }
}
У меня есть класс, который работает с этим интерфейсом:
public class CalculatorCore : ICalculatorBase
{
private readonly IUserInput _input;
public CalculatorCore(IUserInput input)
{
_input = input;
}
public int AddByUserInput()
{
_input.SecondInput += 2;
return _input.FirstInput + _input.SecondInput;
}
}
Для этого класса я написал тестовый класс и тестовый метод:
[TestClass]
public class CalculatorCoreTest
{
[TestMethod]
public void AddByUserInput()
{
Mock<IUserInput> userInputMock = new Mock<IUserInput>();
userInputMock.SetupProperty(f => f.FirstInput, 1);
userInputMock.SetupProperty(f => f.SecondInput, 1);
var calculatorCore = new CalculatorCore(userInputMock.Object);
var actual = calculatorCore.AddByUserInput();
Assert.AreEqual(actual, 4);
}
}
Этот тест пройден, и у меня нет никаких проблем.
Однако, когда я удаляю строку
userInputMock.SetupProperty(f => f.SecondInput, 1);
и измените часть кода утверждения на
Assert.AreEqual(actual, 3);
тест не пройден, и мой результат равен 1. Это происходит потому, что когда я добавляю 2 к свойству фиктивного объекта, оно все равно равно 0;
Почему я не могу изменить значение свойства фиктивного объекта, которое не было настроено?
Без настройки свойство невозможно установить. Чтобы сделать это визуально, оно будет действовать как следующее свойство:
int SecondInput { set {} get => default; }
Поэтому он всегда будет возвращать 0
. Если вы хотите, чтобы все имитируемые свойства действовали как реальные свойства, вы также можете вызвать SetupAllProperties()
на своем макете, чтобы быстро настроить желаемое поведение.
Этот вопрос, а также ответ от NotFound предполагают, что тестируемый API может быть улучшен.
Вместо того, чтобы использовать Moq для предоставления экземпляра IUserInput
, почему бы вам не создать правильный класс?
public sealed class UserInput : IUserInput
{
public int FirstInput { get; set; }
public int SecondInput { get; set; }
}
Это делает тест более простым и надежным:
[Fact]
public void AddByUserInput()
{
var userInput = new UserInput { FirstInput = 1, SecondInput = 1 };
var calculatorCore = new CalculatorCore(userInput);
var actual = calculatorCore.AddByUserInput();
Assert.Equal(4, actual);
}
В самом деле, если вы это сделали, то зачем вам интерфейс?
Какова цель интерфейса с двумя свойствами? Интерфейс — это способ внедрить полиморфизм в код C#, но сколько существует допустимых способов реализовать свойство чтения/записи? Вам действительно нужно изменить это поведение?
Как следует из ответа NotFound, существует несколько неправильных способов реализации интерфейса типа IUserInput
, но в большинстве случаев только один полезный способ.
Таким образом, если у вас нет веских причин сохранить интерфейс, измените CalculatorCore
, чтобы в качестве входных данных использовался конкретный класс:
public class CalculatorCore : ICalculatorBase
{
private readonly UserInput _input;
public CalculatorCore(UserInput input)
{
_input = input;
}
public int AddByUserInput()
{
_input.SecondInput += 2;
return _input.FirstInput + _input.SecondInput;
}
}
или, возможно, решить проблему Feature Envy , применив рефакторинг Move Method:
public sealed class UserInput
{
public int FirstInput { get; set; }
public int SecondInput { get; set; }
public int AddByUserInput()
{
SecondInput += 2;
return FirstInput + SecondInput;
}
}
Это еще больше упрощает тест:
[Fact]
public void AddOnUserInput()
{
var userInput = new UserInput { FirstInput = 1, SecondInput = 1 };
var actual = userInput.AddByUserInput();
Assert.Equal(4, actual);
}
Это хороший пример того, как написание тестов проливает полезный свет на проектирование тестируемой системы.
Благодарю за ваш ответ. Я полностью согласен с вашими соображениями. Кроме того, я ценю, что вы нашли время ответить мне. Я просто хотел привести пример, иллюстрирующий проблему, возникающую при изучении модульного тестирования. При написании реального кода я буду придерживаться всех необходимых принципов.
Благодарю за ваш ответ. Ваш ответ прояснил мою проблему и способы ее решения.