• Nenhum resultado encontrado

disso, pretendia-se permitir também que se pudesse especificar que um teste deveria poder esperar até uma determinada fase de execução da aplicação sob teste, mapeada em um certo estado de suas threads, o qual não deveria ser apenas o estado em que estariam todas paradas ou finalizadas, como era o caso do que era fornecido pelo arcabouço ThreadServices. A idéia seria permitir também a definição de outros estados do sistema pelos quais se pode esperar e também de outras transições de estados para as threads. Para evitar o problema das asserções tardias, viu-se também que a abordagem a ser utilizada deveria também ser capaz de evitar mudanças de estado enquanto asserções estão sendo feitas. Ao evoluir este trabalho inicial sob a forma de uma abordagem, ao invés de apenas um novo arcabouço, buscou-se prover uma solução mais geral para o problema das asserções antecipadas e tardias e que pudesse ser utilizada em diferentes contextos de sistemas, implementados em diferentes linguagens de programação, provendo-se as bases para a criação de diferentes arcabouços de teste.

5.2

A Abordagem Thread Control for Tests

Visando resolver os problemas existentes em abordagens como atrasos explícitos e espera ocupada, e ao mesmo tempo prover uma solução baseada em primitivas oferecidas aos tes- tadores, com funcionalidade semelhante ao métodowaitUntilWorkIsDonedo trabalho anterior, foi proposta a abordagem Thread Control for Tests. Para oferecer tais primitivas e evitar o problema das asserções feitas cedo ou tarde demais, essa abordagem se baseia na monitoração e no controle das threads do sistema sendo testado. O serviço geral provido por essas primitivas é a capacidade de fazer o teste esperar até um momento apropriado onde asserções podem ser feitas com segurança (por exemplo, no momento em que todas as th- reads estão paradas) e também a habilidade de evitar mudanças no estado das threads do sistema uma vez que o estado esperado para asserções, especificado também através de uma primitiva de teste, foi atingido, evitando assim falhas indesejadas na execução dos testes.

Thread Control for Tests é uma abordagem geral para o teste de sistemas multi-threaded.

Ela propõe que testes envolvendo operações assíncronas apresentem as seguintes fases:

1. preparação do ambiente, indicando o estado esperado no qual asserções podem ser executadas;

5.2 A Abordagem Thread Control for Tests 64

2. invocação das operações do sistema, exercitando-o;

3. espera até que o estado esperado para o sistema, especificado na fase 1, tenha sido alcançado, podendo a thread do teste ser notificada uma vez que isso acontece;

4. execução das asserções, sem temer que alguma thread desperte e execute operações que afetem os resultados de teste;

5. prosseguimento do teste, permitindo assim que o sistema e o teste continuem nor- malmente (retornando-se à fase 1) ou terminem. Essa fase é necessária pois threads que tentam mudar de estado enquanto asserções estão sendo feitas são bloqueadas para evitar as asserções tardias, e só nessa fase de prosseguimento é que são liberadas para prosseguir.

Esta abordagem propõe que as fases 1, 3 e 5 sejam auxiliadas por uma ferramenta de testes. A idéia básica é que tal ferramenta deve disponibilizar aos desenvolvedores de testes algumas operações simples (primitivas de teste). Para isso, a ferramenta deve ser capaz de monitorar as threads do sistema e notificar o teste assim que um estado esperado para o sistema é alcançado. Além disso, ela deve também evitar perturbações no sistema enquanto as asserções estão sendo executadas (fase 4).

Na abordagem Thread Control for Tests, um estado a ser alcançado é definido em termos de estados das threads, podendo tal definição ser feita de maneira geral ou específica, como explicado a seguir. Um estado pelo qual se deseja esperar pode ser definido de maneira ge- ral, como por exemplo: espere até que todas as threads criadas pelo teste estejam esperando

ou tenham terminado ou espere até que o trabalho de todas as threads tenha terminado, o que representa uma certa fase na execução do sistema. Pode-se também especificar uma

condição de parada de uma forma mais específica, como no estilo: espere até que certas

threads estejam em determinados estados ou espere até que uma Thread B termine e todas as instâncias da Thread A estejam esperando. Uma definição de condição mais geral pode

