Para adicionar à excelente resposta da emddudley, o maior ganho que você pode obter ao zombar do serviço é poder testar o que deve acontecer, o serviço não funciona corretamente. O pseudocódigo de teste pode ser algo como isto:
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());
}
}
E agora sua implementação foi editada com este novo requisito
public int getAge(Service s, Logger l)
{
try {
int age = s.execute(empId);
return age;
}
catch(Exception ex) {
l.LogError(ex);
throw;
}
}
Em outros cenários, é mais provável que você precise responder a respostas mais complicadas. Se o serviço forneceu o processamento do cartão de crédito, você precisaria responder a Sucesso, Serviço indisponível, Cartão de crédito expirado, Número inválido, etc. Ao zombar do serviço, você pode garantir que responde a esses cenários da maneira adequada à sua situação. Nesse caso, você deve simular a entrada / saída do serviço e o feedback obtido ao saber que o código consumidor funcionará para todas as saídas conhecidas é realmente significativo e valioso.
EDIT: Acabei de notar que você deseja poder zombar sem modificar o método existente. Para fazer isso, o locate("ageservice");
método precisaria ser alterado para suportar objetos simulados nos testes e localizar o serviço real quando estiver pronto. Essa é uma variação do padrão do localizador de serviço que abstrai a lógica para recuperar a implementação do serviço que você está usando. Uma versão rápida disso pode ser assim:
public Service locate(string serviceToLocate) {
if(testEnvironment) // Test environment should be set externally
return MockService(serviceToLocate);
else
return Service(serviceToLocate);
}
Minha recomendação, no entanto, seria mover as dependências de serviço para os Construtores:
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());
}
}
Agora, o método getAge não tem mais a responsabilidade de procurar o serviço, pois foi extraído da classe deixando completamente uma implementação semelhante 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;
}
}