Nunit: Recurso de instância por caso de teste

Criado em 24 nov. 2017  ·  112Comentários  ·  Fonte: nunit/nunit

O modelo mental de todos parece ser que há uma instância criada por caso de teste ao paralelizar dentro de um dispositivo elétrico (https://github.com/nunit/nunit/issues/2105, https://github.com/nunit/nunit/issues / 2252, https://github.com/nunit/nunit/issues/2573), embora esse nunca tenha sido o caso. Eu tendo a resolver esse problema sempre usando classes estáticas e definindo uma classe de fixture aninhada descartável que fornece uma experiência mais rica em alguns aspectos ( exemplo ), mas isso pode não ser o que queremos para todos. A pequena desvantagem aqui está na terminologia, que a classe estática é o que o NUnit considera ser o acessório, mas o acessório real é a classe aninhada.

Não é uma opção tornar a instância por caso de teste o padrão porque isso interrompe aparelhos não paralelos que dependem de um teste ser capaz de acessar o estado de outro teste. Pessoas que usam o atributo [Order] são provavelmente as únicas pessoas que ele afetaria, mas quebraria totalmente qualquer teste dependente de ordem que eu possa imaginar.

Eu gosto da proposta de Charlie:

  • Um atributo de nível de montagem que permite definir a maneira como os acessórios de teste são criados para serem uma instância única ou instância por acessório.
  • A instância única funcionaria exatamente como agora
  • Instância por acessório criaria um novo acessório para cada teste
  • OU Execute as atividades de configuração e desmontagem uma vez para cada teste (ou seja, não mais "uma vez") OU faça com que OneTimeSetUp e OneTimeTearDown sejam marcados como erros.

Extensões possíveis:

  • Torne este nível de classe também, permitindo que dispositivos elétricos individuais tenham diferentes ciclos de vida.
  • Tenha uma terceira configuração de nível de montagem que executa automaticamente instâncias separadas para qualquer acessório que contenha um ou mais testes executados em paralelo. (É complicado saber sempre com antecedência)

Eu diria que faça o básico primeiro, no entanto.

No momento, é garantido que será um bug se houver estado de instância mutável e um fixture rodar em paralelo consigo mesmo, então poderíamos ter uma opção Auto que muda para instância por caso de teste se os casos de teste do fixture estiverem configurados para rodar em paralelo e evite quaisquer alterações significativas . Acho que Auto tem o potencial de ser confuso, mas também pode ser o padrão mais sensato para que todos não adicionem [assembly: InstancePerTestCase] por rotina.

Sou a favor de grades de proteção. OneTimeSetUp e OneTimeTearDown devem ser sinalizados como erros se o aparelho fizer uma instância por caso de teste. Talvez a menos que sejam estáticos?

done feature normal docs releasenotes

Comentários muito úteis

alguma chance de conseguirmos uma revisão? parece que estamos chegando muito perto

Todos 112 comentários

Atualmente, você __pode__ compartilhar o estado entre os testes paralelos, desde que (1) você defina o estado no construtor ou na configuração única e (2) não o altere em nenhum teste. Você pode até mesmo sobreviver em alguns casos se o estado for definido como uma constante no teste ou configuração, desde que você nunca o defina com qualquer outro valor. Este último é nojento, embora eu ache que temos alguns testes NUnit que o fazem.

Meu pensamento é que não podemos mudar o padrão tão cedo, ou nunca. Ter um estado mutável atualmente não garante um bug. É apenas um bug se você realmente modificá-lo e não sabemos disso, a menos que façamos uma análise de código - o que não vamos fazer!

Se você não transformar nenhum estado de instância em um teste ou configuração ou desmontagem, você não será quebrado. Se o fizer, você já está quebrado, contanto que seus testes possam ser executados em paralelo com eles mesmos. Não há nenhuma maneira de você não estar quebrado que eu possa pensar. Essa é a justificativa para a opção Auto, se pensarmos que a complexidade resultante para o usuário vale o resultado dos "padrões razoáveis".

Você tem razão, mas eu continuaria a defender que esta é uma extensão potencial, que não precisamos decidir agora. Vamos fazer isso iterativamente, em vez de tentar tomar todas as decisões agora.

eu estou com @CharliePoole os padrões devem permanecer como estão e não podem ser removidos, caso contrário, poderia quebrar as pessoas que desejam esse comportamento.

talvez um atributo de nível de fixação para acionar instância por thread em vez de instância compartilhada por todos os threads.

@BlythMeister Não se preocupe! Não estamos considerando nenhuma mudança que possa fazer com que o código existente comece a quebrar.

Isto é interessante. Acho que instância por caso de teste é mais fácil de compreender do que instância por thread. As maneiras como os testes são atribuídos a threads não seriam confiáveis, portanto, não consigo pensar em nenhum benefício que teria sobre a instância por caso de teste.

Sim, estou usando o termo thread para significar execução paralela ... o que é factualmente impreciso.
me desculpe.

Em termos de XP (e espero que todos saibam que essa é sempre minha perspectiva), temos três histórias, que se baseiam uma na outra ...

  1. O usuário pode especificar o comportamento de instância por caso de teste globalmente para todos os acessórios em uma montagem.

  2. O usuário pode especificar o comportamento de instância por caso de teste em um dispositivo elétrico.

  3. Os usuários podem especificar o comportamento de instância por caso de teste para aparelhos que contêm testes que podem ser executados em paralelo.

Eu gostaria de ver (1) totalmente implementado nesta edição. Acho que devemos simplesmente tirar (2) de nossas cabeças - ou pelo menos fora da parte que escreve o código - por enquanto. Acho que (3) é uma possibilidade, mas contém algumas armadilhas que já encontramos em outras questões. (Um fixture não sabe se terá casos de teste rodando em paralelo!) Vamos falar sobre aquele JustInTime, que seria após (1) e (2) serem concluídos e mesclados. FWIW, acho que temos uma tendência a ficar amarrados ao tentar pensar vários movimentos à frente. Mencionei (2) e (3) para que não os bloqueemos de forma alguma, o que parece fácil de evitar.

Acho que estamos todos de acordo então. Eu tenho algumas prioridades NUnit antes disso, então vou deixar para outros comentarem e possivelmente escolherem.

A beleza de fazer isso com um atributo em vez de uma configuração de linha de comando é que funcionará para qualquer executor.

Eu concordo com @CharliePoole que deve ser um atributo. Os testes precisarão ser projetados com a instância por teste ou instância por dispositivo em mente, portanto, eles devem sempre ser executados dessa maneira. Também prefiro fazer apenas a opção 1. Acho que se você deseja esse estilo de teste, deve aceitá-lo para todo o seu conjunto de testes. Permitir que você aceite em níveis mais baixos introduz uma complexidade que não acho que tenha um bom ROI.

Apenas curioso para saber se há algum plano iminente para implementar esse recurso?

@rprouse Isso ainda está marcado como requerendo design, mas parece que a discussão já cobriu tudo.

@CharliePoole concordou que isso pode ser diferente do design. @pfluegs , não começamos a trabalhar nisso além de projetá-lo, então não há planos ainda.

Não tenho certeza se alguém presta muita atenção a isso, mas a ausência de discussão ou rótulos de design implica que alguém poderia se voluntariar. 😄

Eu acabei de ser duramente mordido por este mesmo problema - 1 para consertar.

Caso desejemos mais opções, gostaríamos de:

`` `c #
[montagem: FixtureCreation (FixtureCreationOptions.PerTestCase)]

And:
```diff
namespace NUnit.Framework
{
+   public enum FixtureCreationOptions
+   {
+       Singleton, // = default
+       PerTestCase
+   }
}

Meu próprio pensamento sobre isso mudou um pouco, pelo menos no contexto de uma nova estrutura.

Agora acho que deve haver dois atributos de nível de classe, um para cada comportamento. Eu faria o TestFixture funcionar com uma instância por teste e a luminária compartilhada funcionar como o NUnit agora. Obviamente, essa é uma mudança significativa no contexto do NUnit, mas a abordagem geral pode valer a pena considerar.

@CharliePoole [TestFixture(FixtureOptions.PerTestCase)] parece uma boa sintaxe, mas eu quero fortemente um atributo de nível de montagem também para que eu possa ativá-lo em todos os projetos de teste. Qual seria a aparência de um atributo de nível de montagem?

Certo, é por isso que expressei a reserva WRT sobre o que faria em uma nova estrutura. No caso do NUnit, entretanto, você não gostaria de mudar a semântica de TestFixture e não consigo pensar em um nome realmente bom para o comportamento instância por caso. Se você tivesse dois nomes, tudo o que você teria que fazer para obter o comportamento desejado em todos os aparelhos seria uma edição global de seu arquivo de origem.

Não gosto de configurações globais no nível do assembly porque elas acabam confundindo alguns desenvolvedores, que podem não estar cientes do que está definido no arquivo AssemblyInfo. Atualmente, permitimos alguns, então suponho que não haja grande mal em adicionar outro. Não tenho nenhuma ideia sobre um nome para o atributo de nível de montagem, exceto que ele deve comunicar que estamos definindo uma opção (padrão?) Para todos os acessórios na montagem.

Eu também não gosto de padrões em nível de assembly até certo ponto, pelo motivo que você mencionou. Em comparação com a adição de um novo atributo a cada uma das minhas classes de acessórios sem atributos atualmente, o atributo de nível de montagem é a rota que eu prefiro.

@ nunit / framework-team O que você acha? Para a introdução inicial do recurso:

  • sim / não para configuração em nível de montagem?
  • sim / não para configuração de nível de fixação?
  • sim / não para configuração em nível de método?
  • sim / não para a configuração de nível de caso de teste?

Bom ponto sobre classes de fixture sem atributos.

Eu diria sim, sim, não, não

Eu concordo com Charlie - com o esclarecimento de que 'nível de fixação' significa 'nível de classe'. (Ao contrário de, por exemplo, um método de fonte de caso de teste. 😊)

Isso ajuda! Portanto, o problema com [TestFixture(FixtureCreationOptions.PerTestCase)] é que vários atributos TestFixture são permitidos na mesma classe e eles podem se contradizer. Melhor e mais simples, talvez ter um novo atributo nomeado aplicável a assemblies e classes?

`` `c #
[montagem: ShareFixtureInstances (false)]

[ShareFixtureInstances (false)]
public class SomeFixture
{
// ...
}

Framework API:

```diff
namespace NUnit.Framework
{
+   [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class)]
+   public sealed class ShareFixtureInstancesAttribute : PropertyAttribute
+   {
+       public bool ShareFixtureInstances { get; }

+       public ShareFixtureInstancesAttribute(bool shareFixtureInstances);
+   }
}

