Unidade de teste de métodos privados em C #

291

O Visual Studio permite o teste de unidade de métodos particulares por meio de uma classe acessadora gerada automaticamente. Eu escrevi um teste de um método privado que é compilado com êxito, mas falha no tempo de execução. Uma versão bastante mínima do código e do teste é:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

O erro de tempo de execução é:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

De acordo com o intellisense - e, portanto, acho que o destino do compilador - é do tipo TypeA_Accessor. Mas, em tempo de execução, é do tipo TypeA e, portanto, a adição da lista falha.

Existe alguma maneira de parar esse erro? Ou, talvez mais provável, que outros conselhos as outras pessoas têm (eu prevejo que talvez "não teste métodos privados" e "não teste de unidade manipule o estado dos objetos").

junichiro
fonte
Você precisa de um acessador para a classe privada TypeB. Acessador O TypeA_Accessor fornece acesso a métodos privados e protegidos do TypeA. No entanto, o TypeB não é um método. É uma aula.
Dima
O Accessor fornece acesso a métodos, membros, propriedades e eventos privados / protegidos. Ele não fornece acesso a classes privadas / protegidas dentro da sua classe. E classes privadas / protegidas (Tipo B) devem ser usadas apenas por métodos de propriedade de classe (Tipo A). Então, basicamente, você está tentando adicionar uma classe privada (TypeB) de fora do TypeA ao "myList", que é privado. Como você está usando o acessador, não há nenhum problema para acessar o myList. No entanto, você não pode usar o TypeB através do acessador. A solução possível seria mover o Tipo B para fora do Tipo A. Mas isso pode prejudicar o seu design.
Dima
Sinta que o teste de métodos particulares deve ser feito pelo seguinte stackoverflow.com/questions/250692/…
nate_weldon

Respostas:

274

Sim, não teste métodos particulares .... A idéia de um teste de unidade é testar a unidade por sua 'API' pública.

Se você acha que precisa testar muito comportamento privado, provavelmente há uma nova 'classe' oculta dentro da classe que está tentando testar, extraia-a e teste-a por sua interface pública.

Um conselho / ferramenta de pensamento ... Há uma ideia de que nenhum método deve ser privado. Significando que todos os métodos devem viver em uma interface pública de um objeto ... se você sentir que precisa torná-lo privado, provavelmente viverá em outro objeto.

Esse conselho não funciona muito bem na prática, mas geralmente é um bom conselho, e muitas vezes leva as pessoas a decompor seus objetos em objetos menores.

Keith Nicholas
fonte
385
Discordo. No OOD, métodos e propriedades particulares são uma maneira intrínseca de não se repetir ( en.wikipedia.org/wiki/Don%27t_repeat_yourself ). A idéia por trás da programação e do encapsulamento da caixa preta é ocultar detalhes técnicos do assinante. Portanto, é realmente necessário ter métodos e propriedades particulares não triviais em seu código. E se não for trivial, ele precisa ser testado.
AxD
8
não é intrínseco, algumas linguagens OO não têm métodos privados, propriedades privadas podem conter objetos com interfaces públicas que podem ser testadas.
Keith Nicholas
7
O objetivo deste conselho é que se seu objeto faz uma coisa e está SECO, geralmente há poucas razões para ter métodos particulares. Geralmente, os métodos privados fazem algo pelo qual o objeto não é realmente responsável, mas é bastante útil, se não trivial, geralmente é outro objeto, pois provavelmente está violando o SRP
Keith Nicholas
28
Errado. Você pode usar métodos particulares para evitar a duplicação de código. Ou para validação. Ou para muitos outros propósitos que o mundo público não deveria conhecer.
Jorj
37
Quando você foi despejado em uma base de código OO tão horrivelmente projetada e pediu para "aprimorá-la gradualmente", foi uma decepção descobrir que não pude fazer alguns primeiros testes para métodos privados. Sim, talvez nos livros didáticos esses métodos não estariam aqui, mas no mundo real temos usuários com requisitos de produto. Não posso simplesmente fazer uma refatoração massiva de "Olhe para um código limpo" sem precisar fazer alguns testes no projeto. Parece um outro exemplo de forçar programadores praticantes a entrar em caminhos que parecem ingenuamente bons, mas falham em levar em consideração coisas realmente confusas.
dune.rocks
666

Você pode usar a classe PrivateObject

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);
Scuttle
fonte
25
Esta é a resposta correta, agora que a Microsoft adicionou o PrivateObject.
Zoey
4
Boa resposta, mas observe que o PrivateMethod precisa ser "protegido" em vez de "privado".
HerbalMart
23
@ HerbalMart: Talvez eu o entenda mal, mas se você está sugerindo que o PrivateObject pode acessar apenas membros protegidos e não privados, você está enganado.
kmote
17
@JeffPearce Para métodos estáticos, você pode usar "PrivateType pt = new PrivateType (typeof (MyClass));" e, em seguida, chame InvokeStatic no objeto pt, como chamaria Invoke em um objeto privado.
Steve Hibbert
12
Caso alguém esteja se perguntando a partir do MSTest.TestFramework v1.2.1 - as classes PrivateObject e PrivateType não estão disponíveis para projetos direcionados ao .NET Core 2.0 - Há um problema no github para isso: github.com/Microsoft/testfx/issues/366
shiitake
98

