Programação assíncrona

Código assíncrono de testes de unidade: Três soluções para testes melhores

Sven Grand

Baixar o código de exemplo

A programação assíncrona se tornou cada vez mais importante durante a última década. Seja para paralelismo baseado na CPU ou simultaneidade baseada em E/S, os desenvolvedores estão empregando a assincronia para ajudar a obter o máximo dos recursos disponíveis e, no fim das contas, fazer mais com menos. Aplicativos cliente com maior capacidade de resposta e aplicativos de servidor mais escalonáveis estão bem ao alcance.

Os desenvolvedores de software aprenderam vários padrões de design para projetar funcionalidades síncronas com eficiência, mas as práticas recomendadas para projetar software assíncronos são relativamente novas, embora o suporte dado por linguagens de programação e bibliotecas para programação paralela e simultânea tenha sido significativamente aprimorado com o lançamento do Microsoft .NET Framework 4 e 4.5. Embora já existam várias orientações para o uso das novas técnicas (consulte “Práticas recomendadas na programação assíncrona” em bit.ly/1ulDCiI e “Conversas: Práticas recomendadas para assincronia” em bit.ly/1DsFuMi [o conteúdo pode estar em inglês]), as melhores práticas para projetar APIs internas e externas para aplicativos e as bibliotecas com recursos de linguagem como async e await e a Task Parallel Library (TPL) ainda são desconhecidas por muitos desenvolvedores.

Esta lacuna não só afeta o desempenho e a confiabilidade de aplicativos e bibliotecas em desenvolvimento, mas também a capacidade que os desenvolvedores têm de testar as soluções desenvolvidas, pois muitas das práticas recomendadas que permitem a criação de projetos assíncronos robustos também facilitam os testes de unidade.

Partindo dessas práticas recomendadas, este artigo vai apresentar formas de projetar e refatorar códigos, aprimorando a capacidade de teste, e demonstrar como isso influencia os testes. As soluções são aplicáveis a códigos que se aproveitam de async e await, assim como códigos baseados em mecanismos de multithreading de nível inferior provenientes de frameworks e bibliotecas mais antigos. Além disso, no processo, as soluções não só serão melhor fatoradas para teste, mais também serão consumidas de forma mais fácil e eficiente pelos usuários do código desenvolvido.

A equipe com que trabalho está desenvolvendo um software para dispositivos de raios X. Nesta área, é crucial que a cobertura de nosso teste de unidade seja sempre a melhor possível. Recentemente, um desenvolvedor me perguntou: “Você sempre nos pressiona a escrever testes de unidade para todo o código. Como faço para escrever testes de unidade razoáveis quando o meu código inicia um outro thread ou usa um temporizador que depois vai iniciar um thread e executá-lo várias vezes?”

Excelente pergunta. Imagine que eu preciso testar o seguinte código:

public void StartAsynchronousOperation()
{
  Message = "Init";
  Task.Run(() =>
    {
      Thread.Sleep(1900);
      Message += " Work";
    });
}
public string Message { get; private set; }

Minha primeira tentativa de escrever um teste para o código não foi muito promissora:

[Test]
public void FragileAndSlowTest()
{
  var sut = new SystemUnderTest();
  // Operation to be tested will run in another thread.
  sut. StartAsynchronousOperation();
  // Bad idea: Hoping that the other thread finishes execution after 2 seconds.
  Thread.Sleep(2000);
  // Assert outcome of the thread.
  Assert.AreEqual("Init Work", sut.Message);
}

Eu preciso que os testes de unidade sejam executados rapidamente e apresentem resultados previsíveis, mas o que escrevi era frágil e lento. O método StartAsynchronousOperation dá início à operação a ser testada em outro thread, e o teste deveria conferir o resultado da operação. O tempo necessário para começar um novo thread ou inicializar um thread existente encontrado no pool de threads Sting, e para executar a operação não é previsível, porque depende de outros processos em execução na máquina de teste. O teste pode falhar vez por outra, quando a suspensão for muito curta e a operação assíncrona ainda não tiver terminado. E então eu me vejo entre a cruz e a espada: Ou tento manter o tempo de espera o mais curto possível, sob risco de um teste frágil, ou aumento o tempo de suspensão para torná-lo mais robusto, e acabo deixando-o ainda mais lento.

Enfrento uma questão semelhante ao testar um código que usa um temporizador:

