multithreading - technology - whats hyperthreading




Como devo testar o código segmentado? (16)

(se possível) não use threads, use atores / objetos ativos. Fácil de testar.

Até agora evitei o pesadelo que está testando o código multi-thread, já que parece um campo minado demais. Gostaria de perguntar como as pessoas testaram o código que depende de threads para execução bem-sucedida, ou apenas como as pessoas testaram esses tipos de problemas que só aparecem quando dois segmentos interagem de uma determinada maneira?

Este parece ser um problema realmente importante para os programadores hoje em dia, seria útil reunir nosso conhecimento neste imho.


Concorrência é uma interação complexa entre o modelo de memória, hardware, caches e nosso código. No caso de Java, pelo menos esses testes foram parcialmente abordados principalmente por jcstress . Os criadores dessa biblioteca são conhecidos por serem autores de muitos recursos de simultaneidade de JVM, GC e Java.

Mas até mesmo essa biblioteca precisa de um bom conhecimento da especificação do Modelo de Memória Java para que saibamos exatamente o que estamos testando. Mas eu acho que o foco deste esforço é um mircobenchmarks. Não aplicativos de negócios enormes.


Eu enfrentei esse problema várias vezes nos últimos anos, quando escrevi código de manipulação de thread para vários projetos. Estou fornecendo uma resposta tardia porque a maioria das outras respostas, embora forneça alternativas, não responde de fato à pergunta sobre o teste. Minha resposta é endereçada aos casos em que não há alternativa para o código multithread; Eu abordo os problemas de design de código para completar, mas também discuto os testes unitários.

Escrevendo código multithread testável

A primeira coisa a fazer é separar seu código de manipulação de segmento de produção de todo o código que faz o processamento de dados real. Dessa forma, o processamento de dados pode ser testado como código único, e a única coisa que o código multithread faz é coordenar os threads.

A segunda coisa a lembrar é que os erros no código multithread são probabilísticos; os bugs que se manifestam com menos frequência são os bugs que se infiltrarão na produção, serão difíceis de reproduzir mesmo em produção e, portanto, causarão os maiores problemas. Por esse motivo, a abordagem de codificação padrão de escrever o código rapidamente e depois depurá-lo até funcionar é uma má idéia para o código multithread; isso resultará em código onde os bugs fáceis são corrigidos e os bugs perigosos ainda estão lá.

Em vez disso, ao escrever um código multithread, você deve escrever o código com a atitude de evitar escrever os bugs em primeiro lugar. Se você removeu corretamente o código de processamento de dados, o código de tratamento de encadeamentos deve ser pequeno o suficiente - preferivelmente algumas linhas, na pior das hipóteses algumas dúzias de linhas - que você tem a chance de escrevê-lo sem escrever um bug e certamente sem gravar muitos bugs , se você entende de segmentação, tome seu tempo e seja cuidadoso.

Escrevendo testes unitários para código multithread

Uma vez que o código multithreaded é escrito com o maior cuidado possível, ainda vale a pena escrever testes para esse código. O objetivo principal dos testes não é testar bugs de condição de corrida altamente dependentes de tempo - é impossível testar repetidamente essas condições de corrida - mas sim testar se sua estratégia de bloqueio para evitar esses bugs permite que vários segmentos interajam como pretendido .

Para testar corretamente o comportamento de bloqueio correto, um teste deve iniciar vários segmentos. Para tornar o teste repetível, queremos que as interações entre os encadeamentos ocorram em uma ordem previsível. Não queremos sincronizar externamente os threads no teste, porque isso irá mascarar bugs que poderiam acontecer na produção, onde os threads não são sincronizados externamente. Isso deixa o uso de atrasos de temporização para sincronização de threads, que é a técnica que usei com sucesso sempre que precisei escrever testes de código multithread.

Se os atrasos forem muito curtos, o teste se tornará frágil, porque pequenas diferenças de tempo - digamos, entre máquinas diferentes nas quais os testes podem ser executados - podem fazer com que o tempo seja desativado e o teste falhe. O que eu geralmente faço é começar com atrasos que causam falhas de teste, aumentam os atrasos para que o teste passe de maneira confiável na minha máquina de desenvolvimento e, em seguida, dobre os atrasos para que o teste tenha uma boa chance de passar em outras máquinas. Isso significa que o teste levará um tempo macroscópico, embora, na minha experiência, o desenho cuidadoso do teste possa limitar esse tempo a não mais do que uma dúzia de segundos. Como você não deve ter muitos lugares que exigem um código de coordenação de threads em seu aplicativo, isso deve ser aceitável para o seu conjunto de testes.