Soa bem. Qual é o comportamento de herança disso - se eu tiver o atributo em uma classe base, com seus próprios testes, e também herdar dessa classe? 😊

Presumivelmente, como é um atributo de 'classe' - deve estar na classe real a partir da qual os testes em questão estão sendo executados?

Além disso, não estou totalmente interessado no nome - como um acessório nem sempre é uma classe em termos de nunidade, eu não acho (por exemplo, todos os testes em um testcasesesource - a menos que 'suite' e 'acessório' tenham uma semântica mais forte significando do que eu penso, em termos Nunit!)

Que tal algo parecido com InstancePerTest ... mas melhor nomeado ...

Acordado sobre o nível de montagem e classe, mas não mais profundo. Eu também prefiro algo parecido com a sugestão de ClassInstancePerTest(bool) . Eu também suponho que o atributo em um nível de classe substituiria o nível de montagem e eu esperaria que fosse herdado. Acho que a herança é importante porque você normalmente desejaria uma instância por teste com base nos membros de uma classe combinada com paralelismo. Portanto, você gostaria que isso fosse aplicado automaticamente às classes derivadas.

Certa vez, fiquei genuinamente surpreso com o fato de que o NUnit não cria uma instância por teste quando um tipo de acessório de teste é construtível. Sempre pensei em testes de unidade como, não sei, teste de unidade : você configura o andaime, executa o teste e o andaime desaparece. Nenhuma transferência inadvertida de estado entre os testes.

