2011-05-11 10 views
8

tengo un método como el siguiente:método de vacío Prueba de la unidad que crea un nuevo objeto

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

me gustaría probar que el valor de enumeración que se pasa al constructor del objeto ICommand I' m crear es el valor correcto. ¿Hay alguna manera de que pueda hacer esto con Rhino.Mocks?

+0

¿El constructor lanza una excepción si el valor enum es inesperado? – MattDavey

+0

El propio constructor no lanza. Los argumentos se pasan por el método Execute al modelo de vista de una vista que se crea a través de una clase NavigationManager. El nuevo modelo de vista luego usa la enumeración para alguna propiedad de visualización y si se trata de un valor inesperado, arroja. – alimbada

Respuesta

11

Opción 1: Utilice una costura

La forma más fácil es refactorizar ese método con una costura:

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); 
} 

esta manera se puede interceptar la creación del operador 'nuevo' extendiendo esta clase. Al hacer esto a mano, podría verse así:

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() { } 
    } 
} 

Esta clase falsa se puede utilizar en sus métodos de prueba.


Opción 2: Comportamiento independiente y datos

Una mejor manera serían para separar los datos del comportamiento. El comando tiene tanto datos (el mensaje) como el comportamiento (manejo de ese mensaje). Si puede realizar dicho cambio en su base de código: separe esto, por ejemplo, definiendo comandos y controladores de comando. Aquí está un ejemplo:

// 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 
    } 
} 

Ahora puede utilizar la inyección de dependencia para inyectar un controlador en la clase que desea probar. Esta clase ahora se verá así:

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 
     }); 
    } 
} 

Desde ahora separado los datos de la conducta, que será muy fácil crear un controlador de comandos falsa (o crearlo usando Rhino se burla) que comprueba si el comando correcto fue enviado al controlador. Manualmente este sería el siguiente:

public class FakeHandler<TCommand> : IHandler<TCommand> 
{ 
    public TCommand HandledCommand { get; set; } 

    public void Handle(TCommand command) 
    { 
     this.HandledCommand = command; 
    } 
} 

Este controlador falsos pueden ser reutilizados a través de su proyecto de prueba de unidad. Una prueba que utiliza este FakeHandler podría tener este aspecto:

[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); 
} 

La separación de los datos a partir del comportamiento no sólo hace que su aplicación más comprobable. Hace que su aplicación sea más resistente para cambiar. Por ejemplo, las preocupaciones transversales se pueden agregar a la ejecución de comandos, sin la necesidad de hacer cambios a cualquier controlador en el sistema.Como el IHandler<T> es una interfaz con un único método, es muy fácil escribir un decorator que puede ajustar todos los controladores y agregar cosas como registro, seguimiento de auditoría, creación de perfiles, validación, manejo de transacciones, improvisación de errores, etc. Puede leer más al respecto en this article.

+0

Fui con su primera sugerencia. La clase falsa no era necesaria sin embargo. Pude usar AssertWasCalled (...) de Rhino.Mocks para verificar que los parámetros correctos hayan pasado al método CreateCommand (...). ¡Gracias! :-) – alimbada

+0

@Alimbada: Gracias. Pero no te olvides de los controladores. Piense en cómo esto puede simplificar sus pruebas y su diseño. – Steven

2

Ninguna que yo sepa. Lo más parecido que se me viene a la mente es usar una fábrica, luego crear un StrictMock de esa fábrica. Algo así:

readonly ICommandFactory factory; 

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

a continuación, puede colocar las expectativas de invocación a Create().

HTH

+0

Probablemente necesitará una fábrica de comando por comando específico si hace esto. En el caso del OP, esa clase necesita un 'IMyCommandFactory'. Esto probablemente sería menos ideal. Todavía +1 para usar (constructor) inyección de dependencia. – Steven

Cuestiones relacionadas