Por fim, acompanhe o número de bugs detectados pelo seu teste. Se o seu teste tiver 80% de cobertura de código, pode-se esperar que ele capture 80% de seus bugs. Se seu teste for bem projetado, mas não encontrar bugs, há uma chance razoável de que você não tenha bugs adicionais que só serão exibidos na produção. Se o teste detectar um ou dois erros, você ainda pode ter sorte. Além disso, e você pode querer considerar uma revisão cuidadosa ou mesmo uma reescrita completa do seu código de manipulação de thread, já que é provável que o código ainda contenha bugs ocultos que serão muito difíceis de encontrar até que o código esteja em produção, e muito difícil consertar então.


Eu fiz muito disso, e sim, é uma droga.

Algumas dicas:

  • GroboUtils para executar vários encadeamentos de teste
  • alphaWorks ConTest para classes de instrumentos para causar interleavings para variar entre as iterações
  • Crie um campo tearDown e verifique-o em tearDown (consulte a Listagem 1). Se você pegar uma exceção ruim em outro thread, basta atribuí-lo ao throwable.
  • Eu criei a classe utils na Listagem 2 e achei inestimável, especialmente waitForVerify e waitForCondition, o que aumentará muito o desempenho de seus testes.
  • Faça bom uso do AtomicBoolean em seus testes. É thread-safe, e muitas vezes você precisa de um tipo de referência final para armazenar valores de classes de retorno de chamada e afins. Veja o exemplo na Listagem 3.
  • Certifique-se de sempre dar ao seu teste um tempo limite (por exemplo, @Test(timeout=60*1000) ), pois os testes de simultaneidade podem, às vezes, ser suspensos para sempre quando estão quebrados.

Listagem 1:

@After
public void tearDown() {
    if ( throwable != null )
        throw throwable;
}

Listagem 2:

import static org.junit.Assert.fail;
import java.io.File;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.Random;
import org.apache.commons.collections.Closure;
import org.apache.commons.collections.Predicate;
import org.apache.commons.lang.time.StopWatch;
import org.easymock.EasyMock;
import org.easymock.classextension.internal.ClassExtensionHelper;
import static org.easymock.classextension.EasyMock.*;

import ca.digitalrapids.io.DRFileUtils;

/**
 * Various utilities for testing
 */
public abstract class DRTestUtils
{
    static private Random random = new Random();

/** Calls {@link #waitForCondition(Integer, Integer, Predicate, String)} with
 * default max wait and check period values.
 */
static public void waitForCondition(Predicate predicate, String errorMessage) 
    throws Throwable
{
    waitForCondition(null, null, predicate, errorMessage);
}

/** Blocks until a condition is true, throwing an {@link AssertionError} if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param errorMessage message use in the {@link AssertionError}
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, String errorMessage) throws Throwable 
{
    waitForCondition(maxWait_ms, checkPeriod_ms, predicate, new Closure() {
        public void execute(Object errorMessage)
        {
            fail((String)errorMessage);
        }
    }, errorMessage);
}

/** Blocks until a condition is true, running a closure if
 * it does not become true during a given max time.
 * @param maxWait_ms max time to wait for true condition. Optional; defaults
 * to 30 * 1000 ms (30 seconds).
 * @param checkPeriod_ms period at which to try the condition. Optional; defaults
 * to 100 ms.
 * @param predicate the condition
 * @param closure closure to run
 * @param argument argument for closure
 * @throws Throwable on {@link AssertionError} or any other exception/error
 */
static public void waitForCondition(Integer maxWait_ms, Integer checkPeriod_ms, 
    Predicate predicate, Closure closure, Object argument) throws Throwable 
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    if ( checkPeriod_ms == null )
        checkPeriod_ms = 100;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    while ( !predicate.evaluate(null) ) {
        Thread.sleep(checkPeriod_ms);
        if ( stopWatch.getTime() > maxWait_ms ) {
            closure.execute(argument);
        }
    }
}

/** Calls {@link #waitForVerify(Integer, Object)} with <code>null</code>
 * for {@code maxWait_ms}
 */
static public void waitForVerify(Object easyMockProxy)
    throws Throwable
{
    waitForVerify(null, easyMockProxy);
}

