Para agregar a la excelente respuesta de emddudley, la mayor ganancia que puede obtener al burlarse del servicio es poder probar lo que debería suceder si el servicio no funciona correctamente. El pseudocódigo de prueba podría verse así:
public int AgeMinimumValue_LogsServiceError_Test()
{
ClassUnderTest uut = new ClassUnderTest();
MockService service = new MockService();
service.Throws(new TimeoutException());
MockLogger logger = new MockLogger();
try {
int age = uut.getAge(service, logger);
Assert.Fail("Exception was not raised by the class under test");
}
catch (TimeoutException) {
Assert(logger.LogError().WasCalled());
}
}
Y ahora su implementación ha sido editada con este nuevo requisito
public int getAge(Service s, Logger l)
{
try {
int age = s.execute(empId);
return age;
}
catch(Exception ex) {
l.LogError(ex);
throw;
}
}
En otros escenarios, es más probable que necesite responder a respuestas más complicadas. Si el servicio proporcionó el procesamiento de la tarjeta de crédito, deberá responder a Éxito, Servicio no disponible, Tarjeta de crédito vencida, Número no válido, etc. Al burlarse del servicio, puede asegurarse de responder a estos escenarios de una manera adecuada para su situación. En este caso, debe burlarse de la entrada / salida del servicio y la retroalimentación que obtiene al saber que el código de consumo funcionará para todas las salidas conocidas es realmente significativa y valiosa.
EDITAR: Acabo de notar que quieres poder burlarte sin modificar el método existente. Para hacer esto, el locate("ageservice");
método debería cambiarse para admitir objetos simulados en las pruebas y localizar el servicio real una vez que esté listo. Esta es una variación del patrón del localizador de servicios que abstrae la lógica para recuperar la implementación del servicio que está utilizando. Una versión rápida de eso puede verse así:
public Service locate(string serviceToLocate) {
if(testEnvironment) // Test environment should be set externally
return MockService(serviceToLocate);
else
return Service(serviceToLocate);
}
Sin embargo, mi recomendación sería mover las dependencias del servicio a los Constructores:
public int AgeMinimumValue_LogsServiceError_Test()
{
MockService service = new MockService();
service.Throws(new TimeoutException());
MockLogger logger = new MockLogger();
ClassUnderTest uut = new ClassUnderTest(service, logger);
try {
int age = uut.getAge();
Assert.Fail("Exception was not raised by the class under test");
}
catch (TimeoutException) {
Assert(logger.LogError().WasCalled());
}
}
Ahora, el método getAge ya no tiene la responsabilidad de buscar el servicio, ya que se ha extraído completamente de la clase dejando una implementación similar a esta:
public int getAge()
{
try {
// _service is a private field set by the constructor
int age = _service.execute(empId);
return age;
}
catch(Exception ex) {
// _logger is a private field set by the constructor
_logger.LogError(ex);
throw;
}
}