Você coloca testes de unidade no mesmo projeto ou em outro projeto?

137

Você coloca testes de unidade no mesmo projeto por conveniência ou em uma montagem separada?

Se você os colocar em uma montagem separada como a nossa, teremos vários projetos extras na solução. É ótimo para testes de unidade durante a codificação, mas como você libera o aplicativo sem todos esses conjuntos extras?

leora
fonte

Respostas:

100

Na minha opinião, os testes de unidade devem ser colocados em uma montagem separada do código de produção. Aqui estão apenas alguns contras de colocar testes de unidade no mesmo conjunto ou conjuntos que o código de produção:

  1. Os testes de unidade são enviados com o código de produção. A única coisa fornecida com o código do produto é o código de produção.
  2. As montagens serão desnecessariamente inchadas por testes de unidade.
  3. Os testes de unidade podem afetar processos de compilação, como compilação automática ou contínua.

Eu realmente não conheço nenhum profissional. Ter um projeto extra (ou 10) não é um engodo.

Editar: Mais informações sobre compilação e envio

Eu recomendaria ainda que qualquer processo de construção automatizado coloque testes de produção e unidade em locais diferentes. Idealmente, o processo de criação de teste de unidade é executado apenas se o código de produção é construído e copia os arquivos do produto no diretório de testes de unidade. Fazer isso dessa maneira resulta na separação dos bits reais para remessa, etc. Além disso, é bastante trivial executar testes de unidade automatizados neste momento em todos os testes em um diretório específico.

Para resumir, aqui está a idéia geral para uma compilação diária, teste e envio de bits e outros arquivos:

  1. A construção da produção é executada, colocando os arquivos de produção em um diretório "produção" específico.
    1. Crie apenas projetos de produção.
    2. Copie os bits compilados e outros arquivos em um diretório "produção".
    3. Copie bits e outros arquivos para um diretório candidato a lançamento, também conhecido como diretório de lançamento de Natal, seria "Release20081225".
  2. Se a construção da produção for bem-sucedida, a construção do teste de unidade será executada.
    1. Copie o código de produção para o diretório "tests".
    2. Crie testes de unidade no diretório "tests".
    3. Execute testes de unidade.
  3. Envie notificações de compilação e resultados de testes de unidade aos desenvolvedores.
  4. Quando um candidato a release (como Release20081225) for aceito, envie esses bits.
Jason Jackson
fonte
15
Na IMO, os contras listados nem sempre são aplicáveis. Um profissional para o mesmo projeto é o agrupamento mais fácil de testes de classe + testados - essas pequenas conveniências percorrem um longo caminho ao escrever testes. A preferência pessoal vence aqui e, às vezes, seus pontos são relevantes, mas não o tempo todo.
orip 07/12/08
7
Você deve primeiro perguntar se você precisa remover os testes ao enviar. Se sim, você precisa de um projeto separado. Caso contrário, use os outros prós e contras para decidir. Pessoas que assumem que não podem implantar os testes sempre chegarão à conclusão do "projeto separado" por padrão.
orip 17/03/10
7
Não vejo vantagens no envio de códigos que não sejam de produção, como testes de unidade, e há muitas desvantagens. Testes de unidade de remessa significa que você está lidando com mais bits que precisam ser distribuídos. Os testes de unidade também têm um conjunto separado de dependências. Agora você está enviando NUnit, Rhino ou Moq, etc. Isso é ainda mais incômodo. A colocação de testes de unidade em um projeto separado requer apenas uma pequena quantidade de esforço, e isso é um custo único. Estou muito confortável com a conclusão de que os testes de unidade não devem ser enviados.
Jason Jackson
4
Como alternativa à abordagem "projeto separado" para remover testes de unidade no código de produção, considere usar um símbolo personalizado e diretivas do compilador, como #if. Esse símbolo personalizado pode ser alternado usando os parâmetros da linha de comando do compilador nos scripts do software de IC. Consulte msdn.microsoft.com/en-us/library/4y6tbswk.aspx .
Rich C
23
O .NET está atrasado em ter testes em um projeto completamente separado, e eu apostaria que isso mudará em breve. Não há razão para que o processo de compilação não possa ser feito para ignorar o código de teste na compilação do release. Eu usei vários geradores de aplicativos da web que fazem isso. Eu acho que é absolutamente melhor incluir arquivos de código de teste de unidade ao lado dos arquivos de código que eles descrevem, espalhados pela mesma hierarquia de arquivos. O Google recomenda isso, chamado de organização de arquivos 'fractal'. Por que os testes são diferentes dos comentários embutidos e da documentação leia-me? O compilador ignora de bom grado o último.
Brandon Arnold
112

