Модульное тестирование пустого метода, который создает новый объект - PullRequest
8 голосов
/ 11 мая 2011

У меня есть метод, подобный следующему:

public void ExecuteSomeCommand()
{
    new MyCommand( someInt, SomeEnum.EnumValue ).Execute();
}

Я хотел бы проверить, что значение enum, которое передается в конструктор создаваемого мной объекта ICommand, является правильным значением.Есть ли способ сделать это с Rhino.Mocks?

Ответы [ 2 ]

11 голосов
/ 11 мая 2011

Вариант 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> является интерфейсом с одним методом, очень просто написать декоратор , который может обернуть каждый обработчик и добавить такие вещи, как ведение журнала, окончание аудита, профилирование, проверка, обработка транзакций, отказоустойчивостьоб этом можно прочитать в этой статье .

2 голосов
/ 11 мая 2011

Ни о чем я не знаю. Самое близкое, что приходит мне в голову, - это использовать фабрику, а затем создать StrictMock этой фабрики. Примерно так:

readonly ICommandFactory factory;

public Constructor(ICommandFactory factory)
{
    this.factory = factory;
}
public void ExecuteSomeCommand()
{
    factory.Create( someInt, SomeEnum.EnumValue ).Execute();
}

тогда вы можете поместить ожидания при вызове на Create().

НТН

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...