private System.Threading.Timer timer;
private readonly Object lockObject = new Object();
public void StartRecurring()
{
  Message = "Init";
  // Set up timer with 1 sec delay and 1 sec interval.
  timer = new Timer(o => { lock(lockObject){ Message += " Poll";} }, null,
    new TimeSpan(0, 0, 0, 1), new TimeSpan(0, 0, 0, 1));
}
public string Message { get; private set; }

E é bem provável que o teste apresente os mesmos problemas:

[Test]
public void FragileAndSlowTestWithTimer()
{
  var sut = new SystemUnderTest();
  // Execute code to set up timer with 1 sec delay and interval.
  sut.StartRecurring();
  // Bad idea: Wait for timer to trigger three times.
  Thread.Sleep(3100);
  // Assert outcome.
  Assert.AreEqual("Init Poll Poll Poll", sut.Message);
}

Ao testar uma operação razoavelmente complexa, com várias ramificações de código, eu acabo tendo um número enorme de testes separados. Meus conjuntos de testes ficam cada vez mais lentos a cada novo teste. Os custos de manutenção para estes conjuntos de testes aumentam, porque preciso perder tempo investigando falhas esporádicas. Além disso, conjuntos de testes lentos tendem a ser executados com pouca frequência, gerando menos benefícios. É bem provável que, em determinado momento, eu desista de vez de executar esses testes lentos, com falhas intermitentes.

Os dois tipos de testes mostrados mais cedo também são incapazes de detectar quando a operação gera uma exceção. Como a operação é executada em um thread diferente, as exceções não são propagadas para o thread executor de teste. Isso limita a capacidade dos testes de verificar o comportamento de erro correto do código em teste.

Vou apresentar três soluções gerais para evitar testes de unidade lentos e frágeis, aprimorando o design do código testado, e mostrar como isso permite que os testes de unidade verifiquem exceções. Cada solução tem suas vantagens, desvantagens e limitações. Por fim, vou dar algumas recomendações sobre como escolher a melhor solução para diferentes situações.

Este artigo é sobre teste de unidade. Tipicamente, os testes de unidade testam o código isolado de outras partes do sistema. Uma dessas outras partes do sistema são os recursos de multithreading do SO. Classes e métodos de biblioteca padrão são usados para agendar trabalhos assíncronos, mas o aspecto de multithreading deve ser excluído de testes de unidade, que devem se concentrar na funcionalidade que é executada sem sincronia.

Os testes de unidade para um código assíncrono fazem sentido quando o código contém blocos de funcionalidade sendo executados em um thread, e os testes de unidade devem verificar se os blocos estão trabalhando como esperado. Quando os testes de unidade mostram que a funcionalidade está correta, faz sentido usar estratégias de teste adicionais para revelar problemas de simultaneidade. Existem várias abordagens para testar e analisar um código multithread de forma a encontrar esse tipo de problema (acesse, por exemplo, “Ferramentas e técnicas para identificar problemas de simultaneidade”, em bit.ly/1tVjpll). Testes de estresse, por exemplo, podem gerar excesso de carga em grande parte do sistema, ou até mesmo em todo o sistema. Essas estratégias são sensatas para complementar testes de unidade, mas estão fora do escopo deste artigo. As soluções deste artigo vão mostrar como excluir as partes multithread enquanto a funcionalidade é testada isoladamente, por meio de testes de unidade..

Solução 1: Separar a funcionalidade do multithreading

A solução mais simples para fazer o teste de unidade da funcionalidade envolvida em operações assíncronas é separar essa funcionalidade do multithreading. Gerard Mezaros descreveu essa abordagem no padrão Objeto Humilde (Humble Object) no livro “xUnit Test Patterns” (Addison-Wesley, 2007; sem edição em português). A funcionalidade a ser testada é extraída em uma nova classe em separado e a parte multithreading permanece dentro do Objeto Humilde, que chama a nova classe (consulte Figura 1).

Padrão Objeto Humilde
Figura 1 Padrão Objeto Humilde

O código a seguir mostra a funcionalidade extraída após a refatoração, que é puramente um código síncrono:

public class Functionality
{
  public void Init()
  {
    Message = "Init";
  }
  public void Do()
  {
    Message += " Work";
  }
  public string Message { get; private set; }
}

