Вариант 1: использование шва
Самый простой способ - это рефакторинг этого метода со швом:
public void ExecuteSomeCommand()
{
this.CreateCommand(someInt, SomeEnum.EnumValue).Execute();
}
// Your seam
protected virtual ICommand CreateCommand(int someInt,
SomeEnum someEnum)
{
return new MyCommand(someInt, SomeEnum.EnumValue);
}
Таким образом, вы можете перехватить создание оператора 'new', расширив этот класс.Когда вы делаете это вручную, это может выглядеть так:
public FakeSomeService : SomeService
{
public int SomeInt;
public SomeEnum SomeEnum;
protected override Command CreateCommand(int someInt,
SomeEnum someEnum)
{
this.SomeInt = someInt;
this.SomeEnum = someEnum;
return new FakeCommand();
}
private sealed class FakeCommand : Command
{
public override void Execute() { }
}
}
Этот поддельный класс можно использовать в ваших тестовых методах.
Вариант 2: Разделить поведение и данные
Лучшим способом было бы отделение данных от поведения.У вашей команды есть и данные (сообщение), и поведение (обработка этого сообщения).Если вам разрешено делать такие изменения в вашей кодовой базе: разделите это, например, определив команды и обработчики команд.Вот пример:
// Define an interface for handling commands
public interface IHandler<TCommand>
{
void Handle(TCommand command);
}
// Define your specific command
public class MyCommand
{
public int SomeInt;
public SomeEnum SomeEnum;
}
// Define your handler for that command
public class MyCommandHandler : IHandler<MyCommand>
{
public void Handle(MyCommand command)
{
// here your old execute logic
}
}
Теперь вы можете использовать внедрение зависимостей , чтобы внедрить обработчик в класс, который вы хотите протестировать.Этот класс теперь будет выглядеть следующим образом:
public class SomeService
{
private readonly IHandler<MyCommand> handler;
// Inject a handler here using constructor injection.
public SomeService(IHandler<MyCommand> handler)
{
this.handler = handler;
}
public void ExecuteSomeCommand()
{
this.handler.Handle(new MyCommand
{
SomeInt = someInt,
SomeEnum = someEnum
});
}
}
Поскольку вы теперь отделили данные от поведения, будет очень легко создать поддельный обработчик команд (или создать его с помощью насмешек Rhino), который проверяет,правильная команда была отправлена обработчику.Вручную это выглядело бы так:
public class FakeHandler<TCommand> : IHandler<TCommand>
{
public TCommand HandledCommand { get; set; }
public void Handle(TCommand command)
{
this.HandledCommand = command;
}
}
Этот поддельный обработчик можно повторно использовать в вашем проекте модульного тестирования.Тест с использованием FakeHandler
может выглядеть следующим образом:
[TestMethod]
public void SomeTestMethod()
{
// Arrange
int expected = 23;
var handler = new FakeHandler<MyCommand>();
var service = new SomeService(handler);
// Act
service.ExecuteSomeCommand();
// Assert
Assert.AreEqual(expected, handler.HandledCommand.SomeInt);
}
Отделение данных от поведения не только делает ваше приложение более тестируемым.Это делает ваше приложение более устойчивым к изменениям.Например, сквозные задачи могут быть добавлены к выполнению команд, без необходимости вносить изменения в какой-либо обработчик в системе.Поскольку IHandler<T>
является интерфейсом с одним методом, очень просто написать декоратор , который может обернуть каждый обработчик и добавить такие вещи, как ведение журнала, окончание аудита, профилирование, проверка, обработка транзакций, отказоустойчивостьоб этом можно прочитать в этой статье .