A questão é: faz sentido escrever um teste de unidade que afirme que o thread atual é o thread principal? Prós e contras?
Recentemente, vi o teste de unidade que afirma o segmento atual para o retorno de chamada do serviço. Não tenho certeza, é uma boa ideia, acredito que seja mais um tipo de teste de integração. Na minha opinião, o teste de unidade deve afirmar o método isoladamente e não deve saber sobre a natureza do consumidor de serviço.
Por exemplo, no iOS, o consumidor deste serviço deve ser uma interface do usuário que, por padrão, possui uma restrição para executar um código no encadeamento principal.
architecture
unit-testing
object-oriented-design
multithreading
ios
Vladimir Kaltyrin
fonte
fonte
Respostas:
Deixe-me mostrar meus princípios favoritos de teste de unidade:
Se o seu teste de unidade insistir em que seja o "segmento principal", é difícil não encontrar o número 4.
Isso pode parecer difícil, mas lembre-se de que um teste de unidade é apenas um dos muitos tipos diferentes de testes.
Você é livre para violar esses princípios. Mas quando o fizer, não chame seu teste de teste de unidade. Não o coloque nos testes reais de unidade e reduza a velocidade.
fonte
Você deve escrever um teste de unidade que testa se um fio é o fio principal? Depende, mas provavelmente não é uma boa ideia.
Se o objetivo de um teste de unidade se destina a verificar o funcionamento adequado de um método, testar esse aspecto quase certamente não está testando esse aspecto. Como você mesmo disse, é conceitualmente mais um teste de integração, já que não é provável que o funcionamento adequado de um método mude com base em qual thread o está executando, e o responsável por determinar qual thread será usado é o chamador e não o próprio método.
No entanto, é por isso que digo que depende, porque pode muito bem depender do método em si, caso o método gere novos threads. Embora com toda a probabilidade esse não seja o seu caso.
Meu conselho seria deixar essa verificação fora do teste de unidade e portá-la para um teste de integração adequado para esse aspecto.
fonte
Quando um consumidor consome um serviço, existe um "contrato" expresso ou implícito sobre qual funcionalidade é fornecida e como. Restrições sobre em qual thread o retorno de chamada pode ocorrer fazem parte desse contrato.
Presumo que seu código seja executado em um contexto em que haja algum tipo de "mecanismo de eventos" executado no "encadeamento principal" e uma maneira de outros encadeamentos solicitarem que o código de agendamentos do mecanismo de eventos seja executado no encadeamento principal.
Na visão mais pura dos testes de unidade, você zombaria absolutamente de toda dependência. No mundo real, poucas pessoas fazem isso. O código em teste pode usar as versões reais de pelo menos recursos básicos da plataforma.
Portanto, a verdadeira questão da IMO é que você considera o mecanismo de eventos e o suporte de threading em tempo de execução como parte dos "recursos básicos da plataforma" dos quais os testes de unidade podem depender ou não? Se você fizer isso, faz todo o sentido verificar se um retorno de chamada ocorre no "encadeamento principal". Se você estiver zombando do mecanismo de eventos e do sistema de encadeamento, projete suas zombarias para que eles possam determinar se o retorno de chamada teria acontecido no encadeamento principal. Se você estiver simulando o mecanismo de eventos, mas não o suporte ao encadeamento em tempo de execução, você deseja testar se o retorno de chamada ocorre no encadeamento em que o mecanismo de eventos simulados é executado.
fonte
Eu posso ver por que seria um teste atraente. Mas o que realmente estaria testando? diga que temos a função
Agora eu posso testar isso e executá-lo em um thread que não seja da interface do usuário, provando que um erro é gerado. Mas, realmente, para que esse teste tenha algum valor, a função precisa ter algum comportamento específico que você deseja que seja executado quando executado em um thread que não seja da interface do usuário.
Agora eu tenho algo para testar, mas não está claro que esse tipo de alternativa seria útil na vida real
fonte
Se o retorno de chamada estiver documentado para não ser seguro para thread invocar outro que não seja, por exemplo, o thread da interface do usuário ou o thread principal, parece perfeitamente razoável em um teste de unidade de um módulo de código que faz com que a chamada desse retorno de chamada garanta que não está fazendo algo que não é seguro para threads, trabalhando fora do thread que o SO ou a estrutura do aplicativo está documentada para garantir a segurança.
fonte
Organizar declaração de
ato
Um teste de unidade deve afirmar que está em um segmento específico? Não, porque não está testando uma unidade nesse caso, nem sequer é um teste de integração, pois não diz nada sobre a configuração que está ocorrendo na unidade ...
Afirmar em qual thread o teste está, seria como Afirmar o nome do seu servidor de teste ou, melhor ainda, o nome do método de teste.
Agora, pode fazer sentido organizar para que o teste seja executado em um encadeamento específico, mas somente quando você quiser ter certeza de que retornará um resultado específico com base no encadeamento.
fonte
Se você possui uma função documentada para ser executada apenas corretamente no thread principal e essa função é chamada em um thread em segundo plano, essa não é a falha das funções. A falha é culpa do chamador, portanto, os testes de unidade são inúteis.
Se o contrato for alterado para que a função seja executada corretamente no encadeamento principal e relatar uma falha ao ser executado em um encadeamento em segundo plano, você pode escrever um teste de unidade para isso, onde é chamado uma vez no encadeamento principal e depois em um thread de segundo plano e verifique se ele se comporta conforme documentado. Portanto, agora você tem um teste de unidade, mas não um teste de unidade, verificando se ele é executado no thread principal.
Eu sugeriria muito que a primeira linha da sua função afirmasse que ela é executada no thread principal.
fonte
O teste de unidade é útil apenas se estiver testando a implementação correta da referida função, não o uso correto, porque um teste de unidade não pode dizer que uma função não será chamada de outro thread no restante da sua base de código, da mesma forma que pode ' diga que funções não serão chamadas com parâmetros que violam suas condições prévias (e tecnicamente o que você está tentando testar é basicamente uma violação de uma pré-condição em uso, algo que você não pode testar efetivamente porque o teste pode ' para restringir como outros lugares na base de código usam essas funções; você pode testar se violações de pré-condições resultam em erros / exceções apropriados).
Este é um caso de afirmação para mim na implementação das funções relevantes, como algumas pessoas apontaram, ou ainda mais sofisticado é garantir que as funções sejam seguras contra threads (embora isso nem sempre seja prático quando se trabalha com algumas APIs).
Também apenas uma nota lateral, mas "thread principal"! = "Thread da interface do usuário" em todos os casos. Muitas APIs da GUI não são seguras para threads (e fazer um kit GUI seguro para threads é muito difícil), mas isso não significa que você precise invocá-las a partir do mesmo thread que aquele que tem o ponto de entrada para o seu aplicativo. Isso pode ser útil mesmo na implementação de suas asserções dentro das funções relevantes da interface do usuário para distinguir "thread da interface do usuário" do "thread principal", como capturar o ID do thread atual quando uma janela é criada para comparar, em vez do ponto de entrada principal do aplicativo (que pelo menos reduz a quantidade de suposições / restrições de uso que a implementação está aplicando apenas ao que é realmente relevante).
A segurança do encadeamento foi, na verdade, o ponto de partida de "pegadinha" em uma antiga equipe minha e, no nosso caso particular, eu a teria rotulado como a "micro-otimização" mais contraproducente de todos, de um tipo que incorreu em mais custos de manutenção do que nunca montagem manuscrita. Tivemos uma cobertura de código bastante abrangente em nossos testes de unidade, juntamente com testes de integração bastante sofisticados, apenas para encontrar conflitos e condições de corrida no software que escapou aos nossos testes. E isso aconteceu porque os desenvolvedores aleatoriamente multithreaded code sem estarem cientes de todos os efeitos colaterais que poderiam ocorrer na cadeia de chamadas de funções que resultariam de seus próprios, com uma idéia bastante ingênua de que eles poderiam corrigir esses erros em retrospectiva apenas jogando trava em torno de esquerda e direita,
Para mim, esse modo de multithreading primeiro e corrigir erros mais tarde é uma estratégia terrível para multithreading a ponto de quase me fazer odiar o multithreading inicialmente; você assegura que seus projetos sejam seguros para encadeamento e que suas implementações usem apenas funções com garantias semelhantes (por exemplo: funções puras) ou evite multithreading. Isso pode parecer um pouco dogmático, mas é melhor do que descobrir (ou pior, não descobrir) problemas difíceis de reproduzir em retrospectiva que escapam aos testes. Não faz sentido otimizar um motor de foguete se isso resultar em uma explosão inesperada inesperada do nada no meio do caminho ao longo de sua jornada para o espaço.
Se você inevitavelmente precisar trabalhar com código que não é seguro para threads, não vejo isso como um problema a ser resolvido tanto com testes de unidade / integração. O ideal seria restringir o acesso . Se o seu código da GUI estiver dissociado da lógica de negócios, você poderá aplicar um design que restrinja o acesso a essas chamadas por qualquer coisa que não seja o encadeamento / objeto que o cria *. Esse modo distante ideal para mim é tornar impossível para outros threads chamar essas funções do que tentar garantir que não.
fonte