Antes da refatoração, a funcionalidade foi misturada ao código assíncrono, mas eu a movi para a classe Funcionalidade. Neste momento, essa classe pode ser testada por meio de testes de unidade simples, porque já não contém mais multithreading. Observe que a refatoração tem importância geral, não apenas para testes de unidade: os componentes não devem expor wrappers assíncronos a operações inerentemente síncronas; em vez disso, devem deixar para o chamador determinar se vai descarregar a invocação da operação. No caso de teste de unidade, eu escolho não fazer isso, mas o aplicativo consumidor pode decidir fazê-lo, seja por razões de capacidade de resposta ou de execução paralela. Para mais informações, veja a postagem de blog de Stephen Toub, “Devo Expor Wrappers Assíncronos a Métodos Síncronos?” (o conteúdo pode estar em inglês). (bit.ly/1shQPfn).

Em uma ligeira variação desse padrão, determinados métodos privados da classe SystemUnderTest podem se tornar públicos para permitir que os testes chamem esses métodos diretamente. Nesse caso, não é preciso criar qualquer classe adicional para testar a funcionalidade sem multithreading.

Separar a funcionalidade através do padrão Objeto Humilde é simples e é algo que pode ser feito não só para um código que imediatamente agenda o trabalho assíncrono de uma vez só, mas também para o código que usa temporizadores. Nesse caso, a manipulação de temporizador é mantida no Objeto Humilde e a operação recorrente é levada para a classe Funcionalidade ou para um método público. Uma vantagem dessa solução é que os testes podem verificar diretamente as exceções geradas pelo código em teste. O padrão Objeto Humilde pode ser aplicado sem prejuízo das técnicas usadas para agendar trabalhos assíncronos. O lado negativo dessa solução é que o código no próprio Objeto Humilde não é testado, e que o código em teste precisa ser modificado.

Solução 2: Sincronizar os testes

Se for capaz de detectar a conclusão da operação executada de forma assíncrona, o teste pode evitar duas desvantagens, a fragilidade e a lentidão. Embora execute um código multithread, o teste pode ser confiável e rápido quando é sincronizado com a operação agendada pelo código em teste. O teste pode se concentrar na funcionalidade, enquanto os efeitos negativos da execução assíncrona são minimizados.

Na melhor das hipóteses, o método em teste retorna uma instância de um tipo que será sinalizado quando a operação estiver concluída. O tipo Tarefa, disponível no .NET Framework desde a versão 4, cumpre bem essa tarefa, e com o recurso async/await, disponível a partir do .NET Framework 4.5, fica fácil redigir Tarefas:

public async Task DoWorkAsync()
{
  Message = "Init";
  await Task.Run( async() =>
  {
    await Task.Delay(1900);
    Message += " Work";
  });
}
public string Message { get; private set; }

Esta refatoração representa uma prática recomendada geral, que ajuda tanto no caso de teste de unidade, quanto no consumo geral da funcionalidade assíncrona exposta. Ao retornar uma Tarefa que representa a operação assíncrona, um consumidor do código é capaz de determinar facilmente quando essa operação foi concluída, seja falhando por conta de uma exceção, seja retornando um resultado. 

Com isso, fazer o teste de unidade por método assíncrono se torna algo tão simples quanto testar uma unidade por método síncrono. Assim, para sincronizar facilmente o teste com o código em teste, basta simplesmente invocar o método alvo e esperar que a Tarefa retornada seja concluída. Essa espera por ser feita de forma síncrona (bloqueando o thread de chamada) por meio dos métodos Espera da Tarefa, ou assíncrona (usando continuações para evitar o bloqueio do thread de chamada) com a palavra-chave await, antes de verificar o resultado da operação assíncrona (consulte Figura 2).

Sincronização via async e await
Figura 2 Sincronização via async e await

Para usar o await em um método de teste de unidade, o próprio teste tem que ser declarado com async na assinatura. Nenhuma declaração de suspensão é necessária:

[Test]
public async Task SynchronizeTestWithCodeViaAwait()
{
  var sut = new SystemUnderTest();
  // Schedule operation to run asynchronously and wait until it is finished.
  await sut.StartAsync();
  // Assert outcome of the operation.
  Assert.AreEqual("Init Work", sut.Message);
}

Felizmente, as versões mais recentes dos principais frameworks de teste de unidade — MSTest, xUnit.net e NUnit — suportam testes async e await (acesse o blog de Stephen Cleary em bit.ly/1x18mta). Os executores de teste conseguem suportar testes de Tarefas async e esperar pela conclusão do thread antes de começar a avaliar as instruções assert. Se o executor de teste do framework de teste de unidade não suportar as assinaturas de método de teste de Tarefa async, o teste pode, pelo menos, chamar o método Esperar na Tarefa retornada pelo sistema em teste.