Projeto separado, mas na mesma solução. (Trabalhei em produtos com soluções separadas para código de teste e produção - é horrível. Você sempre alterna entre os dois.)

As razões para projetos separados são as indicadas por outros. Observe que, se você estiver usando testes orientados a dados, poderá acabar com uma quantidade significativa de inchaço se incluir os testes no conjunto de produção.

Se você precisar acessar os membros internos do código de produção, use InternalsVisibleTo .

Jon Skeet
fonte
+1: Acabei de encontrar testes de unidade no mesmo projeto que o código principal e é difícil encontrar os testes no código real - mesmo que uma convenção de nomenclatura tenha sido seguida.
Fenton
32
Para leitores menos experientes, isso significa que você adiciona [assembly:InternalsVisibleTo("UnitTestProjectName")]ao seu AssemblyInfo.csarquivo de projeto .
Margus
Adição muito útil Margus. Agradecemos a Jon por mencionar InternalsVisibleTo neste contexto.
Bjørn Otto Vasbotten
1
@jropella: Se você estiver assinando a montagem de prod, só poderá tornar os internos visíveis para as assembléias assinadas. E, claro, se você estiver executando qualquer código em confiança total, eles têm acesso via reflexão ...
Jon Skeet
2
@jropella: O Reflection não se importa com InternalsVisibleTo de qualquer maneira ... mas você não teria visto um assembly assinado confiando em um assembly não assinado usando InternalsVisibleTo, porque isso é impedido no momento da compilação.
Jon Skeet
70

Não entendo a objeção frequente à implantação de testes com código de produção. Liderei uma equipe em um pequeno microcap (cresceu de 14 para 130 pessoas). Tínhamos meia dúzia de aplicativos Java e achamos EXTREMAMENTE valioso implantar testes no campo para executá-los em um determinadomáquina que estava exibindo comportamento incomum. Problemas aleatórios ocorrem no campo e ser capaz de lançar alguns milhares de testes de unidade no mistério, com custo zero, foi inestimável e, muitas vezes, diagnosticou problemas em minutos ... incluindo problemas de instalação, problemas de RAM com falhas, problemas específicos de máquina, problemas de rede com falhas, etc, etc. Acho incrivelmente valioso colocar testes em campo. Além disso, problemas aleatórios aparecem em momentos aleatórios e é bom ter os testes de unidade já aguardando para serem executados em um momento. O espaço no disco rígido é barato. Assim como tentamos manter dados e funções juntos (design OO), acho que há algo fundamentalmente valioso em manter código e testes juntos (função + testes que validam as funções).

Gostaria de colocar meus testes no mesmo projeto em C # / .NET / Visual Studio 2008, mas ainda não investiguei isso o suficiente para alcançá-lo.

Um grande benefício de manter o Foo.cs no mesmo projeto que o FooTest.cs é que os desenvolvedores são constantemente lembrados quando uma classe está faltando em um teste de irmão! Isso incentiva as melhores práticas de codificação orientadas a testes ... os buracos são mais aparentes.


