terça-feira, 27 de outubro de 2009

Testes de unidade em aplicações multi-thread

No meu último artigo eu trouxe uma solução para um problema que eu passei aqui na Sr. Nimbus, já esse post não trás boas novidades não, apenas mostra uma característica do Visual Studio 2010 (e acredito que anteriores) de uma história com testes de unidade e aplicações multi-thread.

Vamos ao nosso cenário básico: imagine uma classe responsável por disparar alguns workers em background de acordo com a tarefa que cada um deve executar. Esses procedimentos por si geram notificações através de eventos que podem ser tratados por outras classes da sua solução. Nada demais, o código para isso (bem simplificado) poderia ser assim:

public struct InfoTarefa
{
public string Info;
public DateTime horaExecução;
}

public class NegocioTeste
{
public void ExecutaTarefa(object informação)
{
InfoTarefa tarefa;
tarefa.Info = (string)informação;
tarefa.horaExecução = DateTime.Now;

if (TarefaCompleta != null)
TarefaCompleta(tarefa);
}

public delegate void NotificarTarefaCompleta(InfoTarefa tarefa);

public event NotificarTarefaCompleta TarefaCompleta;
}


public class ThreadEmBackground
{
public List tarefas;
private NegocioTeste negocio;

public ThreadEmBackground(NegocioTeste n)
{
negocio = n;
}

public void ProcessaTarefas()
{
foreach (string tarefa in tarefas)
{
Thread t = new Thread(negocio.ExecutaTarefa);
t.Start(tarefa);
t.Join();
}
}
}
Por simplicidade deste exemplo eu faço um Join() da thread criada, esperando o fim da execução, para deixar esse exemplo bem controlado, mas na aplicação de verdade isso é diferente.
Para testar o código acima basta fazermos o setup correto das pré-condições, colocando as verificações do resultado na rotina de tratamento do evento TarefaCompleta.

[TestMethod]
public void TesteBásico()
{
//SETUP
List tarefas = new List();
tarefas.Add("Tarefa 01");
NegocioTeste negocio = new NegocioTeste();
ThreadEmBackground gerenteThreads = new ThreadEmBackground(negocio);
gerenteThreads.tarefas = tarefas;
negocio.TarefaCompleta += RecebeInfoTarefa;

Assert.AreEqual(1, gerenteThreads.tarefas.Count);

// Execução do curso básico
gerenteThreads.ProcessaTarefas();
}

public void RecebeInfoTarefa(TestesMultiThread.InfoTarefa tarefa)
{
Assert.AreEqual("Tarefa 01", tarefa.Info);
Assert.IsTrue(tarefa.horaExecução > DateTime.Now.AddMinutes(-1));
}
Mas se a verificação falhasse no tratamento do nosso evento, que acontece em outra thread, olhe na figura abaixo o que veríamos:



Um Error aparece no lugar do Failed, com um ícone de warning (?!) e a mensagem “The agent process was stopped while the test was running”. Se clicarmos duas vezes em cima do Error para analisar o que aconteceu (ação já automática com a falha usual do assert), a mesma mensagem será exibida, o que não ajuda em nada (no caso do Failed vemos a stack trace).

Aí você descobre que deve clicar em “Test run error” para ver a stack trace, onde conseguirá a informação “One of the background threads threw exception: Microsoft.VisualStudio.TestTools.UnitTesting.AssertFailedException: Assert.Fail failed. Problema na validação”.

Isto é, por se tratar de uma thread em background, a maneira como o Visual Studio trata as verificações é diferente do comportamento usual, o que eu achei ruim. Isso mata alguém, não vejo maiores problemas, mas fico imaginando se trará algum impacto durante a automação dos testes ou em momentos diferentes, o que me deixa um pouco inseguro.

Outra coisa que eu tentei através do Visual Studio e não consegui ver uma maneira fácil (a não ser codificar), foi a possibilidade de executar o testes orientando o VS para disparar várias threads de uma só vez, me ajudando a testar condições de corrida.

Para ser super sincero, pode ser que o Visual Studio tenha recursos nessa área de testes para aplicações multi-thread, mas em algumas pesquisas rápidas que fiz não encontrei muitas referências e um funcionário da MS diz no fórum que “Nós não temos uma ótima história para contar sobre aplicações multi-threaded”. Em: http://social.msdn.microsoft.com/Forums/en-US/vststest/thread/c19e9ba8-52db-4970-99a4-04468206baf6/
Ainda vou explorar o DevCenter de computação paralela (http://msdn.microsoft.com/en-us/concurrency/bb895950.aspx) e o único recurso que eu achei para esse cenário, o projeto CHESS (http://research.microsoft.com/en-us/projects/chess/).

Para fechar o artigo: Com os computadores pessoais entrando na era dos multi-cores e todo mundo falando da necessidade de termos aplicações que aproveitem de verdade esses recursos (e até a GPU), espero que o Visual Studio traga boas novas nessa área e ajude os desenvolvedores, o que acredito que acontecerá.

Se você tiver alguma dica ou experiência para compartilhar, estou ansioso para ouvi-lo!
Baixe o PDF e código fonte aqui.



[]s
Luciano Caixeta Moreira - {Luti}
Chief Innovation Officer
Sr. Nimbus Serviços em Tecnologia Ltda
luciano.moreira@srnimbus.com.br
www.twitter.com/luticm

3 comentários:

  1. É muita coincidência. Alguém que tem o mesmo nome que o meu, mesmo apelido (Luti), mesma profissão, e ainda por cima trabalha com as mesmas ferramentas ;-) Só falta falar que você adora TDD ?

    []´s

    Luciano C. Fernandes
    luty@ufrj.br
    lucianocfernandes@gmail.com

    ResponderExcluir
  2. Gosto sim de TDD. Realmente muita coincidência, eita mundinho pequeno.

    ResponderExcluir
  3. Cara seus posts são muito interessantes. Pega meus contatos aí:

    MSN: luty@UFRJ.br
    Skype: lucianocfernandes
    Twitter (já estou te followando :D ): luccasfer
    GTalk: lucianocfernandes

    []´s

    Luciano

    ResponderExcluir