Além disso, a funcionalidade baseada em temporizador pode ser aprimorada com a ajuda da classe TaskCompletionSource (consulte os detalhes no download de código). O teste pode, então, esperar pela conclusão de operações recorrentes específicas:

[Test]
public async Task SynchronizeTestWithRecurringOperationViaAwait()
{
  var sut = new SystemUnderTest();
  // Execute code to set up timer with 1 sec delay and interval.
  var firstNotification = sut.StartRecurring();
  // Wait that operation has finished two times.
  var secondNotification = await firstNotification.GetNext();
  await secondNotification.GetNext();
  // Assert outcome.
  Assert.AreEqual("Init Poll Poll", sut.Message);
}

Infelizmente, às vezes o código em teste não pode usar async e await, como ao testar um código que já foi remetido e nos casos em que, em razão de alteração interruptiva, a assinatura do método em teste não puder ser alterada. Em situações assim, a sincronização precisa ser implementada com outras técnicas. A sincronização pode ser realizada se a classe em teste invocar um evento ou chamar um objeto dependente quando a operação for concluída. O exemplo a seguir demonstra como implementar o teste quando um objeto dependente é chamado:

private readonly ISomeInterface dependent;
public void StartAsynchronousOperation()
{
  Task.Run(()=>
  {
    Message += " Work";
    // The asynchronous operation is finished.
    dependent.DoMore()
  });
}

Um exemplo adicional de sincronização baseada em evento é o download de código.

Neste caso, o teste pode ser sincronizado com a operação assíncrona quando o objeto dependente for substituído por um stub durante o teste (consulte Figura 3).

Sincronização por stub de objeto dependente
Figura 3 Sincronização por stub de objeto dependente

O teste precisa equipar o stub com um mecanismo de notificação thread-safe, porque o código stub é executado em outro thread. No exemplo de código de teste a seguir, um ManualResetEventSlim é usado e o stub é gerado com a estrutura de objetos fictícios RhinoMocks:

// Setup
var finishedEvent = new ManualResetEventSlim();
var dependentStub = MockRepository.GenerateStub<ISomeInterface>();
dependentStub.Stub(x => x.DoMore()).
  WhenCalled(x => finishedEvent.Set());
var sut = new SystemUnderTest(dependentStub);

Dessa forma, o teste pode executar a operação assíncrona e esperar pela notificação:

// Schedule operation to run asynchronously.
sut.StartAsynchronousOperation();
// Wait for operation to be finished.
finishedEvent.Wait();
// Assert outcome of operation.
Assert.AreEqual("Init Work", sut.Message);

Essa solução para sincronizar o teste com os threads testados pode ser aplicada ao código com determinadas características: O código em teste precisa ter um mecanismo de notificação como async e await ou um evento simples, ou chamar um objeto dependente.

Uma grande vantagem da sincronização async e await é a capacidade de propagar qualquer tipo de resultado de volta para o cliente que chama. Um tipo especial de resultado é uma exceção. Sendo assim, o teste lida explicitamente com exceções. Os outros mecanismos de sincronização só conseguem reconhecer falhas de sistema indiretamente, por meio de um resultado defeituoso.

O código com funcionalidade baseada em temporizador pode utilizar async/await, eventos ou chamadas para objetos dependentes para permitir que os testes sincronizem com as operações de temporizador. Todas as vezes que a operação termina, o teste é notificado e pode verificar o resultado (ver exemplos no download de código).

Infelizmente, os temporizadores tornam os teste de unidade lentos mesmo quando se usa uma notificação. A operação recorrente a ser testada geralmente só é iniciada após um certo atraso. O teste ficará mais lento e levará, pelo menos, mais o tempo do atraso. Essa é outra desvantagem relacionada ao pré-requisito de notificação.

Agora, vamos dar uma olhada em uma solução que contorna algumas das limitações das duas soluções anteriores.

Solução 3: Teste em um thread

Para essa solução, o código em teste precisa ser preparado de forma que o depois o teste possa disparar diretamente a execução das operações no mesmo thread do próprio teste. Esta é uma tradução da abordagem da equipe jMock para Java (consulte “Testing Multithreaded Code” em jmock.org/threads.html).