“Não há nada chamado como padrão ou melhor prática, provavelmente são apenas opiniões populares”.

O mesmo vale para esta discussão também.

insira a descrição da imagem aqui

Tudo depende do que você acha que é uma unidade, se você acha que UNIT é uma classe, então você só acessará o método público. Se você acha que o UNIT é uma linha de código, o método privado não fará com que você se sinta culpado.

Se você deseja chamar métodos privados, pode usar a classe "PrivateObject" e chamar o método invoke. Você pode assistir a este vídeo detalhado do youtube ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ), que mostra como usar "PrivateObject" e também discute se o teste de métodos privados é lógico ou não.

Shivprasad Koirala
fonte
55

Outro pensamento aqui é estender o teste para classes / métodos "internos", dando mais uma sensação de caixa branca desse teste. Você pode usar InternalsVisibleToAttribute na montagem para expô-los a módulos de teste de unidade separados.

Em combinação com a classe selada, você pode abordar esse encapsulamento de forma que o método de teste seja visível apenas a partir da montagem mais unida de seus métodos. Considere que o método protegido na classe selada é de fato privado.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

E teste de unidade:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}
Jeff
fonte
Eu não tenho certeza se entendi. InternalsVisibleToAttribute torna acessíveis métodos e atributos marcados como "internos", mas meus campos e métodos são "privados". Você está sugerindo que eu mude as coisas de privadas para internas? Eu acho que não entendi.
Junichiro
2
Sim, é o que estou sugerindo. É um pouco "hacky", mas pelo menos eles não são "públicos".
Jeff
27
Esta é uma resposta maravilhosa apenas porque não diz "não teste métodos privados", mas sim, é bastante "hacky". Eu gostaria que houvesse uma solução. Na IMO, é ruim dizer que "métodos privados não devem ser testados", porque da maneira que eu vejo: é equivalente a "métodos privados não devem estar corretos".
MasterMastic 06/06
5
Sim, eu também confuso com aqueles que afirmam que métodos privados não devem ser testados em teste de unidade. API pública é a saída, mas às vezes a implementação incorreta também fornece a saída correta. Ou a implementação causou alguns efeitos colaterais ruins, como manter recursos desnecessários, referenciar objetos que impedem que ele seja coletado pelo gc ... etc. A menos que eles forneçam outro teste que possa abranger os métodos particulares, e não o teste de unidade, caso contrário, eu consideraria que eles não podem manter um código 100% testado.
mr.Pony
Eu concordo com o MasterMastic. Essa deve ser a resposta aceita.
XDS
27

Uma maneira de testar métodos privados é através da reflexão. Isso também se aplica ao NUnit e ao XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);
Jack Davidson
fonte
call methods estático e não estático ?
Kiquenet
2
A desvantagem dos métodos dependentes de reflexão é que eles tendem a quebrar quando você renomeia métodos usando R #. Pode não ser um grande problema em pequenos projetos, mas em grandes bases de código, torna-se meio irritante ter testes de unidade sendo quebrados dessa maneira e depois ter que procurar e corrigi-los rapidamente. Nesse sentido, meu dinheiro vai para a resposta de Jeff.
XDS
2
@XDS Pena que nameof () não funciona para obter o nome de um método privado de fora de sua classe.
Gabriel Morin
12

Ermh ... Veio aqui exatamente com o mesmo problema: Teste um método privado simples , mas essencial . Depois de ler este tópico, parece ser "Eu quero fazer esse furo simples neste pedaço de metal simples e quero garantir que a qualidade atenda às especificações" e depois venha "Ok, isso não é fácil. Antes de tudo, não existe uma ferramenta adequada para fazer isso, mas você pode construir um observatório de ondas gravitacionais em seu jardim.Leia meu artigo em http://foobar.brigther-than-einstein.org/ Primeiro, é claro, você tem que participar de alguns cursos avançados de física quântica, então você precisa de toneladas de nitrogênio ultra-legal e, claro, meu livro disponível na Amazon "...

Em outras palavras...

Não, primeiras coisas primeiro.

Todo e qualquer método, seja ele privado, interno, protegido e público , deve ser testável. Tem que haver uma maneira de implementar esses testes sem demora, como foi apresentado aqui.

Por quê? Exatamente por causa das menções arquitetônicas feitas até agora por alguns colaboradores. Talvez uma simples reiteração dos princípios de software possa esclarecer alguns mal-entendidos.