simplificar bastante os testes já que pode ser reusada (já que vários testes podem utilizar a mesma condição de espera antes das asserções) e esta abordagem foi explorada na defini- ção da operação waitUntilWorkIsDonedo trabalho sobre o ThreadServices [25]. No entanto, quando começam a aparecer muitas exceções à regra, como threads a excluir na

5.2 A Abordagem Thread Control for Tests 65

monitoração ou quando ter um controle sobre o estado de todas é desnecessário ou com- plexo, sendo suficiente uma identificação de uma thread cujo estado é de interesse, vê-se a importância de se poder definir estados esperados mais específicos. A idéia básica de poder oferecer tal funcionalidade é aumentar as possibilidades de estados esperados que podem ser especificadas.

A Figura 5.1 ilustra a idéia geral da abordagem proposta. Ela mostra a monitoração do sistema sendo testado (SUT - System Under Test), considerando pontos de execução pelos quais passam as threads da aplicação (representados por círculos pequenos dentro dos com- ponentes do SUT) e que representam suas transições de estado. Uma vez que tais pontos são alcançados, a entidade responsável por prover primitivas oferecidas aos desenvolvedores de testes, na figura representada peloSystemWatcher, é notificada.

Figura 5.1: Visão Geral da Abordagem

No processo de notificação, caso seja percebido que o estado esperado para asserções já foi alcançado, a thread do teste pode prosseguir e futuras transições de estado nas threads do SUT são bloqueadas enquanto asserções não são concluídas. Um exemplo de uso dessa abor- dagem é um teste que utilize a primitivawaitUntilStateIsReached(sysState)e cujo estado esperado (sysState) seja o momento em que uma certa thread A está parada. Neste caso, quando a thread do teste atinge a invocação a esta primitiva de teste, ela fica esperando até ser notificada. Tomando como ponto de monitoração da aplicação a chamada

5.2 A Abordagem Thread Control for Tests 66

ao métodoObject.waitde Java e considerando que ao passar por este ponto a thread A é considerada “parada”, uma vez que este ponto é atingido, a thread do teste é notificada e volta a executar, realizando normalmente as asserções.

Um outro serviço que a abordagem apresenta é a habilidade de evitar que outras threads perturbem o estado do sistema enquanto as asserções estão sendo realizadas e enquanto ainda não tiver havido uma chamada explicita à alguma primitiva de prosseguimento. Isto é feito bloqueando a thread que queira mudar de estado e o bloqueio ocorre no momento de notificar a mudança de estado que estaria para acontecer.

É importante destacar que a abordagem Thread Control for Tests pode ser vista como

um mecanismo geral de sincronização/coordenação entre o teste e a aplicação, em que se evitam condições de corrida (race conditions) entre estes no momento de verificação dos resultados nos testes.

Para que a abordagem se torne menos intrusiva do que mecanismos como semáforos explícitos na aplicação e nos testes, propõe-se que os mecanismos de monitoração e con- trole das threads não sejam colocados diretamente no código da aplicação e que técnicas de instrumentação auxiliem nesse processo. Além disso, os desenvolvedores de testes terão como interface apenas as primitivas de teste, embora por trás, mecanismos de sincronização estejam sendo providos por ferramentas de teste de apoio à abordagem, como é o caso da ferramenta ThreadControl, explicada a seguir.

Como se pode observar pela descrição da abordagem, para dar suporte a ela, devem ser providos mecanismos para monitorar transições de estado, inclusive para evitar que certas transições ocorram enquanto asserções estão sendo feitas. Como forma de deixar mais con- creta a forma de fazer a monitoração, a Figura 5.2 ilustra alguns exemplos de operações que podem ser monitoradas na linguagem Java. Essa figura também mostra alguns estados pos- síveis das threads do sistema e que são atingidos depois ou antes que essas operações sejam executadas.

Conforme ilustra a Figura 5.2, considera-se que uma thread está indo para o estado termi- nada (Finished) quando a execução de seu métodorunfor concluída. Pode-se também dizer que ela está esperando (Waiting) uma vez que é feita uma chamada (call) ao métodowait

e que ela volta a estar rodando (Running) ao retornar dessa chamada. Transições como estas devem ser monitoradas para que se detecte o mais cedo possível quando um estado esperado