/** Repeatedly calls {@link EasyMock#verify(Object[])} until it succeeds, or a
 * max wait time has elapsed.
 * @param maxWait_ms Max wait time. <code>null</code> defaults to 30s.
 * @param easyMockProxy Proxy to call verify on
 * @throws Throwable
 */
static public void waitForVerify(Integer maxWait_ms, Object easyMockProxy)
    throws Throwable
{
    if ( maxWait_ms == null )
        maxWait_ms = 30 * 1000;
    StopWatch stopWatch = new StopWatch();
    stopWatch.start();
    for(;;) {
        try
        {
            verify(easyMockProxy);
            break;
        }
        catch (AssertionError e)
        {
            if ( stopWatch.getTime() > maxWait_ms )
                throw e;
            Thread.sleep(100);
        }
    }
}

/** Returns a path to a directory in the temp dir with the name of the given
 * class. This is useful for temporary test files.
 * @param aClass test class for which to create dir
 * @return the path
 */
static public String getTestDirPathForTestClass(Object object) 
{

    String filename = object instanceof Class ? 
        ((Class)object).getName() :
        object.getClass().getName();
    return DRFileUtils.getTempDir() + File.separator + 
        filename;
}

static public byte[] createRandomByteArray(int bytesLength)
{
    byte[] sourceBytes = new byte[bytesLength];
    random.nextBytes(sourceBytes);
    return sourceBytes;
}

/** Returns <code>true</code> if the given object is an EasyMock mock object 
 */
static public boolean isEasyMockMock(Object object) {
    try {
        InvocationHandler invocationHandler = Proxy
                .getInvocationHandler(object);
        return invocationHandler.getClass().getName().contains("easymock");
    } catch (IllegalArgumentException e) {
        return false;
    }
}
}

Listagem 3:

@Test
public void testSomething() {
    final AtomicBoolean called = new AtomicBoolean(false);
    subject.setCallback(new SomeCallback() {
        public void callback(Object arg) {
            // check arg here
            called.set(true);
        }
    });
    subject.run();
    assertTrue(called.get());
}

Eu manejo testes unitários de componentes roscados da mesma maneira que eu manejo qualquer teste unitário, ou seja, com inversão de estruturas de controle e isolamento. Eu desenvolvo no .net-arena e fora da caixa o threading (entre outras coisas) é muito difícil (eu diria quase impossível) para isolar totalmente.

Portanto, eu escrevi wrappers que se parecem com algo assim (simplificado):

public interface IThread
{
    void Start();
    ...
}

public class ThreadWrapper : IThread
{
    private readonly Thread _thread;

    public ThreadWrapper(ThreadStart threadStart)
    {
        _thread = new Thread(threadStart);
    }

    public Start()
    {
        _thread.Start();
    }
}

public interface IThreadingManager
{
    IThread CreateThread(ThreadStart threadStart);
}

public class ThreadingManager : IThreadingManager
{
    public IThread CreateThread(ThreadStart threadStart)
    {
         return new ThreadWrapper(threadStart)
    }
}

A partir daí, posso injetar facilmente o IThreadingManager em meus componentes e usar minha estrutura de isolamento preferencial para fazer o encadeamento se comportar conforme o esperado durante o teste.

Isso até agora funcionou muito bem para mim, e eu uso a mesma abordagem para o pool de threads, coisas em System.Environment, Sleep etc.


Eu também tive sérios problemas ao testar o código multi-thread. Então eu encontrei uma solução muito legal em "xUnit Test Patterns" por Gerard Meszaros. O padrão que ele descreve é ​​chamado de objeto Humilde .

Basicamente, ele descreve como você pode extrair a lógica em um componente separado e fácil de testar que é desacoplado de seu ambiente. Depois de testar essa lógica, você pode testar o comportamento complicado (multiencadeamento, execução assíncrona, etc ...)


Existem algumas ferramentas que são muito boas. Aqui está um resumo de alguns dos Java.

Algumas boas ferramentas de análise estática incluem FindBugs (dá algumas dicas úteis), JLint , Java Pathfinder (JPF e JPF2) e Bogor .

MultithreadedTC é uma ótima ferramenta de análise dinâmica (integrada ao JUnit) onde você tem que configurar seus próprios casos de teste.

