Nunit: A captura de AssertionException falha nos testes desde 3.10.

Criado em 14 mar. 2018  ·  22Comentários  ·  Fonte: nunit/nunit

`` ``
Experimente
{
Assert.True (false, "mensagem de erro");
}
catch (AssertionException e)
{
Trace.WriteLine ("Ignorar.");
}

        Trace.WriteLine("Test Passed");

`` ``

No Nunit 3.9.0, este teste é bem-sucedido e marcado como aprovado no explorador de teste ao ser executado a partir do explorador de teste do visual studio. No Nunit 3.10, o caso de teste é marcado como falha na visão geral do explorador de teste. A última linha de rastreamento é executada em ambas as versões.

question

Comentários muito úteis

@fluffynuts Há muito tempo, quando escrevi Assert.Catch ele simplesmente detectou qualquer exceção e não gerou um erro se nenhuma exceção foi lançada. Infelizmente, isso mudou e não temos mais um equivalente "neutro" de Assert.Throws . Você pode escrever um, no entanto. Aqui está uma versão não testada com base no código em Assert.Throws . Ele só lida com código síncrono, mas você pode estendê-lo facilmente para assíncrono usando código adicional de Assert.Throws .

`` `C #
public static void Exception SafelyCatchAnNUnitException (código TestDelegate)
{
Exceção catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
`` `

O método retornará qualquer exceção lançada, limpando quaisquer resquícios do erro deixado pelos métodos de assert do NUnit. Se nenhuma exceção for lançada, ele retornará nulo.

Também é possível usar IsolatedContext() em um nível superior em seu código para recuperar o resultado real do teste dele. Isso permitiria que você teste inteiramente no nível do resultado do teste, sem qualquer referência a exceções. Pensei em escrever uma biblioteca para esse tipo de teste, que acho que seria superior à maneira como o NUnit atualmente faz seus próprios testes.

Todos 22 comentários

Seu teste está testando duas proposições sobre como o NUnit funciona:

  1. Todas as falhas de asserção geram uma AssertionException.
  2. A captura da exceção evita que o erro seja relatado.

Acontece que a primeira declaração é quase sempre verdadeira, mas não se mantém em vários blocos de declaração ou ao emitir avisos. A segunda não é verdade. No momento em que você captura a exceção, a falha já foi registrada. Esta é uma mudança no comportamento interno do NUnit, observável apenas se você estiver capturando essas exceções. Eu, por exemplo, venho dizendo às pessoas para não confiarem nesse comportamento há anos, já que é apenas um acidente de implementação.

Percebo que seu código de amostra se destina apenas a demonstrar o problema que você vê. Você pode fornecer um exemplo do que está realmente tentando fazer para detectar a exceção? Tenho certeza de que podemos ajudá-lo a encontrar uma solução melhor.

Obrigado por entrar em contato comigo em tão pouco tempo. Eu uso o mecanismo quando as coisas ainda estão instáveis. Portanto, às vezes o software não funciona corretamente. Nesses casos, eu relato isso aos desenvolvedores. Nesse ínterim, estendemos o teste para contornar esse comportamento instável. Portanto, o teste passa em vez de falhar neste problema.

Tentamos usar o método Warn.If () para isso, no entanto, ele define o resultado como "Teste pulado" no explorador de teste. Isso causa falhas de relatório, pois o teste não é ignorado. Portanto, optamos por implementar a captura de asserção em vez disso e rastrear um aviso. Eu sei, não é desejável, mas funcionou muito bem. Pelo menos até a última atualização.

Estou curioso para saber se essa mudança foi intencional, nesse caso eu sei que tenho que mudar as coisas.

Warn.If funciona bem ... __except__ no Test Explorer. 😢 O problema é que o Test Explorer sabe sobre aprovação e reprovação, mas não avisa. Tivemos que traduzir nossos resultados de aviso para algo que eles pudessem entender até o momento em que implementassem um status de resultado de aviso. FWIW, temos um problema semelhante com nosso estado de resultado inconclusivo.

A mudança que fizemos foi intencional e vem sendo planejada há muito tempo. Não queremos depender de exceções a maior parte do tempo e queremos relatar o resultado de afirmações - incluindo as aprovadas eventualmente - de alguma forma que não interrompa o teste. Fizemos algumas alterações para mitigar o impacto disso para usuários como você, mas esta não parecia ter nenhuma mitigação possível.

Se você tiver testes instáveis, o padrão por muitos anos tem sido ignorá-los. Infelizmente, muitas equipes "ignoram os testes ignorados" e, nesse caso, não é uma escolha tão boa. Se você pode treinar sua equipe para levar os avisos a sério - quase tão a sério, na verdade, quanto os erros e falhas - então você pode usar o status de aviso para destacar a instabilidade. Outra possibilidade é usar Ignorar com a propriedade Até, para que se transforme em uma falha se não for corrigida em uma determinada data.

