A estática é universalmente “ruim” para testes de unidade e, em caso afirmativo, por que a Resharper recomenda? [fechadas]

85

Eu descobri que existem apenas três maneiras de dependências de teste de unidade (mock / stub) que são estáticas no C # .NET:

Dado que dois deles não são gratuitos e um não atingiu a versão 1.0, zombar de coisas estáticas não é muito fácil.

Isso torna métodos estáticos e "mal" (no sentido de teste de unidade)? E se sim, por que o ressarpiador quer que eu faça algo que possa ser estático, estático? (Assumir que o re-compartilhador também não é "mau".)

Esclarecimento: Estou falando do cenário em que você deseja testar um método por unidade e esse método chama um método estático em uma unidade / classe diferente . Pela maioria das definições de teste de unidade, se você apenas permitir que o método em teste chame o método estático na outra unidade / classe, então você não é um teste de unidade, é um teste de integração . (Útil, mas não um teste de unidade.)

Vaccano
fonte
3
TheLQ: Você pode. Eu acredito que ele está falando sobre não poder testar métodos estáticos, porque na maioria das vezes toca variáveis ​​estáticas. Alterando assim o estado após e entre os testes.
26
Pessoalmente, acho que você está levando a definição de "unidade" longe demais. "Unidade" deve ser "a menor unidade que faz sentido testar isoladamente". Isso pode ser um método, pode ser mais do que isso. Se o método estático não tiver estado e for bem testado, ter uma segunda chamada de teste de unidade não é um problema (IMO).
Mlk #
10
"Pessoalmente, acho que você está levando a definição de" unidade "longe demais." Não, é só que ele está seguindo o uso padrão e você está criando sua própria definição.
7
"por que o novo afiador quer que eu faça algo que possa ser estático, estático?" O compartilhador não quer que você faça nada. É apenas informar que a modificação é possível e talvez desejável a partir de um POV de análise de código. O compartilhador não substitui seu próprio julgamento!
Adam Naylor
4
@ acidzombie24. Os métodos regulares também podem modificar o estado estático para que sejam tão "ruins" quanto os métodos estáticos. O fato de eles também poderem modificar o estado com um ciclo de vida mais curto os torna ainda mais perigosos. (Eu não sou a favor de métodos estáticos, mas o ponto sobre a modificação do estado é um golpe para métodos regulares também, mais ainda)
mike30

Respostas:

105

Olhando para as outras respostas aqui, acho que pode haver alguma confusão entre métodos estáticos que mantêm um estado estático ou causam efeitos colaterais (que me parecem uma péssima idéia) e métodos estáticos que apenas retornam um valor.

Os métodos estáticos que não mantêm nenhum estado e não causam efeitos colaterais devem ser facilmente testados por unidade. De fato, considero esses métodos uma forma de programação funcional "pobre"; você entrega ao método um objeto ou valor e ele retorna um objeto ou valor. Nada mais. Não vejo como esses métodos afetariam negativamente o teste de unidade.

Robert Harvey
fonte
44
O método estático é testável por unidade, mas e os métodos que chamam o método estático? Se os chamadores estiverem em outra classe, eles terão uma dependência que precisa ser dissociada para fazer um teste de unidade.
Vaccano
22
@ Vulcano: Mas se você escrever métodos estáticos que não mantêm o estado e não têm efeitos colaterais, eles são mais ou menos funcionalmente equivalentes a um esboço de qualquer maneira.
Robert Harvey
20
Talvez simples. Porém, as mais complexas podem começar a gerar exceções e ter saídas que não são esperadas (e devem ser capturadas no teste de unidade do método estático, não no teste de unidade do chamador do método estático), ou pelo menos é o que eu foram levados a acreditar na literatura que li.
Vaccano
21
@Vaccano: Um método estático que possui saídas ou exceções aleatórias tem efeitos colaterais.
Tikhon Jelvis
10
@TikhonJelvis Robert estava falando sobre saídas; e as exceções "aleatórias" não devem ser um efeito colateral, elas são essencialmente uma forma de saída. O ponto é que, sempre que você testar um método que chama o método estático, está agrupando esse método e todas as permutações de sua saída potencial e não pode testar seu método isoladamente.
22612 Nicole
26