ConTest da IBM Research é interessante. Ele instrumenta seu código inserindo todos os tipos de comportamentos de modificação de threads (por exemplo, sleep & yield) para tentar descobrir erros aleatoriamente.

SPIN é uma ferramenta muito legal para modelar seus componentes Java (e outros), mas você precisa ter um framework útil. É difícil de usar como está, mas é extremamente poderoso se você souber usá-lo. Algumas ferramentas usam o SPIN debaixo do capô.

MultithreadedTC é provavelmente o mais mainstream, mas algumas das ferramentas de análise estáticas listadas acima são definitivamente dignas de serem vistas.


Já faz um tempo que essa pergunta foi postada, mas ainda não foi respondida ...

A resposta de kleolb02 é boa. Vou tentar entrar em mais detalhes.

Existe uma maneira, que eu pratico para o código C #. Para testes de unidade, você deve ser capaz de programar testes reproduzíveis , que é o maior desafio no código multithread. Então, minha resposta visa forçar o código assíncrono em um equipamento de teste, que funciona de forma síncrona .

É uma idéia do livro de Gerard Meszardos " xUnit Test Patterns " e é chamada de "Humble Object" (p. 695): Você tem que separar o código da lógica principal e qualquer coisa que cheire a código assíncrono um do outro. Isso resultaria em uma classe para a lógica principal, que funciona de forma síncrona .

Isso coloca você na posição de testar o código lógico principal de maneira síncrona . Você tem controle absoluto sobre o tempo das chamadas que está fazendo na lógica principal e, portanto, pode fazer testes reproduzíveis . E este é o seu ganho de separar a lógica do núcleo e a lógica assíncrona.

Essa lógica central precisa ser envolvida por outra classe, que é responsável por receber as chamadas para a lógica principal de forma assíncrona e delegar essas chamadas à lógica principal. O código de produção acessará apenas a lógica principal por meio dessa classe. Porque esta classe deve apenas delegar chamadas, é uma classe muito "burra" sem muita lógica. Então você pode manter seus testes unitários para esta classe trabalhadora asrílica no mínimo.

Qualquer coisa acima disso (teste de interação entre classes) são testes de componentes. Também neste caso, você deve ser capaz de ter controle absoluto sobre o tempo, se mantiver o padrão "Objeto Humilde".


Olha, não há maneira fácil de fazer isso. Eu estou trabalhando em um projeto que é inerentemente multithreaded. Eventos vêm do sistema operacional e eu tenho que processá-los simultaneamente.

A maneira mais simples de lidar com o teste de código de aplicativo complexo e multithread é: Se for muito complexo para testar, você está fazendo errado. Se você tiver uma única instância que tenha vários encadeamentos agindo sobre ela e não puder testar as situações em que esses encadeamentos se sobrepõem, seu design precisará ser refeito. É tão simples e complexo quanto isso.

Há muitas maneiras de programar para multithreading que evita threads sendo executados através de instâncias ao mesmo tempo. O mais simples é tornar todos os seus objetos imutáveis. Claro, isso geralmente não é possível. Portanto, você precisa identificar esses locais em seu design em que os threads interagem com a mesma instância e reduzem o número desses locais. Ao fazer isso, você isola algumas classes onde o multithreading realmente ocorre, reduzindo a complexidade geral de testar seu sistema.

Mas você tem que perceber que, mesmo fazendo isso, você ainda não pode testar todas as situações em que dois tópicos avançam um sobre o outro. Para fazer isso, você teria que executar dois threads simultaneamente no mesmo teste, então controlar exatamente quais linhas eles estão executando em um dado momento. O melhor que você pode fazer é simular essa situação. Mas isso pode exigir que você codifique especificamente para testes e, na melhor das hipóteses, é um meio passo para uma solução real.

Provavelmente, a melhor maneira de testar o código para problemas de encadeamento é por meio da análise estática do código. Se o seu código encadeado não seguir um conjunto finito de padrões de segurança de encadeamento, você pode ter um problema. Eu acredito que a Análise de Código no VS contém algum conhecimento de encadeamento, mas provavelmente não muito.

Veja, do jeito que as coisas estão atualmente (e provavelmente será um bom momento para vir), a melhor maneira de testar aplicativos multithread é reduzir a complexidade do código segmentado o máximo possível. Minimize as áreas onde os segmentos interagem, teste da melhor forma possível e use a análise de código para identificar as áreas de perigo.