Tendo aprendido isso. Eu, como @ jnm2 , instancio testes manualmente. Mas quando os testes, por exemplo, criam e excluem arquivos, os responsáveis ​​pelo caso tornam-se realmente incômodos: eles devem ser IDisponíveis; Não há mais [TearDown] mágicos para você. :( Nenhuma maneira consistente de identificar erros de teste de erros de estrutura também. Este é um caso de uso realmente doloroso.

Se eu puder interferir no design, estou com @CharliePoole em sim, sim, não, não.

O nome PerTestInstance , InstancePerTest ou algo semelhante soa muito melhor do que ShareFixtureInstances . Não gosto da palavra Class no nome do atributo, pois quando estou testando o código F #, não penso nas instâncias de tipo sendo classes . Mas com certeza posso viver com isso! :)

Nossa, mal posso esperar por esse recurso!

@ kkm000 Concordo com o uso de "classe". NUnit normalmente usa nomes que são distintos da implementação. Mesmo assim, as pessoas fazem suposições, mas é melhor ser consistente ao evitar tais coisas.

Exemplos do que estou falando: Test, TestFixture, SetUpFixture, Worker (not thread), etc. Em princípio, uma classe __poderia__ representar um único teste, não um fixture ... Não estou dizendo que fazemos isso ou mesmo deveríamos, mas __nós poderíamos__.

@ kkm000

Obrigado pelo comentário! Você não está invadindo de forma alguma. Nosso objetivo de design é ser agnóstico em relação ao idioma sempre que possível.
Além do foco em C #, uma outra coisa sobre a palavra class : e se um dia oferecermos suporte a fixtures que não são classes?

[InstancePerTestCase] contornaria o problema, mas pode perder a clareza intuitiva.

Hoje, os jogos e as classes não divergem muito, mas se eles divergiam, tenho quase certeza de que esse atributo se preocupa com os jogos e não com as classes.

Suites são um conceito organizacional genérico, enquanto acessórios são coisas que mantêm a lógica de configuração / desmontagem e estado de teste e são passados ​​para métodos de teste (atualmente como argumento this dos métodos de instância). As luminárias não precisam fazer parte da hierarquia organizacional. Vamos imaginar acessórios divergindo do conceito de classes e divergindo do conceito de suítes de teste:

`` `c #
public static class SomeTestSuite // Não é um fixture: nenhuma configuração / lógica de desmontagem e nenhum estado de teste
{
[Teste] // Teste único, não um conjunto parametrizado;
// fixture é injetado por meio de um parâmetro em vez de via implícito este parâmetro
public static void SomeTest (dispositivo SomeFixture)
{
fixture.SomeService.SomeProperty = true;
fixture.SomeAction ();
fixture.AssertSomething ();
}
}

[TestFixture]
public struct SomeFixture: IDisposable
{
public SomeService SomeService {get; } // Estado

public SomeFixture() // This was almost legal syntax in C# 6, but I'm making a point 😁
{
    // Setup
}

public void Dispose()
{
    // Teardown
}

public void SomeAction()
{
    // Helper method
}

public void AssertSomething()
{
    // Helper method
}

}
`` `

No exemplo acima, [FixtureInstancePerTestCase] ou [SharedFixtureInstances(false)] não está focado em classes e não está focado em hierarquia (suítes): está focado em acessórios, e acessórios são a configuração reutilizável / lógica e estado de desmontagem. Especificamente, ele se concentra em se um novo SomeFixture é criado para cada caso de teste ou se o mesmo é passado para cada teste.

Uma vez que este atributo controlará se diferentes ITests compartilharão instâncias de ITest.Fixture, e o que está sendo controlado é o que decoramos com [TestFixture] (outro atributo com Fixture no nome), parece um opção _consistent_ para usar a palavra Fixture.

(Estou trazendo pontos que parecem importantes para discutir, mas não estou super investido no que o nome acaba sendo.)

@ jnm2 Você está certo ao dizer que o conceito de suíte e de fixação são separáveis. NUnit, no entanto, seguiu o exemplo de todas as estruturas de teste xunit existentes na época em que foi criado, combinando os dois. Ele se desviou de outras estruturas ao tornar o acessório compartilhado.

Até onde sei, o xunit.net foi o primeiro (único?) Framework a tornar o fixture uma entidade separada da hierarquia de herança de teste.

Quando criei testes parametrizados, poderia ter introduzido o TestCaseSource como um "acessório" separado. Eu não fiz isso e acho que as alterações necessárias para refazer estariam quebrando agora.

Acho que o motivo pelo qual as pessoas ocasionalmente (antes do paralelismo) perguntam sobre o comportamento instância por caso é que é o que outros frameworks fazem. Por uma razão semelhante, eles __não__ pediram acessórios separáveis.

Então, resumindo, aqui está o que eu acho ...

  1. Precisamos de instância por comportamento de caso de teste.

  2. Não pode ser o padrão devido à compatibilidade com versões anteriores.

  3. Um acessório separável é uma ideia legal, mas também é um problema separado.

  4. Os acessórios separáveis ​​parecem pelo menos tão complexos de implementar quanto os acessórios de instância. É claro que você não teria que lidar com compatibilidade com versões anteriores neste caso, o que torna isso mais fácil.

Apenas para confirmar sobre os pontos 3 e 4, minha amostra de código não pretendia ser uma sugestão de novos recursos, mas um experimento mental com o objetivo de nos ajudar a encontrar o nome certo para esse atributo. O experimento mental me diz que estamos realmente focados na ideia de um acessório, não tanto uma classe ou uma suíte, mesmo que eles coincidam agora.

Qualquer que seja a forma que assumam, os acessórios são definições reutilizáveis ​​de configuração / desmontagem / estado. Acredito que seja esse o significado da palavra. Instâncias de fixação (ocorrências de configuração / desmontagem / estado) são o que nosso novo atributo decide compartilhar ou não entre os casos de teste. Acho que FixtureInstances em algum lugar do nome traria clareza máxima.

Isso parece certo para todos? Se não, que outros pontos podemos considerar?

Ah! Desculpe, pensei que você tinha acertado em um ponto muito bom, geralmente esquecido, mas também estava com medo de que pudéssemos estar divagando. : ligeiramente_frowning_face:

Eu acho que o nome completo (o que você digitar) deve incluir Fixture e Instance. Como isso é feito depende se você está falando sobre um nome de atributo ou um enum usado em sua construção. Não tenho certeza se você decidiu por um ou outro.

Sim, bom ponto.

Se fizermos um enum, ficará mais demorado para digitar. Isso é mais importante para os atributos direcionados à classe do que aos direcionados ao assembly. Também devemos evitar adicionar um enum como parâmetro a TestFixtureAttribute porque vários TestFixtureAttributes são permitidos na mesma classe e o parâmetro de um pode contradizer o parâmetro do outro. Podemos lidar com isso por meio de um erro de tempo de teste, mas geralmente é bom tornar os estados inválidos não representáveis.

Vamos obter algumas opções e podemos ajustar a partir daí.
Estamos procurando por algo que equilibre intuitividade, consistência com o estilo do NUnit e a quantidade certa de flexibilidade. (Na maioria das vezes, essas coisas já devem se sobrepor. 😄)


Proposta A

`` `c #
[montagem: FixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
classe SomeFixture
{
}

Pro: extending later if there's ever more options for fixture handling (doubtful?)
Pro: two names so there's less stuffed into a single name
Con: `[FixtureOptions]` would be legal but highly misunderstandable syntax. (Does it reset the assembly-wide setting to the default, `false`, or does it have no effect, e.g. `null`?)


### Proposal B

```c#
[assembly: FixtureOptions(FixtureCreationOptions.PerTestCase)]

[FixtureOptions(FixtureCreationOptions.Singleton)]
class SomeFixture
{
}

public enum FixtureCreationOptions
{
    Singleton, // = default
    PerTestCase,
    // Doubtful there will be other options?
    // Maybe Pool, where we reset and reuse between nonconcurrent tests?    
}

Pro: estados inválidos não são representáveis
Pro: estender mais tarde se houver cada vez mais opções para manuseio de acessórios (duvidoso?)
Con: redundância na digitação do nome do atributo e do nome do enum

Proposta C

`` `c #
[assembly: FixtureInstancePerTestCase]

[FixtureInstancePerTestCase (false)]
classe SomeFixture
{
}
`` `

Pro: estados inválidos não são representáveis
Pro: difícil de entender (mas tenho um ponto cego aqui porque não sou um novo usuário)
Con: nome longo
Talvez con: nome muito específico que pode especificar a opção e o valor. (Mas também temos [NonParallelizable] que gosto.)


(Propostas alternativas são bem-vindas!)

Há algo a ser dito sobre ter dois atributos: um para a montagem e outro para a própria classe.

Pro: mais fácil de implementar dois atributos simples em vez de um atributo com comportamentos diferentes no contexto. (Lição aprendida com ParallelizableAttribute)
Pro: provavelmente mais fácil de entender para os usuários, já que cada um pode ser nomeado de forma adequada. Por exemplo, o nível de montagem pode incluir a palavra "Padrão".
Contra: Dois atributos a serem lembrados.

Abordagem usando cada uma de suas alternativas:

`` `C #
[montagem: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
classe SomeFixture
{
}

[montagem: DefaultFixtureOptions (FixtureCreationOptions.PerTestCase)]

[FixtureOptions (FixtureCreationOptions.Singleton)]
classe SomeFixture
{
}

[assembly: DefaultFixtureInstancePerTestCase]

[FixtureInstancePerTestCase (false)]
classe SomeFixture
{
}
`` `

Outros comentários:

  • Eu gosto de A e B mais do que C
  • Você não deve chamar o comportamento de fixação compartilhada de "Singleton", porque não é um. : smile: Se você herdar, você obterá várias instâncias do acessório base.
  • Se você usar atributos separados no nível da montagem e do acessório, você pode realmente retirar o acessório do nome no nível do acessório.
  • Não especificamos como SetUpFixtures se encaixam nisso. Eles são problemáticos e podem precisar de um erro de tempo de execução se o atributo for usado neles. Na verdade, uma vantagem de um atributo totalmente separado é que não temos que lidar com isso. (Também é uma vantagem para uma propriedade de TestFixture, BTW)
  • Precisamos avisar as pessoas que eles instância por caso de teste não se aplicam a classes estáticas. É óbvio para todos nós, mas há pessoas que não vão entender imediatamente.

BTW, muitas vezes eu desejei que tivéssemos optado por DefaultTestCaseTimeOut para os níveis de montagem e fixação e mantido o Timeout apenas para métodos.

Bom, obrigado por mencionar essa possibilidade.

Algo me incomodando agora é que isso poderia teoricamente fazer sentido, mesmo que não estejamos indo nesta direção:

c# [DefaultFixtureOptions(InstancePerTestCase = true)] class SomeFixture { [FixtureOptions(InstancePerTestCase = false)] public void SomeTest() { } }

Porque o nível em que isso realmente ocorre não é o nível do aparelho em si, mas sim o nível do caso de teste.
Por exemplo: um acessório derivado pode substituir a configuração do acessório base, mas tudo o que ele realmente está fazendo é afetar a configuração de cada caso de teste produzido pela classe derivada.

Ou:

`` `c #
[montagem: DefaultFixtureOptions (InstancePerTestCase = true)]

// Por que isso também não deve ser considerado um padrão?
[DefaultFixtureOptions (InstancePerTestCase = true)]
classe abstrata pública BaseFixture
{
}

classe selada public SomeFixture: BaseFixture
{
}
`` `

Como podemos chegar a uma justificativa para quando e quando não usar Default ?

Acho que implementar esse atributo em cada caso de teste introduziria uma grande complexidade com apenas um pequeno benefício.

Para acessórios de teste "normais", continuaremos a instanciar a instância como parte da cadeia de comando associada ao dispositivo de teste. Por exemplo, acessórios de teste, não instanciaremos lá, mas teremos que implementar um novo comando como parte da cadeia de teste, o que fará praticamente a mesma coisa que o que agora é feito na cadeia de acessórios.

Se um aparelho pode conter os dois tipos de teste, teremos que executá-lo duas vezes, uma para cada modelo.

O benefício parece pequeno, já que o usuário pode facilmente obter a mesma coisa dividindo os testes em acessórios separados. Afinal, as luminárias são um artefato NUnit e é razoável exigir que os usuários coloquem testes que precisam do mesmo comportamento de luminária na mesma luminária.

Com relação a quando usar "Padrão" ... parece-me que você __não__ o usaria em qualquer TestFixxture e apenas no nível de montagem. As classes básicas não são uma exceção porque o atributo é realmente encontrado na classe derivada em virtude da herança. Portanto, definir o comportamento específico na base não é um padrão. Em vez disso, é a configuração para aquela classe e qualquer derivado dela. As especificações contraditórias devem causar um erro ou ser ignoradas, como fazemos em todos os casos de atributos herdados atualmente. Ou seja, colocar o atributo A na classe base e B na classe derivada não é diferente de colocar A e B diretamente na classe base. Nós o projetamos dessa forma porque seria surpreendente para o usuário fazer qualquer outra coisa.

Dispositivos de configuração WRT, aliás, acho que deve gerar um erro de tempo de execução para especificar qualquer opção de instanciação lá. SetUpFixtures não são TestFixtures.

Ou seja, colocar o atributo A na classe base e B na classe derivada não é diferente de colocar A e B diretamente na classe base. Nós o projetamos dessa forma porque seria surpreendente para o usuário fazer qualquer outra coisa.

Nossa, eu esperava que [Apartment] ou [Parallelizable] substituísse a classe base ou a configuração do método base!

Tudo isso faz sentido para mim.

Podemos descartar a possibilidade de um enum como FixtureCreationOptions ? PerTestCase , PerFixture , hipotético ReusedPerTestCase ?

Estou inclinado para a simplicidade de:

`` `c #
[montagem: DefaultFixtureOptions (InstancePerTestCase = true)]

[FixtureOptions (InstancePerTestCase = false)]
classe SomeFixture
{
}

Hypothetical other creation options would then be added this way:
```c#
[FixtureOptions(InstancePerTestCase = false, ReuseInstances = true)]

@ nunit / framework-team Quais são suas preferências?

Em relação a

[assembly: DefaultFixtureOptions(InstancePerTestCase = true)]

Talvez só eu, mas geralmente entendo os atributos como uma indicação de que o objeto marcado possui uma certa característica, não presente de outra forma; argumentos para o atributo, se houver, estão refinando o referido traço. Tome, por exemplo, Serializable - o objeto é serializável, mas se não for decorado, não é; InternalsVisibleTo - internos visíveis para algo específico e os argumentos refinam isso, caso contrário, internos não são visíveis para nada, etc. Alguns atributos não estão seguindo o padrão, mas geralmente são de baixo nível, por exemplo, CompilationRepresentation, MethodImpl. Eles geralmente vêm com os argumentos do construtor necessários.

Agora considere
c# [assembly: DefaultFixtureOptions]
Este é um C # bem formado, pois a atribuição a InstancePerTestCase é opcional. O que ele expressa semanticamente?

Para mim, [assembly: DefaultFixtureInstancePerTestCase] parece mais natural (com exceção de seu nome sesquipedal, mas isso é apenas uma opinião, não justificável exceto esteticamente). Um atributo, um traço. Talvez este não precise de nenhum parâmetro ou propriedade opcionalmente configurável.

Além disso, como usuário, acho surpreendente que haja dois atributos com nomes diferentes, um aplicável apenas no nível de montagem ( DefaultSomething ) e outro apenas no nível de tipo (apenas Something ) . Naturalmente, não entendo qual foi a "lição aprendida" que @CharliePoole mencionou sobre o atributo Parallelizable , mas deste lado da ilha, um par de atributos para saber onde se faria (no pensamento superficial deste usuário) parece perplexo.

@ kkm000 Concordo com seu comentário sobre um atributo que expressa uma única característica.

WRT dois atributos diferentes para o "mesmo" traço em níveis diferentes, eu __deria__ concordar se pensasse que eles eram realmente os mesmos. Mas não acho que sejam. Vou usar o Timeout como exemplo.

Se o tempo limite for colocado em um método de teste, isso significa "Este método de teste atingirá o tempo limite se demorar mais do que o número de milissegundos especificado para ser executado." No entanto, quando o tempo limite é colocado em um acessório ou montagem, ele tem um significado diferente: "O tempo limite padrão para todos os casos de teste contidos neste item está no número especificado de milissegundos." Esse padrão pode, é claro, ser substituído em níveis inferiores. Dois significados bastante diferentes, que, a propósito, têm duas implementações separadas. O primeiro aplica o tempo limite ao teste no qual ele aparece. O segundo armazena o padrão no contexto de teste onde será encontrado e usado como padrão quando os casos de teste contidos forem encontrados. ParallelizableAttribute funciona de maneira semelhante.

Neste caso particular, colocado em um acessório, o atributo significa "Instancie este acessório uma vez por caso de teste." No nível de montagem, significa "Instancie acessórios de teste neste conjunto uma vez por acessório, a menos que especificado de outra forma."

Neste caso particular, colocado em um acessório, o atributo significa "Instancie este acessório uma vez por caso de teste." No nível de montagem, significa "Instancie acessórios de teste neste conjunto uma vez por acessório, a menos que especificado de outra forma."

Supondo que você errou na última frase - foi _ "Instanciar luminárias de teste nesta montagem uma vez por ~ fixture ~ caso de teste, a menos que especificado de outra forma." _ Pretendido? Ou estou seriamente entendendo mal o escopo e a intenção?

Assumindo que eu não sou, para mim, como um usuário, a semântica não parece diferente em tudo: "instanciar uma classe por teste dentro do escopo marcado pelo atributo ", talvez esclarecido com "a menos que seu escopo interno seja marcado de forma diferente, se houver é de fato um escopo interno ”, seja um acessório ou o conjunto. Apenas minha opinião, mas dois atributos parecem um pouco confusos. Talvez seja apenas minha maneira de pensar, realmente não sei.

duas implementações separadas

No mundo gedanken de unicórnios e arco-íris, a implementação não afeta o lado voltado para o usuário. Mas nós (você, o designer do NUnit e eu, um de seus usuários), somos todos engenheiros, e a engenharia envolve compromissos. Se usar um atributo para escopos diferentes levasse de fato a desordem interna e sobrecarga de manutenção, eu não reclamaria de forma alguma. :)