Nesse caso, os suspeitos comuns são: OCP, SRP e, como sempre, KIS.

Mas espere um pouco. A idéia de disponibilizar tudo ao público é mais menos política e um tipo de atitude. Mas. Quando se trata de código, mesmo na Comunidade de Código Aberto, isso não é dogma. Em vez disso, "ocultar" algo é uma boa prática para facilitar a familiarização com uma determinada API. Você ocultaria, por exemplo, os cálculos básicos do seu bloco de construção de termômetro digital novo no mercado - não para ocultar a matemática por trás da curva real medida para curiosos leitores de código, mas para impedir que seu código se torne dependente de alguns, talvez de repente usuários importantes que não resistiram a usar seu código anteriormente privado, interno e protegido para implementar suas próprias idéias.

Do que estou falando?

private double TranslateMeasurementIntoLinear (double realMeasurement);

É fácil proclamar a Era de Aquário ou o que é chamado hoje em dia, mas se meu sensor passar de 1,0 a 2,0, a implementação do Translate ... poderá mudar de uma equação linear simples que seja facilmente compreensível e "re utilizável "para todos, para um cálculo bastante sofisticado que usa análise ou o que quer que seja, e assim eu decifrava o código de outros. Por quê? Porque eles não entenderam os princípios básicos da codificação de software, nem mesmo o KIS.

Para resumir este conto de fadas: Precisamos de uma maneira simples de testar métodos privados - sem demora.

Primeiro: Feliz ano novo, pessoal!

Segundo: ensaie as lições do arquiteto.

Terceiro: o modificador "público" é religião, não uma solução.

CP70
fonte
5

Outra opção que não foi mencionada é apenas criar a classe de teste de unidade como filho do objeto que você está testando. Exemplo da NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Isso permitiria o teste fácil de métodos privados e protegidos (mas não herdados) e permitiria manter todos os seus testes separados do código real, para que você não estivesse implantando conjuntos de testes na produção. Mudar seus métodos privados para métodos protegidos seria aceitável em muitos objetos herdados, e é uma alteração bastante simples de fazer.

CONTUDO...

Embora essa seja uma abordagem interessante para resolver o problema de como testar métodos ocultos, não tenho certeza se defenderia que esta é a solução correta para o problema em todos os casos. Parece um pouco estranho testar um objeto internamente, e eu suspeito que possa haver alguns cenários em que essa abordagem exploda em você. (Objetos imutáveis, por exemplo, podem dificultar alguns testes).

Enquanto mencionei essa abordagem, sugeriria que essa é mais uma sugestão de brainstorm do que uma solução legítima. Tome com um grão de sal.

EDIT: Acho realmente hilário que as pessoas votem nesta resposta, pois descrevo isso explicitamente como uma má ideia. Isso significa que as pessoas estão concordando comigo? Estou tão confuso.....

Roger Hill
fonte
É uma solução criativa, mas meio hacky.
Shinzou 19/11/19
3

Do livro Trabalhando Efetivamente com o Código Legado :

"Se precisamos testar um método privado, devemos torná-lo público. Se tornar público nos incomoda, na maioria dos casos, significa que nossa classe está fazendo muito e devemos corrigi-lo."

A maneira de corrigi-lo, de acordo com o autor, é criando uma nova classe e adicionando o método como public.

O autor explica mais a fundo:

"Um bom design é testável, e um design que não é testável é ruim."

Portanto, dentro desses limites, sua única opção real é criar o método public, seja na atual ou em uma nova classe.

Kai Hartmann
fonte
2

TL; DR: Extrai o método privado para outra classe, teste nessa classe; leia mais sobre o princípio SRP (princípio de responsabilidade única)

Parece que você precisa extrair o privatemétodo para outra classe; nisso deveria ser public. Em vez de tentar testar o privatemétodo, você deve testar o publicmétodo dessa outra classe.

Temos o seguinte cenário:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Precisamos testar a lógica de _someLogic; mas parece que Class Aassume mais papel do que precisa (viola o princípio do SRP); basta refatorar em duas classes

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

Desta forma, someLogicpoderia ser teste em A2; em A1, basta criar um A2 falso e injetar no construtor para testar se A2 é chamado para a função nomeada someLogic.

o0omycomputero0o
fonte
0

No VS 2005/2008, você pode usar o acessador privado para testar o membro privado, mas esse caminho desapareceu na versão posterior do VS

Allen
fonte
1
Boa resposta em 2008 até talvez no início de 2010. Agora, consulte as alternativas PrivateObject e Reflection (veja várias respostas acima). O VS2010 tinha bug (s) de acesso, o MS o reprovou no VS2012. A menos que você seja forçado a permanecer no VS2010 ou mais antigo (ferramentas de compilação maiores de 18 anos), economize seu tempo evitando acessadores particulares. :-).
Zephan Schroeder 01/03/19