Outra maneira de testar o código encadeado, e sistemas muito complexos em geral, é através do Fuzz Testing . Não é ótimo, e não vai encontrar tudo, mas é provável que seja útil e simples de fazer.

Citar:

O teste de fuzz ou fuzzing é uma técnica de teste de software que fornece dados aleatórios ("fuzz") às entradas de um programa. Se o programa falhar (por exemplo, ao travar ou ao falhar as asserções de código internas), os defeitos podem ser anotados. A grande vantagem do teste fuzz é que o design do teste é extremamente simples e livre de preconceitos sobre o comportamento do sistema.

...

O teste de fuzz é freqüentemente usado em grandes projetos de desenvolvimento de software que empregam testes de caixa preta. Esses projetos geralmente têm um orçamento para desenvolver ferramentas de teste, e o teste de penugem é uma das técnicas que oferece um alto custo benefício.

...

No entanto, o teste fuzz não é um substituto para testes exaustivos ou métodos formais: ele pode fornecer apenas uma amostra aleatória do comportamento do sistema e, em muitos casos, passar por um teste fuzz pode apenas demonstrar que um software lida com exceções sem causar falhas comportando-se corretamente. Assim, o teste fuzz só pode ser considerado como uma ferramenta de busca de insetos, em vez de uma garantia de qualidade.


Para o código J2E, usei o SilkPerformer, o LoadRunner e o JMeter para testes de simultaneidade de encadeamentos. Todos eles fazem a mesma coisa. Basicamente, eles fornecem uma interface relativamente simples para administrar a versão do servidor proxy, necessária, para analisar o fluxo de dados TCP / IP e simular vários usuários que fazem solicitações simultâneas para o seu servidor de aplicativos. O servidor proxy pode dar a você a capacidade de fazer coisas como analisar as solicitações feitas, apresentando a página inteira e o URL enviado ao servidor, bem como a resposta do servidor, após o processamento da solicitação.

Você pode encontrar alguns bugs no modo http inseguro, onde você pode pelo menos analisar os dados do formulário que estão sendo enviados e alterá-los sistematicamente para cada usuário. Mas os verdadeiros testes são quando você executa em https (Secured Socket Layers). Então, você também tem que lidar com a alteração sistemática dos dados da sessão e dos cookies, o que pode ser um pouco mais complicado.

O melhor erro que já encontrei, ao testar a simultaneidade, foi quando descobri que o desenvolvedor tinha confiado na coleta de lixo Java para fechar a solicitação de conexão estabelecida no login, no servidor LDAP, ao efetuar login. Isso resultou na exposição dos usuários para sessões de outros usuários e resultados muito confusos, ao tentar analisar o que aconteceu quando o servidor foi colocado de joelhos, mal conseguindo completar uma transação, a cada poucos segundos.

No final, você ou alguém provavelmente terá que se dobrar e analisar o código de erros como o que acabei de mencionar. E uma discussão aberta em todos os departamentos, como o que ocorreu, quando desdobramos o problema descrito acima, é mais útil. Mas essas ferramentas são a melhor solução para testar o código multi-threaded. JMeter é open source. SilkPerformer e LoadRunner são proprietários. Se você realmente quer saber se o seu aplicativo é seguro, é assim que os garotos grandes fazem isso. Eu fiz isso para empresas muito grandes profissionalmente, então não estou adivinhando. Eu estou falando por experiência pessoal.

Uma palavra de cautela: leva algum tempo para entender essas ferramentas. Não será uma questão de simplesmente instalar o software e ativar a GUI, a menos que você já tenha tido alguma exposição à programação multi-threaded. Tentei identificar as três categorias críticas de áreas a serem compreendidas (formulários, sessão e dados de cookies), com a esperança de que pelo menos começar a entender esses tópicos ajudará você a se concentrar em resultados rápidos, em vez de ter que ler o documentação inteira.


Passei a maior parte da semana passada em uma biblioteca universitária estudando a depuração de código concorrente. O problema central é que o código concorrente é não determinístico. Normalmente, a depuração acadêmica caiu em um dos três campos aqui:

  1. Event-trace / replay. Isso requer um monitor de eventos e, em seguida, revisa os eventos que foram enviados. Em uma estrutura de UT, isso envolveria enviar manualmente os eventos como parte de um teste e fazer revisões post mortem.
  2. Scriptable É aqui que você interage com o código em execução com um conjunto de gatilhos. "Em x> foo, baz ()". Isso pode ser interpretado em uma estrutura UT em que você tem um sistema de tempo de execução acionando um determinado teste em uma determinada condição.
  3. Interativo. Obviamente, isso não funcionará em uma situação de teste automático. ;)