fonte
17
Eu posso ver como isso seria valioso com testes de integração, mas para testes de unidade? Se você os escrever corretamente, eles não deverão ter nenhuma dependência na máquina.
Joel McBeth
+1 eu concordo. Como estou fazendo principalmente o .NET, os testes de remessa com código de produção também têm o bom efeito colateral de reduzir a etapa de integração contínua de compilação e teste: 1) Apenas metade dos projetos a serem construídos, 2) todos os conjuntos de testes no lugar do aplicativo principal portanto, é mais fácil parametrizar seu executor de teste. Ao usar o Maven, esse argumento não se aplica, é claro. Finalmente, meus clientes são pessoas mais técnicas e realmente gostam de poder executar os testes, pois estão documentando o estado atual em relação às especificações.
precisa saber é o seguinte
Eu acho que pode fazer todo sentido fazer testes de integração no estilo TDD / BDD, ou seja, escrever código de teste que pode ser facilmente automatizado com corredores de teste padrão como NUnit, xUnit etc. Claro, você não deve misturar unidade com teste de integração. Apenas certifique-se de saber qual conjunto de testes executar com rapidez e frequência para verificação de compilação (BVT) e quais para testes de integração.
mkoertgen
1
A razão pela qual as pessoas se opõem a isso é porque não é a isso que estão acostumadas. Se sua primeira experiência com teste de unidade foi que eles estavam dentro do código de produção, de fato, com sintaxe específica na linguagem de programação que indicava o que um teste está associado a um método de produção, eles jurariam que esse era um aspecto inestimável do fluxo de trabalho de desenvolvimento e é absolutamente louco colocá-los em um projeto separado. A maioria dos desenvolvedores é assim. Seguidores.
Zumalifeguard
19

Coloque testes de unidade no mesmo projeto que o código para obter um melhor encapsulamento.

Você pode facilmente testar métodos internos, o que significa que você não tornará públicos métodos que deveriam ter sido internos.

Também é muito bom ter os testes de unidade próximos ao código que você está escrevendo. Ao escrever um método, você pode encontrar facilmente os testes de unidade correspondentes, pois estão no mesmo projeto. Quando você cria um assembly que inclui unitTests, qualquer erro no unitTest fornecerá um erro de compilação, portanto, você deve manter seu unittest atualizado, apenas para compilar. A unificação de um projeto separado pode fazer com que alguns desenvolvedores esqueçam a construção do projeto de unittest e falhem nos testes interrompidos por um tempo.