Você parece estar confundindo dados estáticos e métodos estáticos . O novo compartilhador, se bem me lembro, recomenda tornar privateestáticos os métodos dentro de uma classe, se puderem ser feitos - acredito que isso traga um pequeno benefício de desempenho. Não é recomendável fazer "qualquer coisa que possa ser" estática!

Não há nada errado com os métodos estáticos e eles são fáceis de testar (desde que não alterem nenhum dado estático). Por exemplo, pense em uma biblioteca de matemática, que é uma boa candidata a uma classe estática com métodos estáticos. Se você possui um método (artificial) como este:

public static long Square(int x)
{
    return x * x;
}

então isso é eminentemente testável e não tem efeitos colaterais. Você apenas verifica que, quando passa, digamos, 20, recebe 400. Não há problema.

Dan Diplo
fonte
4
O que acontece quando você tem outra classe que chama esse método estático? Parece simples neste caso, mas é uma dependência que não pode ser isolada, exceto com uma das três ferramentas listadas acima. Se você não o isola, então você não está "testando a unidade", está "testando a integração" (porque está testando o quão bem as suas diferentes unidades "se integram").
Vaccano
3
Nada acontece. Por quê? A estrutura do .NET está cheia de métodos estáticos. Você está dizendo que também não pode usá-los em nenhum método que você queira testar na unidade?
Dan Diplo
3
Bem, se seu código estiver no nível / qualidade de produção do .NET Framework, com certeza, vá em frente. Mas o ponto principal do teste de unidade é o teste da unidade, isoladamente. Se você também está chamando métodos em outras unidades (sejam estáticas ou não), agora está testando sua unidade mais suas dependências. Não estou dizendo que não é um teste útil, mas não um "teste de unidade" pela maioria das definições. (Como agora você está testando sua unidade em teste e a unidade que possui o método estático).
Vaccano
10
Presumivelmente, você (ou outros) já testou o método estático e mostrou que ele funciona (pelo menos conforme o esperado) antes de escrever muito mais código nele. Se algo quebrar na parte que você testar a seguir, é onde você deve procurar primeiro, não nas coisas que você já testou.
cHao 21/09/10
6
@ Vulcano Então, como a Microsoft testa o .NET Framework? Muitas das classes no Framework referenciam métodos estáticos em outras classes (como System.Math) para não mencionar a abundância de métodos estáticos de fábrica etc. Além disso, você nunca seria capaz de usar nenhum método de extensão etc. O fato é que funções simples como esta são fundamental para as línguas modernas. Você pode testá-los isoladamente (já que geralmente serão determinísticos) e depois usá-los em suas aulas sem se preocupar. Não é um problema!
Dan Diplo
18

Se a pergunta real aqui for "Como faço para testar este código?":

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

Em seguida, basta refatorar o código e injetar como de costume a chamada para a classe estática assim:

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}
Ensolarado
fonte
9
Felizmente também temos dúvida sobre a legibilidade do código e beijo na SO também :)
gbjbaanb
um delegado não seria mais fácil e faria a mesma coisa?
jk.
@jk poderia ser, mas é difícil usar o contêiner IoC.
29512 Sunny
15

A estática não é necessariamente ruim, mas pode limitar suas opções quando se trata de teste de unidade com falsificações / zombarias / tocos.

Existem duas abordagens gerais para zombar.

O primeiro (tradicional - implementado pela RhinoMocks, Moq, NMock2; zombarias e tocos manuais também estão neste campo) se baseia em costuras de teste e injeção de dependência. Suponha que você esteja testando um código estático e que tenha dependências. O que acontece frequentemente no código projetado dessa maneira é que a estática cria suas próprias dependências, invertendo a inversão de dependência . Você logo descobre que não pode injetar interfaces simuladas no código em teste projetado dessa maneira.