Aliás, quando eu treino equipes, geralmente tenho um Big Visible Display continuamente atualizado de testes ignorados. Na maioria das equipes, todos sabem quem é responsável por quê, então não há necessidade de chamar a pessoa responsável pelo teste irregular. Eles tendem a ser corrigidos quando todos veem o problema.

@svanimpelen , você tentou usar o atributo Retry em seus testes fragmentados? Isso permitirá que você os execute várias vezes para que passem.

Quanto ao aviso, resultando em testes ignorados no explorer, conversamos com a equipe do Visual Studio sobre nos permitir especificar mais estados de resultado do que o número limitado de MSTest que eles suportam atualmente. Eles gostaram da ideia, então esperamos ver melhorias lá no futuro. Até então, você precisará sofrer com o ignorado. Dito isso, ignorado resulta em avisos. Eu presumo que você queira que os avisos sejam muito óbvios 😄

Muitos de nossos métodos documentam que a API lança AssertionException em caso de falha:

https://github.com/nunit/nunit/blob/d03e93c8f25170a8ff48e80863a3fe6fd294613a/src/NUnitFramework/framework/Assert.That.cs#L40 -L43

Esta documentação, mais o comportamento de longa data do NUnit, não constitui uma alteração significativa no 3.10?
A menos que esteja faltando alguma coisa, devemos pelo menos remover os documentos agora incorretos.

Concordo que a documentação pode ser enganosa, agora que introduzimos vários blocos de asserção. Dito isso, o AssertionException ainda está sendo lançado neste caso, a diferença é que o teste agora está sendo relatado como uma falha no Visual Studio, embora a exceção tenha sido detectada. Não vejo isso como uma mudança significativa. Os usuários confiavam em comportamentos não documentados que funcionavam. Para mim, não é diferente que os usuários confiem no fato de que os testes foram executados em ordem alfabética para ordenar seus testes.

Provavelmente devemos atualizar a documentação. Poderíamos remover as informações sobre o lançamento de uma exceção e, em vez disso, apenas declarar que uma condição falsa falhou no teste. E / Ou, poderíamos afirmar que a exceção não é lançada em blocos multi-assert? Pessoalmente, acho que o tipo de exceção lançada é um detalhe interno, então prefiro a primeira opção.

Eu acho isso certo. Se o uso de exceções deve ser documentado em qualquer lugar, pode ser na seção wiki sobre internos.

Quando a documentação do método diz que uma exceção será lançada, eu acho que está inequivocamente dizendo que a exceção pode ser detectada no site da chamada. C # não tem exceções verificadas e documentos XML são a forma como temos comunicado essa parte do contrato de método. Pelo que sei, a linguagem "lança X" sempre significou que o método não o detecta, ainda estou um pouco preocupado com a forma como os outros o interpretaram. Talvez uma nota de alteração urgente no wiki para ficar no lado seguro?

Gosto da ideia de remover todas as referências a AssertionException dos documentos XML e de criar uma página wiki alertando as pessoas para não usarem AssertionException.

💭Se capturar AssertionException é sempre um bug, podemos considerar a possibilidade de tornar esse tipo interno?

@ jnm2.

Concordo com você sobre a importância dos documentos XML, mas o fato é que esses documentos __não__ são como temos comunicado parte do contrato até agora. Na verdade, acho que você pode ser a primeira pessoa a sugerir isso e acho que é uma boa ideia.

Mas acho que você deve reconhecer que começar a pensar neles dessa forma envolverá a mudança de muitos comentários errôneos! Não acho que possamos tratar cada uma dessas alterações de documento como uma alteração significativa. Em uma base caso a caso, podemos razoavelmente tratar qualquer mudança de comportamento subjacente como quebra, mas isso é diferente. As alterações de documentos não devem ser interrompidas.

Isso levanta a questão do que deveria estar na lista de alterações importantes. Se entendi seus comentários anteriores, acho que você tende a documentar qualquer coisa que possa prejudicar qualquer usuário. Eu prefiro documentar coisas que acreditamos que quebrariam muitos usuários.

É assim que vejo a diferença - outras pessoas podem discordar ... Documentar __tudo que pode quebrar__ cheira a CYA para mim, como o tipo de CYA que a administração às vezes se entrega quando se concentra em quem é culpado pelo fracasso em vez de prevenir o fracasso em si. SE documentarmos tudo, então podemos dizer ... "Veja, estamos protegidos." Se apenas documentarmos o que consideramos importante, corremos o risco de estar errados e ter que nos desculpar, mas damos ao usuário comum um conjunto de alterações muito mais fácil de entender. Eu prefiro o último.