No final, para mim, este é um problema menor, e não sinto muito pelo mesmo atributo e posso conviver com os atributos dispostos de qualquer maneira.

@ kkm000 Sim, eu pretendia "uma vez por caso de teste".

Concordo que tudo faz sentido se você pensar nisso como uma série de escopos aninhados. No entanto, a experiência mostra que os usuários (a) nem sempre pensam assim ou (b) não acertam na definição de escopo.

Como exemplo do último, considere as classes aninhadas e as classes básicas. Nenhum deles é tratado pelo NUnit como "escopos" aninhados para fins de teste, mas podem parecer assim para os usuários.

Vou admitir que o problema que mencionei com Timeout / DefaultTimeout é um pouco mais sério, já que o Timeout em um aparelho pode facilmente significar que o tempo limite se aplica a todo o aparelho, o que é claro que não. Por esse motivo, o tempo limite na montagem pode se aplicar a toda a montagem, o que não acontece.

Nesse caso, parece haver menos ambigüidade, porque estamos usando "por caso de teste" no nome, então eu poderia facilmente ir para qualquer lado. Eu não estou trabalhando nisso de qualquer maneira. :sorriso:

Venha para pensar sobre isso, se Timeout tivesse sido chamado de TestCaseTimeout, teria sido melhor!