O segundo (simulado qualquer coisa - implementado por TypeMock, JustMock e Moles) depende da API de criação de perfil do .NET . Ele pode interceptar qualquer uma das instruções do CIL e substituir um pedaço do seu código por um falso. Isso permite que o TypeMock e outros produtos deste campo zombem de qualquer coisa: estática, classes seladas, métodos particulares - coisas que não foram projetadas para serem testáveis.

Há um debate em andamento entre duas escolas de pensamento. Diz-se, siga os princípios e o design do SOLID para testabilidade (que geralmente inclui a facilidade na estática). O outro diz: compre TypeMock e não se preocupe.

azheglov
fonte
14

Verifique isto: "Métodos estáticos são morte para testabilidade" . Breve resumo do argumento:

Para testar a unidade, você precisa pegar um pequeno pedaço do seu código, reconectar suas dependências e testá-lo isoladamente. Isso é difícil com métodos estáticos, não apenas no caso de eles acessarem o estado global, mas mesmo se apenas chamarem outros métodos estáticos.

Rafał Dowgird
fonte
32
Eu não mantenho o estado global ou cause efeitos colaterais nos meus métodos estáticos, portanto esse argumento me parece irrelevante. O artigo que você vinculou cria um argumento de declive escorregadio que não tem base se os métodos estáticos estiverem confinados ao código processual simples, agindo de maneira "genérica" ​​(como funções matemáticas).
Robert Harvey
4
@ Robert Harvey - como você faz um teste unitário de um método que usa um método estático de outra classe (isto é, depende de um método estático). Se você simplesmente deixar chamar, então você não está "testando a unidade", está "testando a integração"
Vaccano
10
Não basta ler o artigo do blog, ler os muitos comentários que discordam dele. Um blog é apenas uma opinião, não um fato.
Dan Diplo
8
@Vaccano: Um método estático sem efeitos colaterais ou estado externo é funcionalmente equivalente a um valor. Sabendo disso, não é diferente de testar a unidade um método que cria um número inteiro. Esse é um dos principais insights que a programação funcional nos fornece.
Steven Evers
3
A menos que os sistemas embarcados funcionem ou um aplicativo cujos bugs tenham potencial para criar incidentes internacionais, o IMO, quando a "testabilidade" impulsiona a arquitetura, você o alterou antes de adotar as metodologias de teste em todos os cantos. Também estou cansado da versão do XP de tudo que domina todas as conversas. XP não é uma autoridade, é uma indústria. Aqui está a definição original, sensata de teste de unidade: python.net/crew/tbryan/UnitTestTalk/slide2.html
Erik Reppen
5

A verdade simples raramente reconhecida é que, se uma classe contiver uma dependência visível do compilador em outra classe, ela não poderá ser testada isoladamente dessa classe. Você pode falsificar algo que parece um teste e aparecerá em um relatório como se fosse um teste.

Mas não terá as principais propriedades definidoras de um teste; falhando quando as coisas estão erradas, passando quando estão certas.

Isso se aplica a quaisquer chamadas estáticas, chamadas de construtor e qualquer referência a métodos ou campos não herdados de uma classe ou interface base . Se o nome da classe aparecer no código, é uma dependência visível do compilador e você não pode testar validamente sem ela. Qualquer pedaço menor simplesmente não é uma unidade testável válida . Qualquer tentativa de tratá-lo como se fosse, terá resultados não mais significativos do que escrever um pequeno utilitário para emitir o XML usado por sua estrutura de teste para dizer 'teste aprovado'.

Dado isso, existem três opções:

  1. defina o teste de unidade como sendo o teste da unidade composta por uma classe e suas dependências codificadas. Isso funciona, desde que você evite dependências circulares.

  2. nunca crie dependências em tempo de compilação entre as classes que você é responsável por testar. Isso funciona, desde que você não se importe com o estilo de código resultante.

  3. não teste de unidade, teste de integração. O que funciona, desde que não entre em conflito com outra coisa para a qual você precisa usar o termo teste de integração.