Há momentos em que é seguro capturar uma exceção de asserção. Por exemplo, fiz uma alteração há algum tempo para que permanecesse seguro ao executar testes diretamente, sem um TestExecutionContext envolvido. Se você tornar a exceção interna, você a quebrará. Na verdade, fiquei feliz em quebrá-lo, mas a equipe concordou que não deveríamos, então eu consertei. Você apenas tem que decidir o que está disposto a quebrar. Essa correção __não__ tratou do problema atual, que trata de capturar a exceção dentro do próprio teste, em vez de no código que invoca o método.

Capturar AssertionException em um teste é o que eu chamaria de "cheiro" clássico. Provavelmente está errado, mas você precisa examinar o caso específico para saber. Eu diria aos usuários "Catching AssertionException é provavelmente um erro, a menos que você tenha conhecimento detalhado de componentes internos do NUnit. Para garantir o comportamento adequado, você não deve tentar alterar o resultado do teste e deve relançar a exceção depois de fazer o que quer que seja com isso." No entanto, ainda não acho que essas informações pertençam aos documentos XML ou aos documentos gerais do usuário. Eu colocaria como uma página na seção interna.

Aliás, minha experiência anterior foi que documentar algo e, em seguida, dizer às pessoas para não usarem, leva ao aumento do uso. 😄

Bons pontos, obrigado! Acho que estou pensando em parte em termos de CYA. Há outro elemento também: como consumidor de bibliotecas, pessoalmente gostaria de receber informações sobre todas as alterações importantes para que eu possa procurar suposições erradas em meu próprio código. Particularmente com mudanças como essa que o compilador não percebe. Já que eu apreciaria isso, eu projeto isso em nossos usuários até certo ponto.

Devemos começar um novo problema para rastrear a correção de documentos?

Por que novo?

O título parece algo que fecharíamos como uma pergunta respondida em vez de corrigir. Normalmente, mudaríamos o título e usaríamos esse problema para rastrear a alteração do documento?

O que normalmente faço é revisar o problema e classificá-lo ... neste caso, como um problema de documentação. Admitimos que, primeiro, passamos muito tempo discutindo isso como um possível bug. É uma questão de administração de projeto, realmente, e fora do meu escopo.

O que estou dizendo é fazer algo lógico e consistente. 😄 Fechar como não é um bug é uma opção. Isso separa a reclamação da solução, no entanto. Escrever um comentário explicativo conforme você o reclassifica como um bug do docs e, possivelmente, edita o título mantém a solução com a reclamação. Em qualquer caso, o título da edição de edições concluídas acaba nas notas de lançamento, por isso deve ser expressivo do que aconteceu.

Você pode fornecer um exemplo do que está realmente tentando fazer para detectar a exceção? Tenho certeza de que podemos ajudá-lo a encontrar uma solução melhor.

Atualmente, estou enfrentando o mesmo problema desde que meus testes começaram a falhar após a atualização para 3.10. Ora aqui está o meu problema:
Eu tenho métodos de asserção de teste personalizados. Esses chamam internamente NUnit.Assert. Eu testo esses métodos de asserção para garantir que eles realmente falhem quando eu espero que falhem.
Atualmente, meus testes capturam AssertionException para verificar se o método de asserção personalizado fará com que um teste falhe, mas depois de atualizar todos esses testes começa a falhar devido aos motivos acima.

Qual é a maneira recomendada de testar aqueles com 3.10?

Obrigado.

A abordagem mais simples é usar Assert.Throws para capturar a exceção em vez de fazer você mesmo. Assert.Throws entende os componentes internos do NUnit e garante que a falha não seja relatada.

Eu vi isso em minhas notificações outro dia e não pensei muito nisso. No entanto, atualizando um projeto de biblioteca meu, que faz "Assert.That" dentro de sua lógica (é uma estrutura auxiliar de teste - especificamente para fornecer uma maneira fácil de testar a persistência do EF ao codificar contextos e entidades do EF manualmente e codificar o banco de dados correspondente migrações manualmente (ou com algo como FluentMigrator) - então, em outras palavras, ele deve realizar asserções em nome do chamador), agora recebo testes que falham onde não deveriam estar - o único teste em particular está afirmando que uma determinada condição deve gerar falhas de teste - o que realmente acontece, mas como o "catch" não funciona mais como sempre, esses testes, embora realmente passando, são relatados como falhas.

Portanto, acho que não posso lançar asserções NUnit adequadas ou usar Assert.That em código de biblioteca destinado a auxiliar no desenvolvimento de teste mais rápido executando tarefas tediosas para o usuário.

Eu concordaria com declarações anteriores de que se a exceção não pode ser consumida pelo usuário, ela não deveria estar disponível para o usuário lançar; no entanto, isso não resolve o problema em que tenho Assert.That código em vigor para o consumidor e testes para provar que eles lançam falhas porque o código faz o que deveria: falha com asserções nunit.