O sistema em teste no exemplo a seguir usa um objeto agendador de tarefas injetado para agendar trabalhos assíncronos. Para demonstrar a capacidade da terceira solução, incluí uma segunda operação que será iniciada após o término da primeira:

private readonly TaskScheduler taskScheduler;
public void StartAsynchronousOperation()
{
  Message = "Init";
  Task task1 = Task.Factory.StartNew(()=>{Message += " Work1";},
                                     CancellationToken.None,
                                     TaskCreationOptions.None,
                                     taskScheduler);
  task1.ContinueWith(((t)=>{Message += " Work2";}, taskScheduler);
}

O sistema em teste é modificado para usar um TaskScheduler separado. Durante o teste, o TaskScheduler “normal” é substituído por um DeterministicTaskScheduler, que permite iniciar operações assíncronas de forma sincronizada (consulte Figura 4).

Usando um TaskScheduler separado em SystemUnderTest
Figure 4 Usando um TaskScheduler separado em SystemUnderTest

O teste a seguir pode executar as operações agendadas no mesmo thread que o próprio teste. O teste injeta o Deterministic­TaskScheduler dentro do código em teste. O DeterministicTaskScheduler não gera um novo thread imediatamente, apenas enfileira tarefas agendadas. Na próxima instrução, o método RunTasksUntil­Idle executa as duas operações em sincronia:

[Test]
public void TestCodeSynchronously()
{
  var dts = new DeterministicTaskScheduler();
  var sut = new SystemUnderTest(dts);
  // Execute code to schedule first operation and return immediately.
  sut.StartAsynchronousOperation();
  // Execute all operations on the current thread.
  dts.RunTasksUntilIdle();
  // Assert outcome of the two operations.
  Assert.AreEqual("Init Work1 Work2", sut.Message);
}

O DeterministicTaskScheduler substitui os métodos do TaskScheduler para fornecer a funcionalidade de agendamento e adiciona, entre outros, o método RunTasksUntilIdle especificamente para fazer testes (consulte o download de código para obter detalhes de implementação do DeterministicTaskScheduler). Como no teste de unidade síncrono, é possível usar stubs para se concentrar em uma única unidade de funcionalidade por vez.

Um código que use temporizadores é problemático não só porque os testes ficam frágeis e lentos. Os testes de unidade ficam mais complicados quando o código usa um temporizador que não esta sendo executado em um thread de trabalho. Na biblioteca de classes .NET Framework existem temporizadores especificamente projetados para uso em aplicativos de interface de usuário, como o System.Windows.Forms.Timer for Windows Forms e o System.Windows.Threading.DispatcherTimer for Windows Presentation Foundation (WPF) (consulte “Comparando as classes de temporizador da biblioteca do .NET Framework” bit.ly/1r0SVic) (conteúdo pode estar em inglês). Eles usam a fila de mensagens de interface de usuário, que não está diretamente disponível durante o teste de unidade. O teste exibido no início deste artigo não vai funcionar com esses temporizadores. O teste precisa criar o bombeamento de mensagens usando, por exemplo, o WPF DispatcherFrame (consulte o exemplo no download de código). Para manter os testes de unidade simples e claros ao implantar temporizadores baseados em interface de usuário, é preciso substituir esses temporizadores durante os testes. Vou introduzir uma interface para temporizadores que permite substituir os temporizadores “reais” por uma implementação para fins específicos de teste. Farei o mesmo para temporizadores baseados em “thread” como System.Timers.Timer ou System.Threading.Timer, porque assim posso aprimorar os testes de unidade em todos os casos. O sistema em teste precisa ser modificado para usar esta interface ITimer:

private readonly ITimer timer;
private readonly Object lockObject = new Object();
public void StartRecurring()
{
  Message = "Init";
  // Set up timer with 1 sec delay and 1 sec interval.
  timer.StartTimer(() => { lock(lockObject){Message += 
    " Poll";} }, new TimeSpan(0,0,0,1));
}

Ao introduzir a interface ITimer, eu posso mudar o comportamento do temporizador durante os testes, como mostrado na Figura 5.

Usar ITimer em SystemUnderTest
Figure 5 Usar ITimer em SystemUnderTest

O esforço extra para definir a interface ITimer vale a pena, porque assim um teste de unidade que verifique o resultado da inicialização e da operação recorrente poderá ser executado de forma muito rápida e confiável em milissegundos:

[Test]
public void VeryFastAndReliableTestWithTimer()
{
  var dt = new DeterministicTimer();
  var sut = new SystemUnderTest(dt);
  // Execute code that sets up a timer 1 sec delay and 1 sec interval.
  sut.StartRecurring();
  // Tell timer that some time has elapsed.
  dt.ElapseSeconds(3);
  // Assert that outcome of three executions of the recurring operation is OK.
  Assert.AreEqual("Init Poll Poll Poll", sut.Message);
}

O DeterministicTimer for escrito especificamente para objetivos de teste. Ele permite que o teste controle o momento exato em que a ação do temporizador é executada, sem espera. A ação é executada no mesmo thread em que o teste (consulte o download de código para obter detalhes de implementação do DeterministicTimer). Para execução do código testado em um contexto “sem testes”, é preciso implementar um adaptador ITimer para um temporizador existente. O download de código traz exemplos de adaptadores para vários temporizadores da biblioteca de classes do framework. A interface ITimer pode ser adaptada às necessidades de situação concreta e só pode conter um subconjunto da funcionalidade integral de temporizadores específicos.

Ao testar um código assíncrono com um DeterministicTaskScheduler ou um DeterministicTimer, você pode desativar facilmente o multithreading durantes os testes. A funcionalidade é executada no mesmo thread que o teste. A interoperação do código de inicialização e o código assíncrono é mantida e pode ser testada. Um teste desse tipo pode, por exemplo, verificar os valores de tempo corretos usados para inicializar um temporizador. As exceções são encaminhadas para os testes, de forma que é possível verificar diretamente o comportamento de erro do código.

Conclusão

Os testes de unidade eficazes de código assíncrono têm três benefícios principais: Os custos de manutenção para testes são reduzidos, os testes são executados mais rapidamente e o risco de não realizar mais os testes é minimizado. As soluções apresentadas neste artigo podem ajudar você a atingir esse objetivo.

A primeira solução, separar a funcionalidade dos aspectos assíncronos de um programa através do Objeto Humilde é o mais genérico. Ele pode ser aplicado em todas as situações, não importando como os threads foram iniciados. Recomendo o uso dessa solução para cenários assíncronos altamente complexos, para uma funcionalidade complexa, ou para combinações de ambos. Eis um bom exemplo do princípio de design de separação de preocupações (consulte bit.ly/1lB8iHD).

A segunda solução, que sincroniza o teste com a conclusão do thread testado, pode ser aplicada quando o código em teste fornece um mecanismo de sincronização como o async e await. Essa solução faz sentido quando os pré-requisitos do mecanismo de notificação são preenchidos de alguma forma. Se possível, use a elegante sincronização async e await quando threads sem temporizador forem iniciados, porque as exceções são propagadas para o teste. Os testes com temporizadores podem usar await, eventos ou chamadas para objetos dependentes. Esses testes podem ficar lentos quando os temporizadores tiverem longos atrasos ou intervalos.

A terceira solução usa o DeterministicTaskScheduler e o DeterministicTimer, evitando assim a maioria das limitações e desvantagens das outras soluções. É preciso algum esforço para preparar o código em teste, mas a cobertura atingida no teste de unidade pode ser muito ampla. Os testes para código com temporizadores podem ser executados muito rapidamente, sem espera por tempos de atraso e intervalo. Além disso, as exceções são propagadas para os testes. Sendo assim, essa solução leva a conjuntos de testes de unidades robustos, rápidos e elegantes, combinados a uma ampla cobertura de código.

Essas três soluções podem ajudar desenvolvedores de software a evitar as armadilhas dos testes de unidade de códigos assíncronos. Elas podem ser usadas para criar conjuntos de testes de unidade rápidos e robustos e cobrir uma ampla gama de técnicas de programação paralela.


Sven Grand é arquiteto de software de engenharia de qualidade da unidade de negócios de diagnósticos por raios X da Philips Healthcare. Ele foi “mordido pelo bicho do teste” anos atrás, quando ouviu falar pela primeira vez do desenvolvimento orientado por testes em uma conferência da Microsoft em 2001. Você pode contatá-lo pelo e-mail sven.grand@philips.com.

Agradecemos aos seguintes especialistas técnicos pela revisão deste artigo: Stephen Cleary, James McCaffrey, Henning Pohl e Stephen Toub