Agora, como observadores acima observaram, você pode projetar seu sistema concorrente em um estado mais determinista. No entanto, se você não fizer isso corretamente, voltará a projetar um sistema seqüencial novamente.

Minha sugestão seria focar em ter um protocolo de design muito rígido sobre o que é encadeado e o que não é encadeado. Se você restringir sua interface para que haja dependências mínimas entre elementos, será muito mais fácil.

Boa sorte, e continue trabalhando no problema.


Se você está testando simples novo Thread (runnable) .run () Você pode simular Thread para executar o executável sequencialmente

Por exemplo, se o código do objeto testado invocar um novo thread como este

Class TestedClass {
    public void doAsychOp() {
       new Thread(new myRunnable()).start();
    }
}

Em seguida, zombar de novos Threads e executar o argumento executável sequencialmente pode ajudar

@Mock
private Thread threadMock;

@Test
public void myTest() throws Exception {
    PowerMockito.mockStatic(Thread.class);
    //when new thread is created execute runnable immediately 
    PowerMockito.whenNew(Thread.class).withAnyArguments().then(new Answer<Thread>() {
        @Override
        public Thread answer(InvocationOnMock invocation) throws Throwable {
            // immediately run the runnable
            Runnable runnable = invocation.getArgumentAt(0, Runnable.class);
            if(runnable != null) {
                runnable.run();
            }
            return threadMock;//return a mock so Thread.start() will do nothing         
        }
    }); 
    TestedClass testcls = new TestedClass()
    testcls.doAsychOp(); //will invoke myRunnable.run in current thread
    //.... check expected 
}

Testar o código MT para correção é, como já foi dito, um problema bastante difícil. No final, tudo se resume a garantir que não haja corridas de dados sincronizadas incorretamente em seu código. O problema com isso é que existem infinitas possibilidades de execução de threads (interleavings) sobre as quais você não tem muito controle (não deixe de ler this artigo, no entanto). Em cenários simples, pode ser possível comprovar a exatidão pelo raciocínio, mas esse geralmente não é o caso. Especialmente se você quiser evitar / minimizar a sincronização e não optar pela opção de sincronização mais óbvia / fácil.

Uma abordagem que eu sigo é escrever código de teste altamente concorrente, a fim de tornar possível a ocorrência de corridas de dados potencialmente não detectados. E então eu corri esses testes por algum tempo :) Uma vez eu tropecei em uma palestra onde alguns cientistas da computação exibiam uma ferramenta que fazia isso (criando aleatoriamente testes a partir de especificações e executando-os descontroladamente, simultaneamente, checando os invariantes definidos). ser quebrado).

By the way, eu acho que este aspecto de testar o código MT não foi mencionado aqui: identificar invariantes do código que você pode verificar aleatoriamente. Infelizmente, encontrar essas invariantes é um problema bastante difícil também. Além disso, eles podem não aguentar todo o tempo durante a execução, portanto, você precisa encontrar / impor pontos de execução nos quais possa esperar que eles sejam verdadeiros. Trazer a execução do código para tal estado também é um problema difícil (e pode ter problemas de concorrência. É, é muito difícil!

Alguns links interessantes para ler:

  • this : Uma estrutura que permite forçar certos entrelaçamentos de thread e, em seguida, verificar invariantes
  • jMock Blitzer : Sincronização de teste de estresse
  • assertConcurrent : versão JUnit de synronization de testes de estresse
  • Testando código concorrente : Visão geral dos dois principais métodos de força bruta (teste de estresse) ou determinístico (indo para os invariantes)

Você pode usar o EasyMock.makeThreadSafe para tornar a instância de teste thread-safe


Awaitility também pode ser útil para ajudá-lo a escrever testes unitários determinísticos. Ele permite que você aguarde até que algum estado em algum lugar do sistema seja atualizado. Por exemplo:

await().untilCall( to(myService).myMethod(), greaterThan(3) );

ou

await().atMost(5,SECONDS).until(fieldIn(myObject).ofType(int.class), equalTo(1));

Ele também tem suporte a Scala e Groovy.

await until { something() > 4 } // Scala example




unit-testing