E você pode remover os testes de unidade do código de produção usando tags de compilação (IF #Debug).

Os testes de integração automática (feitos no iUnit) devem estar em um projeto separado, pois não pertencem a nenhum projeto.

jenspo
fonte
1
Mas isso significa que você não pode executar testes no Releasebuild e poucos "heisenbugs" podem ocorrer.
Hippy
3
Com montagens amigo ( msdn.microsoft.com/en-us/library/0tke9fxk.aspx ) usando InternalsVisibleTopermite testar métodos internos de um projeto de teste separado
Parox
3
@hlpPy - você pode configurar símbolos de compilação condicional arbitrários e pode ter mais de 2 configurações de compilação. Por exemplo; você pode ter Debug, Approvale Release; e compile a aprovação com todas as otimizações de versão. Além disso, normalmente os testes de unidade não são bons para detectar heisenbugs em primeiro lugar (na minha experiência). Você tende a precisar de testes de regressão de integração específicos para esses - e pode colocá-los lado a lado.
Eamon Nerbonne
Um projeto separado fornece o mesmo erro de tempo de compilação. Se você precisar se aprofundar no código, parece mais que você precisa de mais abstrações do que hackear testes de unidade no seu código. Confiar em condicionais no seu código para alternar ramificações também não é bom. Parece mais com testes de unidade e integrações.
Nick Turner
12

Depois de passar algum tempo em projetos TypeScript, onde os testes geralmente são colocados em um arquivo ao lado do código que eles estão testando, passei a preferir essa abordagem do que mantê-los separados:

  • É mais rápido navegar para o arquivo de teste.
  • É mais fácil lembrar de renomear os testes quando você renomeia a classe que está sendo testada.
  • É mais fácil lembrar de mover os testes quando você move a classe que está sendo testada.
  • É imediatamente óbvio se uma classe está faltando nos testes.
  • Você não precisa gerenciar duas estruturas de arquivos duplicadas, uma para testes e outra para código.

Então, quando iniciei um novo projeto .NET Core recentemente, queria ver se era possível imitar essa estrutura em um projeto C # sem enviar os testes ou assemblies de teste com a versão final.

A colocação das seguintes linhas no arquivo do projeto parece estar funcionando bem até agora:

  <ItemGroup Condition="'$(Configuration)' == 'Release'">
    <Compile Remove="**\*.Tests.cs" />
  </ItemGroup>
  <ItemGroup Condition="'$(Configuration)' != 'Release'">
    <PackageReference Include="nunit" Version="3.11.0" />
    <PackageReference Include="NUnit3TestAdapter" Version="3.12.0" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.9.0" />
  </ItemGroup>

O acima assegura que na Releaseconfiguração todos os arquivos nomeados *.Tests.cssejam excluídos da compilação e também que as referências necessárias ao pacote de teste de unidade sejam removidas.

Se você ainda deseja poder testar as classes na configuração de liberação, basta criar uma nova configuração derivada de Release chamada algo como ReleaseContainingTests.


Atualizar: Depois de usar essa técnica por um tempo, também achei útil personalizar seus ícones no VS Code para fazer com que os testes (e outras coisas) se destacem um pouco mais no painel do explorer:

Captura de tela do VS Code

Para fazer isso, use a extensão Material Icon Theme e adicione algo como o seguinte às suas preferências de código VS JSON:

"material-icon-theme.files.associations": {
  "*.Tests.cs": "test-jsx",
  "*.Mocks.cs": "merlin",
  "*.Interface.cs": "yaml",
}
James Thurley
fonte
Exatamente o que começamos a fazer
Matthew Steeples
Você também trabalhou para as bibliotecas de classes padrão .net?
Zenuka 24/07
10

Meus testes de unidade sempre vão em um projeto separado. De fato, para cada projeto que tenho na minha solução, há um projeto de teste separado que o acompanha. O código de teste não é um código de aplicativo e não deve ser misturado a ele. Uma vantagem de mantê-los em projetos separados - pelo menos usando o TestDriven.Net - é que posso clicar com o botão direito do mouse em um projeto de teste e executar todos os testes nesse projeto, testando uma biblioteca inteira de código de aplicativo com um clique.

tvanfosson
fonte
1
Então, como você faz testes unitários de classes internas? Ou você apenas testa as classes públicas?
User19371
7
<assembly: InternalsVisibleTo = "TestProject" /> se necessário, embora eu normalmente teste apenas interfaces públicas.
tvanfosson 07/12/08
@tvanfosson, o que você faz com as especificações (Specflow)? Você teria um projeto separado ou os colocaria no projeto de Teste de Unidade? +1.
w0051977 22/03
@ w0051977 Eu nunca usei Specflow então eu não sei
tvanfosson
@tvanfosson, e os testes de integração? Você os colocaria em um projeto separado para os testes de unidade?
w0051977
10

Se a estrutura do NUnit for usada, há um motivo adicional para colocar os testes no mesmo projeto. Considere o seguinte exemplo do código de produção combinado com testes de unidade:

public static class Ext
{
     [TestCase(1.1, Result = 1)]
     [TestCase(0.9, Result = 1)]
     public static int ToRoundedInt(this double d)
     {
         return (int) Math.Round(d);
     }
}

Os testes de unidade aqui servem como documentação e especificação para o código que está sendo testado. Não sei como obter esse efeito de auto-documentação, com os testes localizados em um projeto separado. O usuário da função precisaria procurar os testes para ver esses casos, o que é improvável.

Atualização : Eu sei que esse uso de TestCaseatributo não era o que os desenvolvedores do NUnit pretendiam, mas por que não?

Andrej Adamenko
fonte
2
Eu gosto desta ideia; tendo alguns exemplos de entradas e saídas esperadas com o código.
Preston McCormick
3

Eu flutuo entre o mesmo projeto e projetos diferentes.

Se você está lançando uma biblioteca, liberar o código de teste com o código de produção é um problema, caso contrário, acho que geralmente não é (embora exista uma forte barreira psicológica antes de você tentar).

Ao colocar testes no mesmo projeto, acho mais fácil alternar entre testes e o código que eles testam e mais fácil refatorar / movê-los.

orip
fonte
3

Coloquei-os em projetos separados. O nome da montagem reflete o dos espaços para nome, como regra geral para nós. Portanto, se houver um projeto chamado Company.Product.Feature.sln, ele terá uma saída (nome do assembly) de Company.Product.Feature.dll. O projeto de teste é Company.Product.Feature.Tests.sln, produzindo Company.Product.Feature.Tests.dll.

É melhor mantê-los em uma única solução e controlar a saída por meio do Configuration Manager. Temos uma configuração nomeada para cada um dos principais ramos (Desenvolvimento, Integração, Produção), em vez de usar o Debug and Release padrão. Depois de definir as configurações, você poderá incluí-las ou excluí-las clicando na caixa de seleção "Compilar" no Gerenciador de Configurações. (Para obter o Configuration Manager, clique com o botão direito do mouse na solução e vá para o Configuration Manager.) Observe que às vezes acho que o CM no Visual Studio está com erros. Algumas vezes, tive que entrar nos arquivos do projeto e / ou solução para limpar os destinos que ele criou.

Além disso, se você estiver usando o Team Build (e tenho certeza de que outras ferramentas de compilação do .NET são iguais), você poderá associar a compilação a uma configuração nomeada. Isso significa que, se você não criar seus testes de unidade para a construção "Produção", por exemplo, o projeto de construção também poderá estar ciente dessa configuração e não construí-los, pois foram marcados como tal.

Além disso, costumávamos fazer o XCopy cair da máquina de compilação. O script omitiria a cópia de qualquer coisa chamada * .Tests.Dll da implantação. Era simples, mas funcionou.

Joseph Ferris
fonte
1

Eu diria que os mantenha separados.

Além dos outros motivos mencionados, ter código e testes juntos inclina os números de cobertura dos testes. Quando você informa sobre a cobertura do teste de unidade - a cobertura relatada é mais alta porque os testes são cobertos quando você executa testes de unidade. Quando você relata a cobertura do teste de integração, a cobertura relatada é menor porque os testes de integração não executam testes de unidade.

Sebastian K
fonte
Isso não depende da tecnologia usada para cobrir os testes? Quero dizer, o OpenCover considera as linhas de código cobertas se forem executadas por um teste, incluindo as linhas do próprio teste, para que, se os testes tiverem exceções inesperadas, eles também sejam sinalizados como descobertos.
Isaac Llopis
0

Estou realmente inspirado pela estrutura de teste de unidade da biblioteca Flood NN de Robert Lopez. Ele usa um projeto diferente para cada classe de unidade testada e possui uma solução contendo todos esses projetos, além de um projeto principal que compila e executa todos os testes.

O interessante também é o layout do projeto. Os arquivos de origem estão em uma pasta, mas a pasta do projeto VS está abaixo. Isso permite criar subpastas diferentes para diferentes compiladores. Todos os projetos do VS são enviados com o código, portanto, é muito fácil para qualquer um executar um ou todos os testes de unidade.


fonte
0

Sei que essa é uma pergunta muito antiga, mas gostaria de acrescentar minha experiência. Alterei recentemente o hábito de teste de unidade de projetos separados para o mesmo.

Por quê?

Primeiro, sou muito propenso a manter a estrutura da pasta principal do mesmo projeto que o teste. Portanto, se eu tiver um arquivo Providers > DataProvider > SqlDataProvider.cs, estou criando a mesma estrutura em meus projetos de teste de unidade, comoProviders > DataProvider > SqlDataProvider.Tests.cs

Mas depois que o projeto está ficando cada vez maior, uma vez que você move arquivos de uma pasta para outra ou de um projeto para outro, fica muito trabalhoso sincronizar os projetos de teste de unidade.

Segundo, nem sempre é muito fácil navegar da classe a ser testada para a classe de teste de unidade. Isso é ainda mais difícil para JavaScript e Python.

Recentemente, comecei a praticar que, a cada arquivo que criei (por exemplo SqlDataProvider.cs), estou criando outro arquivo com o sufixo Test, comoSqlDataProvider.Tests.cs

No começo, parece que irá inchar arquivos e referências de bibliotecas, mas a longo prazo, você eliminará a síndrome dos arquivos em movimento à primeira vista e também garantirá que todos os arquivos candidatos a serem testados terão um arquivo emparelhado com .Testssufixo. Isso facilita o acesso ao arquivo de teste (porque fica lado a lado), em vez de procurar um projeto separado.

Você pode até escrever regras de negócios para varrer o projeto e identificar a classe que não possui o arquivo .Tests e relatá-las ao proprietário. Além disso, você pode dizer facilmente ao seu executor de teste para direcionar as .Testsclasses.

Especialmente para Js e Python, você não precisará importar suas referências de um caminho diferente; você pode simplesmente usar o mesmo caminho do arquivo de destino que está sendo testado.

Estou usando essa prática há algum tempo e acho que é uma troca muito razoável entre tamanho do projeto x capacidade de manutenção e curva de aprendizado para os novatos no projeto.

Teoman shipahi
fonte
Discordo. É muito mais organizado agrupar itens do que testar classes individuais. Se você estiver testando seus provedores; Você teria um IProvider, ISqlProvider e um MockSqlConnection. Em uma pasta Provedor, você teria o Teste de Unidade para cada provedor. Você pode se aprofundar nos testes de unidade, mas então você está apenas escrevendo testes e não código e pode até remover o profissional e começa a escrever o código para testar.
Nick Turner
0

Como outros responderam - coloque testes em projetos separados

Uma coisa que não foi mencionada é o fato de que você não pode realmente executar nunit3-console.exenada além de .dllarquivos.

Se você planeja executar seus testes pelo TeamCity, isso representará um problema.

Digamos que você tenha um projeto de aplicativo de console. Quando você compila, ele retorna um executável .exepara a binpasta.

Pelo que nunit3-console.exevocê não seria capaz de executar nenhum teste definido no aplicativo de console.

Em outras palavras, um aplicativo de console retorna um exearquivo e uma biblioteca de classes retorna dllarquivos.

Eu só fui mordido por isso hoje e é uma merda :(

Kolob Canyon
fonte
0

Fui implantando recentemente no docker. Não vejo o valor de ter um projeto separado quando o Dockerfile pode copiar facilmente o diretório / src e sair do diretório / test. Mas eu sou novo no dotnet e pode estar faltando alguma coisa.

MikeF
fonte
-2

Projetos separados, embora eu discuta comigo mesmo se eles devem compartilhar o mesmo svn. No momento, estou dando a eles repositórios svn separados, um chamado

"MyProject" - para o próprio projeto

e um chamado

"MyProjectTests" - para os testes associados ao MyProject.

Isso é razoavelmente limpo e tem a vantagem de se comprometer com o projeto e se comprometer com os testes, são bastante separados. Isso também significa que você pode entregar o svn do projeto, se necessário, sem precisar liberar seus testes. Isso também significa que você pode ter diretórios branch / trunk / tag para seus testes e para seu projeto.

Mas estou cada vez mais inclinado a ter algo como o seguinte, em um único repositório svn, para cada projeto.

Meu projeto
| \ Trunk
| | \ Code
| \ Testes
| \ Tags
| | \ 0,1
| | | \ Code
| | \ Testes
| \ 0.2
| | \ Code
| \ Testes
\ Ramos
  \ MyFork
   | \ Code
    \Teste

Eu ficaria interessado em saber o que as outras pessoas pensam dessa solução.

sampablokuper
fonte
5
Você não precisa de confirmações separadas para teste e código do aplicativo. Eles devem andar de mãos dadas e os commits envolverão ambos, especialmente se estiver usando o design do estilo * DD.
eddiegroves 03/06/2009