soru
fonte
3
Não há nada que diga que um teste de unidade é um teste de uma única classe. É um teste de uma única unidade. As propriedades definidoras dos testes de unidade são que eles são rápidos, repetíveis e independentes. A referência Math.Piem um método não o torna um teste de integração por nenhuma definição razoável.
Sara
ou seja, opção 1. Concordo que essa é a melhor abordagem, mas pode ser útil saber que outras pessoas usam os termos de maneira diferente (razoavelmente ou não).
soru
4

Não há duas maneiras sobre isso. As sugestões do ReSharper e vários recursos úteis do C # não seriam usados ​​com tanta frequência se você estivesse escrevendo testes de unidade atômica isolados para todo o seu código.

Por exemplo, se você tem um método estático e precisa removê-lo, não pode, a menos que use uma estrutura de isolamento baseada em perfil. Uma solução compatível com chamada é alterar a parte superior do método para usar a notação lambda. Por exemplo:

ANTES:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

DEPOIS DE:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

Os dois são compatíveis com chamadas. Os chamadores não precisam mudar. O corpo da função permanece o mesmo.

Em seguida, em seu código de teste de unidade, você pode stubar esta chamada da seguinte maneira (supondo que ela esteja em uma classe chamada Database):

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

Tome cuidado para substituí-lo pelo valor original após concluir. Você pode fazer isso através de uma tentativa / finalmente ou, na limpeza do teste de unidade, a chamada após cada teste, escreva um código como este:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

que reinvocará o inicializador estático da sua classe.

Os Funda Lambda não são tão ricos em suporte quanto os métodos estáticos regulares, portanto, essa abordagem tem os seguintes efeitos colaterais indesejáveis:

  1. Se o método estático for um método de extensão, primeiro você deverá alterá-lo para um método sem extensão. O Recarregador pode fazer isso por você automaticamente.
  2. Se qualquer um dos tipos de dados dos métodos estáticos for um assembly de interoperabilidade incorporada, como para o Office, você precisará agrupar o método, agrupar o tipo ou alterá-lo para o tipo 'objeto'.
  3. Você não pode mais usar a ferramenta de refatoração de assinatura de alteração do Resharper.

Mas digamos que você evite completamente a estática e a converta em um método de instância. Ainda não é ridículo, a menos que o método seja virtual ou implementado como parte de uma interface.

Portanto, na realidade, qualquer pessoa que sugerir o remédio para stubbing métodos estáticos deve torná-los métodos de instância, eles também estariam contra métodos de instância que não são virtuais ou fazem parte de uma interface.

Então, por que o C # tem métodos estáticos? Por que ele permite métodos de instância não virtuais?

Se você usar um desses "Recursos", simplesmente não poderá criar métodos isolados.

Então, quando você os usa?

Use-os para qualquer código que você não espere que alguém queira apagar. Alguns exemplos: o método Format () da classe String, o método WriteLine () da classe Console, o método Cosh () da classe Math

E mais uma coisa. A maioria das pessoas não liga para isso, mas se você puder sobre o desempenho de uma chamada indireta, esse é outro motivo para evitar métodos de instância. Há casos em que é um sucesso de desempenho. É por isso que existem métodos não virtuais em primeiro lugar.

zumalifeguard
fonte
3
  1. Acredito que seja em parte porque os métodos estáticos são "mais rápidos" de chamar do que os métodos de instância. (Entre aspas, porque isso cheira a micro otimização), consulte http://dotnetperls.com/static-method
  2. Ele está dizendo que ele não precisa de estado, portanto pode ser chamado de qualquer lugar, removendo a sobrecarga da instatiação, se essa é a única coisa que alguém precisa.
  3. Se eu quiser zombar, geralmente acho que é a prática declarada em uma interface.
  4. Se for declarado em uma interface, o R # não sugerirá que você a torne estática.
  5. Se declarado virtual, o R # também não sugere que você o torne estático.
  6. Manter estado (campos) estaticamente é algo que sempre deve ser considerado com cuidado . O estado estático e os fios se misturam como lítio e água.