Estou feliz com um único [FixtureInstancePerTestCase] . Parece a menor carga mental para os usuários.

@ nunit / framework-team Você pode nos ajudar a definir uma direção, passando rapidamente de https://github.com/nunit/nunit/issues/2574#issuecomment -426347735 para baixo?

[FixtureInstancePerTestCase] também é minha opção menos rejeitada. 😄 A falta de extensibilidade é a única desvantagem.

Não gosto do problema [FixtureOptions] - acho isso muito confuso. Eu pessoalmente prefiro um único atributo para assembly e classe - eu entendo que isso nos causou problemas antes para Timeout e Paralellizable - no entanto, quando este tem "Fixture" no nome, parece menos ambíguo para mim.

Eu, pessoalmente, cito, gostei da opção enum ... exceto que não consigo pensar em como nomeá-la apropriadamente para torná-la mais sucinta!

Existe alguma atualização sobre isso?

Quaisquer atualizações serão publicadas para este problema.

Alguém pode resolver esse problema por favor ??
Atualmente, devemos agrupar todo o nosso código de teste assim:
csharp [Test] public void TestExample(){ using(var tc = new MyTestContext()){ //code goes here } }
Usando esse recurso, podemos ter o contexto de teste inicializado na configuração e descartado na desmontagem e ainda ser thread-safe.

Olá, também estamos enfrentando esse problema em nossa empresa. Estamos migrando de uma estrutura que suporta esse recurso (Py.Test), então isso é um problema. Agradeceríamos muito se você pudesse nos mostrar o código-fonte que controla essa instanciação para que possamos bifurcá-la e fazer as alterações necessárias.

@MChiciak
Se esse problema não for corrigido em breve, estamos considerando a migração para xUnit, já que em xUnit este é o comportamento padrão: "xUnit.net cria uma nova instância da classe de teste para cada teste que é executado" https: // xunit .github.io / docs / shared-context

@LirazShay , @MChiciak Também encontrei esse problema, mas concordo com os pontos de @CharliePoole . Portanto, criei meus campos específicos de teste que são inicializados em SetUp [ThreadStatic] e funciona bem.

@dfal
Obrigado pela boa solução alternativa!

Eu adoraria isso. Atualmente, tenho um conjunto de testes existente com milhares de testes em execução no nunit, que não podem ser paralelizados facilmente por causa do estado compartilhado. Se essa opção fosse fornecida, seríamos capazes de paralelizar nosso conjunto de testes com muito mais facilidade.

Como um experimento mental, imagine que simplesmente mudamos a forma como TestFixture funcionava. O que isso quebraria?

  1. Todos os testes que usam ordenação e dependem de alterações de estado feitas por um teste para influenciar outros testes. Esta é uma prática muito ruim no caso de testes de unidade (supostamente) independentes. Sempre alertamos as pessoas para não fazerem isso e não me importaria de quebrar nenhum teste (com uma mensagem, é claro) que dependesse de ter uma única instância dessa forma.

  2. Dependendo do que é feito na configuração única, é possível que as instâncias de fixture individuais não sejam capazes de rodar em paralelo. Isso é irônico, já que a motivação para a mudança é permitir que os aparelhos funcionem em paralelo! Esta não é uma mudança significativa para pessoas que precisam do recurso, uma vez que eles já são incapazes de rodar em paralelo, mas é uma mudança significativa para aqueles que já contam com a execução de testes em paralelo __after__ um `OneTimeSetUp 'não paralelizável.

  3. Qualquer nível de fixture OneTimeSetUp seria executado várias vezes. Isso eliminaria qualquer ganho de eficiência da configuração única e pode, em alguns casos, alterar a semântica. Em particular, se a inicialização afetou o estado global de alguma forma (por exemplo, configurar um banco de dados), isso pode levar a erros. Este parece um rompimento inaceitável para mim.

Achei esse experimento de pensamento útil para pensar sobre como implementar a instanciação de luminárias por caso de teste. Ele claramente precisa ser um novo recurso não padrão se introduzido no NUnit 3. Para o NUnit 4 ou qualquer outra nova estrutura (por exemplo, TestCentric), o novo comportamento pode facilmente ser o padrão.

como uma observação, eu simplesmente substituí TestClass e TestMethod alguns MSTest por TestFixture e Test respectivamente, e descobri que NUnit e MSTest têm semânticas diferentes aqui. O MSTest cria novas instâncias para cada teste onde, como tenho certeza que todos que estão lendo isso sabem, o NUnit reutiliza as instâncias do TestFixture.

Seria bom se eu pudesse escrever TestFixture(lifeCycle = OneTestPerInstance)] ou similar, para facilitar esta migração.

_edit: _ Na verdade, tendo acabado de portar os testes para usar um método Setup , na verdade descobriu-se que o código inicializador de código / antes / desmontagem foi bastante torturado em MSTest e agora é um simples construtor de inicializador de propriedade, que é muito melhor e mais completo, por isso estou feliz por ter feito a tradução. Ainda assim, por uma questão de compatibilidade, seria bom ter.

@CharliePoole

Todos os testes que usam ordenação e dependem de alterações de estado feitas por um teste para influenciar outros testes.

Aah, sim, pare, quebre esses testes, por favor !!! 🤣 Esse é o pior abuso de uma estrutura de teste de unidade que posso imaginar.

Qualquer nível de fixação OneTimeSetUp

Uh, eu sempre pensei que isso fosse executado uma vez por processo ... Eu sempre me perguntei por que não é estático.

Mas sim, o desempenho também é um ponto muito válido.

Este recurso está em discussão há mais de 2 anos, não quero parecer ofensivo, mas há algum plano concreto e realista para realmente implementá-lo no futuro próximo?

Gostaria de saber se minha equipe deve planejar a migração para outro framework ou isso será resolvido nos próximos meses, porque esse problema é um obstáculo para nós.

Esta é uma mudança importante na forma como os testes são executados e precisaria considerar o impacto total para não quebrar completamente aqueles que dependem da funcionalidade atual.

A melhor maneira de "resolver" isso é tornar o thread da classe de teste seguro. Assim como qualquer boa aplicação multithread, a segurança da thread deve ser primordial.
Se você estiver usando, por exemplo, campos que você configurou em um método de configuração e depois usados ​​em muitos métodos de teste, sua classe não é thread-safe. isso não é uma falha do NUnit. Você escreveu um singleton não seguro para threads.

Ao criar uma subclasse dentro de sua TestFixture que contém as informações contextuais que você cria como parte de cada teste, você está então se certificando de que sua TestFixture singleton é threadsafe ... assim ... você "resolve" o problema.
Portanto, em vez de gastar tempo "migrando para outro framework", posso sugerir que você gaste esse tempo tornando seu único TestFixtures seguro ... você provavelmente achará que é um exercício muito mais rápido.

Por exemplo, quando me deparei com o fato de que um NUnit TestFixture é um singleton, converti meus mais de 100 acessórios de teste com mais de 500 testes para serem threadsafe (e tive que mudar de RhinoMocks para Moq, pois RhinoMocks também não é threadsafe) e isso levou me cerca de 4 semanas (para ser honesto, Rhino -> Moq levou a maior parte desse tempo!)

Você está 100% certo em princípio, mas quando você tem um conjunto de testes existente de ~ 7.000 + testes de unidade, apenas reescrevê-los para serem thread-safe é um grande investimento.

Não tenho certeza se é mais um investimento do que mudar para outra estrutura (pode haver alguma ferramenta para automatizar isso), mas não acho que essa solicitação de recurso deva ser descartada por princípio, IMO

Não estou defendendo que esse recurso não deva ser examinado.

Mas uma mudança tão grande na forma como o nunit funciona, na minha opinião, deve ser considerada para compatibilidade com versões anteriores (que pode ser bastante difícil) ou nova versão principal (também não é uma tarefa pequena)

Estou sugerindo que tornar o código um usuário de nunit grava threadsafe para ser executado em um ambiente multithread é uma forma de ação mais fácil. Você poderia até mesmo fazer um script disso.

Não contribuo com este repositório, portanto, não tenho nenhum contexto sobre como ele é implementado. Isso pode ser incrivelmente ingênuo por causa disso, mas não seria possível implementar isso como uma opção opcional? Para que isso não quebre nada?

Toda esta questão está discutindo várias maneiras de implementar uma opção opcional ... há muitas maneiras

Este é um projeto de código aberto composto inteiramente por voluntários. Uma vez que um recurso é aceito, como antes, ele espera que alguém da equipe ou de fora o pegue e o faça. Não há "chefe" para atribuir o trabalho a ninguém. Na verdade, é uma coisa boa que ninguém tenha feito isso se não tiver tempo para se dedicar a fazer isso. Dessa forma, está disponível para quem tiver capacidade e interesse em fazê-lo.

RPs são sempre bem-vindos, mas provavelmente não é uma contribuição inédita para alguém, pois tem o potencial de quebrar muitas outras coisas no NUnit. Minha opinião de qualquer maneira.

Não estou mais contribuindo tão ativamente como no passado, então meus comentários sobre como eu acho que deveria ser feito foram dados como um conselho para quem quer que o faça.

Eu concordo que já existe há algum tempo e adoraria vê-lo feito sozinho.

@BlythMeister Acho que uma opção simplifica um pouco demais - como você provavelmente sabe, mas outros não.

Claro, é um interruptor, mas não se trata apenas de acender uma lâmpada. É mais como mudar seu rádio de AM para FM ... circuitos inteiramente novos entram em ação.

Nesse caso, o que está envolvido é todo um novo ciclo de vida na execução de um teste, portanto, uma boa quantidade de código deve ser substituída. Não é uma coisa pequena. O risco estará em garantir que o novo "circuito" não colida com o antigo de forma alguma.

Desculpe sua direita, minha explicação foi vaga nos por que uma "troca" não é simples!

@BlythMeister Na verdade, fiz exatamente o que você descreveu - criei um objeto descartável para rastrear o estado durante um teste e, em seguida, descarte-o após a conclusão do teste - para conduzir meu equipamento de teste Selenium. Um grande problema com essa abordagem é que o estado não persiste em desmoronamento. Como resultado, não é possível executar ações condicionalmente para testes com falha (capturar imagens, logs ou outras partes do estado) porque o estado do teste não será resolvido dentro do próprio método de teste e só pode ser avaliado durante a desmontagem. Portanto, embora um objeto de estado seja uma maneira poderosa de tornar os testes thread-safe, ele só funciona quando você não precisa fazer nada com o resultado do teste durante a desmontagem.

É útil ter em mente que fazer coisas com o resultado do teste não é o objetivo do TearDown. Claro, eu sei que muitas pessoas usam dessa forma e eu entendo a motivação, mas há muitos casos complicados que basicamente se resumem ao fato de que o TearDown faz parte do teste e não termina até que termine.

Com o NUnit 3, esperávamos ver mais usuários examinando os resultados fora da estrutura usando extensões de mecanismo, mas trabalhar dentro do próprio teste parece ser mais familiar e acessível.

@CharliePoole

  1. Qualquer nível de fixture OneTimeSetUp seria executado várias vezes. Isso eliminaria qualquer ganho de eficiência da configuração única e pode, em alguns casos, alterar a semântica. Em particular, se a inicialização afetou o estado global de alguma forma (por exemplo, configurar um banco de dados), isso pode levar a erros. Este parece uma ruptura inaceitável para mim.

Por que não podemos implementar o recurso Instance-per-test-case e ainda executar o OneTimeSetUp apenas uma vez (manter o thread principal, lembre-se de chamá-lo ou algo assim)?

Porque isso quebraria qualquer OneTimeSetUp existente que inicializasse a instância, o que é muito comum.

Eu vejo isso como um problema em qualquer solução baseada em switch. É um problema menor se definirmos uma alternativa totalmente nova para testar as fixações, uma abordagem que considero bastante tentadora.

Porque isso quebraria qualquer OneTimeSetUp existente que inicializasse a instância, o que é muito comum.

Que tal permitir apenas os métodos estáticos OneTimeSetUp e OneTimeTearDown quando o recurso "instância por caso de teste" estiver habilitado? Isso forçaria os membros inicializados nesses métodos a serem estáticos, obviamente sendo compartilhados entre as instâncias de fixture.

Embora a principal motivação desse problema no Github seja a paralelização, gostaria de incluir o _princípio da menor surpresa_ e _evitar erros causados ​​por testes que influenciam uns aos outros_ como motivações também. (Peço desculpas se eu perdi que alguém já mencionou isso.)

Caso você não tenha lido suficientemente neste tópico bastante antigo, meus comentários não argumentam contra o recurso, mas contra uma implementação específica que foi sugerida. Eu prefiro substituir o TestFixture por um tipo inteiramente novo de fixação, que funciona de maneira diferente.

Acho que o princípio de menor surpresa funciona bem aqui. Não é surpreendente se um novo acessório funcionar de maneira diferente.

Se, por outro lado, criarmos um sinalizador que controle o equipamento existente, posso pensar em cinco ou seis locais diferentes na implementação atual onde precisaríamos ramificar esse sinalizador.

Isso não quer dizer que os dois tipos de acessórios possam não compartilhar determinado código. Mas isso estaria fora da visão dos usuários.

@fschmied BTW, se estivéssemos fazendo um novo framework (ou talvez até mesmo o NUnit 4) então eu argumentaria de forma diferente ... Acho que deveríamos ter tornado muito mais difícil para os testes interferirem uns com os outros no NUnit 2 e 3. No entanto , enquanto estivermos fazendo alterações no NUnit 3, acho que as regras de compatibilidade.

Eu prefiro substituir o TestFixture por um tipo inteiramente novo de fixação, que funciona de maneira diferente.
[...]
Acho que o princípio de menor surpresa funciona bem aqui. Não é surpreendente se um novo acessório funcionar de maneira diferente.

Você está certo, é claro. O que eu quis dizer é que, para o tipo original de instalação de teste, o comportamento ainda será surpreendente para a maioria das pessoas. (Aconteceu de surpreender um colega de trabalho há duas semanas, quando ele encontrou um caso de um teste influenciando outro. Ele usa o NUnit desde 2013, por isso pesquisei este problema.)

Mas é claro, é uma troca entre compatibilidade com versões anteriores, complexidade de implementação e o desejo de corrigir um pecado original [1].

De volta à questão de OneTimeSetUp : Eu acho que ainda é importante ter OneTimeSetUp por instalação de teste, mesmo se a classe de instalação de teste for instanciada por teste. Para evitar confusão no caso de OneTimeSetUp manipular o estado da instância, proponho que OneTimeSetUp (e OneTimeTearDown ) seja estático com o recurso instância por teste. (Não importa se é implementado como um switch ou como um novo tipo de dispositivo de teste.)


[1] James Newkirk escreveu isto :

Acho que um dos maiores problemas que aconteceu quando escrevemos o NUnit V2.0 foi não criar uma nova instância da classe de fixture de teste para cada método de teste contido. Eu digo "nós", mas acho que isso foi minha culpa. Não entendi muito bem o raciocínio do JUnit para criar uma nova instância do dispositivo de teste para cada método de teste.

Ele também escreve isso, no entanto:

[...] seria difícil mudar o jeito que o NUnit funciona agora, muita gente reclamaria [...]

:)

Sim, Jim e eu discutimos sobre isso há anos. : smile: Uma coisa em que concordamos é a última frase. Jim saiu para fazer uma nova estrutura antes de implementar sua abordagem preferida. Basicamente, estou fazendo o mesmo e SetUp / TearDown é algo que provavelmente farei de forma diferente.

Eu fico surpreso com a quantidade de usuários NUnit razoavelmente experientes que não estão cientes do uso da mesma instância para todos os casos de teste. Pode ser que ele fosse mais conhecido quando o NUnit foi lançado porque era uma diferença óbvia do JUnit. Agora tudo isso está esquecido e novas pessoas chegam ao NUnit com expectativas que o NUnit não corresponde. Algo para pensar sobre.

então .. isso é para ganhar? ou ainda em fase de design?

agora no .NET acesse seu XUnit sem TestContext ou NUnit, com testes paralelos padronizados

O recurso é aceito e atribuído a uma prioridade normal. Ninguém é atribuído a ele e não está listado em um marco. Isso significa que alguém tem que decidir se quer trabalhar nisso. Pode ser um committer ou um contribuidor.

Normalmente não fazemos uma fase de design - se quisermos, o rótulo de design é aplicado - mas mesmo assim, quem quer que faça isso seria aconselhado a postar um comentário descrevendo como planejam implementá-lo, especialmente como será Comercial. Se você fosse começar codificando-o, poderia acabar nas discussões de design de qualquer maneira, então provavelmente é melhor acabar com isso primeiro.

Então, você gostaria de trabalhar nisso?

claro, eu acabei de começar a trabalhar em
https://github.com/avilv/nunit/tree/instance-per-test

Sou novo nesta base de código, então estou tentando manter o estilo do código original o máximo que posso
até agora eu introduzi um atributo InstancePerTestCase e planejo tê-lo suportado em nível de montagem e classe

deixe-me saber se estou errado aqui

Eu adicionei comentários sobre alguns detalhes de implementação em seu último commit.

No que diz respeito ao design, acho que um atributo derivado de IApplyToContext faz sentido. Acho que preferiria algo mais geral do que apenas InstancePerTestCase, talvez

C# [FixtureLifeCycle(LifeCycle.InstancePerTestCase]

Isso permitiria defini-lo no nível de montagem e redefini-lo em acessórios individuais.

CharliePoole você é demais, muito obrigado pelo feedback e ajuda

Acho que estou entendendo como as coisas são construídas / o que faz o quê etc. lenta mas seguramente

aqui está minha proposta para o enum LifeCycle (eu também atualizei meu branch com sua sugestão)

    /// <summary>
    /// Specifies the lifecycle for a fixture.
    /// </summary>
    public enum LifeCycle
    {
        /// <summary>
        /// A single instance is created and for all test cases.
        /// </summary>
        SingleInstance,

        /// <summary>
        /// A new instance is created for each test case for fixtures that are marked with <see cref="ParallelizableAttribute"/>.
        /// </summary>
        InstancePerTestCaseForParallelFixtures,

        /// <summary>
        /// A new instance is created for each test case.
        /// </summary>
        InstancePerTestCase
    }

@ jnm2 O que você acha dessa abordagem - você é o tipo de "patrocinador" desse recurso em primeiro lugar. Estou um pouco incerto sobre InstancePerTestCaseForParallelFixtures , o que pode ser um pouco demais para uma implementação inicial. Como de costume, uma vez implementado, estamos presos a ele.

Se __do__ aceitarmos o vínculo com o paralelismo, ele deve ser baseado em contexto, não em atributos. Ou seja, qualquer teste pode ser executado em paralelo, então é mais como `InstancePerTestCaseWhenCasesAreParallel. Dispositivos paralelos não exigem esse recurso. E talvez seja melhor deixar para o usuário decidir.

Também concordo que não deve ser acoplado de forma alguma ao paralelismo. paralelismo foi a razão para adicionar esse recurso, mas uma vez que concordamos que esse recurso é útil, na minha opinião, ele não deve ser refletido no código

Eu concordo, eu entendo o caso de uso para usá-lo apenas em cenários paralelos, uma vez que é onde o modelo padrão atual do NUnit vai te incomodar, mas espero que este seja o ciclo de vida padrão no futuro (nunidade 4?) já que eu acho que realmente deveria ter tem sido assim desde o início

talvez devêssemos deixá-lo em SingleInsance / InstancePerTestCase

outra questão é se devemos lidar com o padrão IDisposable aqui, que tornaria SetUp / TearDown redundante, mas pareceria muito mais natural ao estilo do XUnit.

Já descartamos qualquer Dispositivo de Teste que implemente IDisposable. Você só precisa ter certeza de que continua funcionando.

ótimo, vai fazer.

Provavelmente começarei a adicionar casos de teste hoje, mas como é um mecanismo central, pode adicionar alguns casos que apenas garantem que a mesma coisa funcione quando o InstancePerTestCase estiver ligado, corrija-me se estiver errado

O DisposeFixtureCommand cuida de chamar Dispose.

IMO, seu principal problema é garantir que todo o fluxo de comando para o dispositivo de teste seja invocado __ para cada teste__. As mudanças que vejo até agora criam corretamente um fluxo de comando que parece fazer o trabalho, mas a questão principal é como garantir que ele seja chamado para cada caso de teste. (Observe que a estrutura de comando para acessórios é diferente daquela para casos de teste.) Acho que o item de trabalho composto deve ser sensível ao contexto e invocar o comando corretamente (como você fez), mas não tenho certeza de que terá uma chance para fazer isso para __cada__ caso de teste. (Estou apenas lendo o código, então posso estar errado.)

@CharliePoole parece que você está certo, fiz um pequeno gráfico sobre o que exatamente executa um método de teste:
image

Atualmente eu crio a instância do objeto de teste no loop RunChildren, mas isso não será suficiente para os comandos Retry / Repeat, sem mencionar se há outros wrappers que perdi / alguém precisa de novos.
terá que ser muito mais próximo de TestMethodCommand

Eu atualizei as últimas mudanças em
https://github.com/avilv/nunit/tree/instance-per-test-2

removido InstancePerTestCaseWhenCasesAreParallel
FixtureLifeCycle implementado através de adicionar Construto de fixação / Descarte dentro MakeTestCommand em SimpleWorkItem, e ignorando-lo em CompositeWorkItem MakeOneTimeSetUpCommand

parece funcionar bem, também faz mais sentido com a forma como a estrutura é construída
agora eu só preciso adicionar um monte de testes de unidade para ter certeza de que está tudo funcionando corretamente

alguma chance de ser revisado antes da próxima versão? ainda estou interessado em melhorias / mudanças de curso

Essa abordagem parece boa para mim e eu adoraria usá-la na próxima versão, se pudermos fazer isso!

alguma chance de conseguirmos uma revisão? parece que estamos chegando muito perto

Curioso para saber se houve algum movimento neste recurso? Parece que @avilv chegou bem perto. Já esperava por um recurso como esse há muito tempo e estou ansioso para usá-lo!

Obrigado a todos!

Este recurso está funcionando agora?

@CharliePoole, você poderia revisar a versão mais recente do @avilv ? Parece estar totalmente implementado. Acho que muitas pessoas estão esperando por esse recurso.

@janissimsons Desculpe, mas não estou mais neste projeto, então minha revisão não iria levá-lo adiante. @ nunit-framework-team Alguém não pode revisar isso?

Agradecemos todo o trabalho que foi feito para incorporar o # 3427. @Rprouse, alguma idéia de quando podemos esperar isso em um lançamento?

Como usamos o novo recurso? Alguém poderia escrever uma breve descrição sobre os novos atributos e como usá-los adequadamente?

Estou planejando lançar nas próximas semanas. Eu queria esperar até. O NET 5 é o último e faça alguns testes finais com ele antes do lançamento. Duvido que haja algum problema, mas está tão perto que achei melhor esperar.

@rprouse Alguma estimativa de quando você vai lançar a nova versão? Eu gostaria de começar a usar este recurso.

@janissimsons muito em breve. Estamos resolvendo alguns problemas com nossas compilações .NET 5.0 e alguns outros problemas, então irei lançar. Você pode acompanhar o status neste painel do projeto, https://github.com/nunit/nunit/projects/4

Esta é uma notícia maravilhosa!

Eu entendi corretamente que isso tornará o seguinte possível no NUnit?

Combine tudo isso:

  • Os testes são parametrizados
  • Execute testes em paralelo.
  • Ser capaz de executar variações parametrizadas do mesmo teste em paralelo.
  • Os testes podem ser orientados por dados

Tentei algumas coisas no passado aqui https://github.com/jawn/dotnet-parallel-parameterized-tests

A nova versão ainda será compatível com .NET Full Framework 4.7.2 ou pelo menos 4.8?

Atenciosamente,
DR

@jawn , acredito que tudo funcionará, sim. Eu testei todas essas coisas, mas o NUnit tem muitos recursos, então pode haver casos extremos com algumas combinações de recursos. Sinta-se à vontade para retirar nosso pacote NuGet noturno e fazer um teste! Adoraríamos mais testes e feedback antes de lançarmos.

@drauch sim, a versão 3.13 ainda dará suporte ao .NET 3.5.

@avilv Bom trabalho !!
a propósito, usando qual ferramenta você criou aquele diagrama de fluxo legal?
Obrigado

@rprouse Você também poderia atualizar a documentação sobre como usar este novo recurso? Obrigado!

@janissimsons Estou planejando atualizar os documentos para o lançamento. A versão curta, porém, é adicionar [FixtureLifeCycle(LifeCycle.InstancePerTestCase)] à sua classe de teste, provavelmente junto com [Parallelizable(ParallelScope.All)] e uma instância de sua classe de teste será instanciada para cada teste executado dentro da classe. Isso permite que você use variáveis ​​de membro na classe em execuções de teste paralelas sem se preocupar com a alteração das variáveis ​​de outros testes.

@LirazShay O diagrama da captura de tela foi criado no Visual Studio Enterprise. Você pode clicar com o botão direito do mouse em uma seleção no Solution Explorer e escolher "Mostrar no mapa de código", por exemplo. https://docs.microsoft.com/en-us/visualstudio/modeling/map-dependencies-across-your-solutions ReSharper tem um recurso semelhante.

@ jnm2 Muito obrigado !!!
Tenho VS enterprise e não conhecia esse recurso que pode ser muito útil
Muito obrigado pela dica

Estou certo de que isso foi lançado e faz parte da versão 3.13?

Estou certo de que isso foi lançado e faz parte da versão 3.13?

Acredito que tenha havido alguns problemas observados e corrigidos, especialmente em relação à aplicação de atributo de nível de montagem em # 3720 e estamos aguardando 3.13.1 agora ..

NUnit 3.13.1 foi lançado.
Mais algum problema restante?

Não, é por isso que foi fechado @andrewlaser

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