Talvez devêssemos produzir algumas das APIs de resultado?

@fluffynuts Se bem entendi, a cláusula catch está em seu código de teste, não no código de sua biblioteca de usuário. (Se também estiver na biblioteca do usuário, podemos falar sobre isso, mas os problemas são um pouco diferentes.)

Obviamente, o NUnit tem testes de si mesmo, então tínhamos exatamente o mesmo problema potencial quando essa alteração foi feita. Naturalmente, modificamos nossos próprios testes para que continuassem a passar. Você pode fazer a mesma coisa.

A primeira linha de defesa é Assert.Throws . Na maioria dos casos de comportamento de falha de teste, você pode substituir try / catch(AssertionException) por Assert.Throws<AssertionException> diretamente. Internamente, Assert.Throws cria um descartável isolado TestExecutionContext para que os resultados reais do teste não sejam poluídos pelo erro que você está testando.

Em alguns casos, talvez você precise criar seu próprio contexto isolado. Você pode ver como fazer isso examinando o código de Assert.Throws .

Muitos dos testes de falha do próprio NUnit, no entanto, usam uma abordagem diferente. Temos uma montagem separada com testes executados por nossos próprios programas de teste. Capturamos o resultado e podemos examiná-lo. Isso é mais trabalhoso, mas evita colocar muitos detalhes de implementação sobre o NUnit em nossos testes. A maioria dos métodos que fazem isso são encontrados em nosso conjunto de utilitários de teste. Acho que é a isso que @ jnm2 se refere.

Poste qualquer dúvida ou entre em contato comigo off-line para obter mais ajuda com isso.

Obrigado pela informação. Sim, as afirmações são capturadas no código de teste. Assert.Throws normalmente resolveria o problema, mas o teste específico em questão executa o código da biblioteca com um delta "muito baixo" permitido para testar um valor de data e hora que deve ser colocado e recuperado de um banco de dados. Na maioria das vezes, o desvio experimentado para isso em localdb é de 1 ms, mas às vezes ele aumenta - e este teste visa apenas afirmar que o "às vezes" acontece. Então, no momento, eu executo o código em 4 threads paralelos, 10 vezes e espero pelo menos uma falha. Assert.Throws é, infelizmente, muito determinista para este caso, então eu preciso parar de usar exceções nunit (ou Assert, dentro do código da biblioteca), ou eu preciso aprender o fu que vocês usam. Agradeço qualquer sugestão - pode acontecer offline, se quiser.

Heh, acabei de perceber que as falhas do teste de bomba de mensagem para meu AsyncVoidVerificationScope que estou vendo são exatamente o mesmo problema que @fluffynuts está tendo. Na verdade, devido à atualização do NUnit 3.10, não ao meu [assembly: RestoreSynchronizationContext] ITestAction. Bem feito para mim, por ignorá-los enquanto faço várias coisas ao mesmo tempo. : D

De qualquer forma, parece que o que eu quero fazer é embrulhar minha invocação de delegado em using (new TestExecutionContext.IsolatedContext()) assim como Assert.Throws faz.

Assert.Throws e Assert.ThrowsAsync não estabelecem contextos isolados antes de invocar delegados assíncronos. Por que é isso?

@fluffynuts Há muito tempo, quando escrevi Assert.Catch ele simplesmente detectou qualquer exceção e não gerou um erro se nenhuma exceção foi lançada. Infelizmente, isso mudou e não temos mais um equivalente "neutro" de Assert.Throws . Você pode escrever um, no entanto. Aqui está uma versão não testada com base no código em Assert.Throws . Ele só lida com código síncrono, mas você pode estendê-lo facilmente para assíncrono usando código adicional de Assert.Throws .

`` `C #
public static void Exception SafelyCatchAnNUnitException (código TestDelegate)
{
Exceção catchException = null;

using (new TestExecutionContext.IsolatedContext())
{
    try
    {
        code();
    }
    catch (Exception ex)
    {
        caughtException = ex;
    }

    return caughtException;
}

}
`` `

O método retornará qualquer exceção lançada, limpando quaisquer resquícios do erro deixado pelos métodos de assert do NUnit. Se nenhuma exceção for lançada, ele retornará nulo.

Também é possível usar IsolatedContext() em um nível superior em seu código para recuperar o resultado real do teste dele. Isso permitiria que você teste inteiramente no nível do resultado do teste, sem qualquer referência a exceções. Pensei em escrever uma biblioteca para esse tipo de teste, que acho que seria superior à maneira como o NUnit atualmente faz seus próprios testes.

Esta página foi útil?
0 / 5 - 0 avaliações