O R # não é a única ferramenta que fará essa sugestão. A análise de código FxCop / MS também fará o mesmo.

Eu diria que, se o método é estático, geralmente deve ser testável como também. Isso traz alguma consideração de design e provavelmente mais discussão do que eu tenho agora, aguardando pacientemente os votos negativos e comentários ...;)

MIA
fonte
Você pode declarar um método / objeto estático em uma interface? (Eu penso que não). Éter, estou me referindo a quando seu método estático é chamado pelo método em teste. Se o chamador estiver em uma unidade diferente, o método estático precisará ser isolado. Isso é muito difícil de executar sem uma das três ferramentas listadas acima.
Vaccano
1
Não, você não pode declarar estático em uma interface. Seria sem sentido.
MIA
3

Vejo que, depois de muito tempo, ninguém declarou um fato realmente simples. Se o re-afiador me disser que eu posso tornar um método estático, isso significa uma coisa enorme para mim, ouço a voz dele me dizer: "ei, você, essas peças de lógica não são a RESPONSABILIDADE da classe atual de lidar, por isso deve ficar de fora em alguma classe auxiliar ou algo assim ".

g1ga
fonte
2
Discordo. Na maioria das vezes, quando o Resharper diz que posso tornar algo estático, é um pouco de código comum a dois ou mais métodos da classe e, portanto, extraí-o para seu próprio procedimento. Mover isso para um ajudante seria uma complexidade sem sentido.
Loren Pechtel
2
Só entendo seu argumento se o domínio for muito simples e não for adequado para futuras modificações. Diferentemente, o que você chama de "complexidade sem sentido" é um design bom e legível para humanos. Ter uma classe auxiliar com uma razão simples e clara para existir é de alguma maneira o "mantra" dos princípios do SoC e da responsabilidade única. Além disso, considerando que essa nova classe se torna uma dependência para a principal, ela tem que expor alguns membros públicos e, como conseqüência natural, torna-se testável isoladamente e fácil de zombar quando atua como uma dependência.
g1ga
1
Não é relevante para a pergunta.
Igby Largeman
2

Se o método estático for chamado de dentro de outro método , não será possível impedir ou substituir essa chamada. Isso significa que esses dois métodos formam uma única unidade; Teste de unidade de qualquer tipo testa os dois.

E se esse método estático fala com a Internet, conecta bancos de dados, mostra pop-ups da GUI ou converte o teste de unidade em uma bagunça completa, ele simplesmente funciona sem nenhum trabalho fácil. Um método que chama esse método estático não pode ser testado sem a refatoração, mesmo que tenha muito código puramente computacional que se beneficiaria muito com o teste de unidade.

h22
fonte
0

Acredito que o Resharper esteja fornecendo orientações e aplique as diretrizes de codificação com as quais foi configurado. Quando eu uso o Resharper e ele me diz que um método deve ser estático, ele deve estar em um método privado que não atua em nenhuma variável de instância.

Agora, quanto à testabilidade, esse cenário não deve ser um problema, pois você também não deve testar métodos privados.

Quanto à testabilidade de métodos estáticos que são públicos, o teste de unidade fica difícil quando métodos estáticos tocam no estado estático. Pessoalmente, eu manteria isso no mínimo e usaria métodos estáticos como funções puras, tanto quanto possível, onde quaisquer dependências são passadas para o método que podem ser controladas através de um dispositivo de teste. No entanto, esta é uma decisão de design.

aqwert
fonte
Estou me referindo a quando você tem um método em teste (não estático) que chama um método estático em outra classe. Essa dependência só pode ser isolada com uma das três ferramentas listadas acima. Se você não o isolar, estará testando a integração, não a unidade.
Vaccano
Acessar variáveis ​​de instância inerentemente significa que não pode ser estático. O único estado que um método estático pode ter são as variáveis ​​de classe e o único motivo que deve acontecer é se você está lidando com um singleton e, portanto, não tem escolha.
Loren Pechtel