18 12 2013
Substituindo Private Accessors por PrivateObjects no Visual Studio 2013
Olá caro(a) leitor(a)! Seja bem vindo(a) a mais um post neste humilde blog!
No artigo de hoje quero abordar a criação de unit tests que avaliam o funcionamento de atributos, métodos ou propriedades privados no Visual Studio 2013.
Muitas pessoas são categoricamente contra testarmos itens privados no nosso projeto. Eu pessoalmente acredito que isso seja válido em algumas situações. Porém, o foco deste artigo não é abordar se isso é certo ou errado, mas sim, como podemos utilizar PrivateObjects no Visual Studio 2012 e 2013 para conseguirmos sanar essa necessidade.
Caso você ainda não tenha notado, os Private Accessors foram deprecados a partir do Visual Studio 2012. Veja só o trecho que aborda esse assunto na documentação do MSDN:
Private accessors are deprecated in Visual Studio 2012. You will no longer be able to create private accessors.
Mas, muitos ainda nem notaram que os Private Accessors foram, de fato, deprecados. Isso se deve ao fato que as novas versões do Visual Studio continuam sendo compatíveis com os Private Accessors, desde que eles tenham sido criados em uma versão anterior do Visual Studio, como podemos observar caso continuemos lendo a documentação:
…Visual Studio 2010 test projects will compile and work in Visual Studio 2012. The build will include output warnings.
Uma solução, caso você queira continuar utilizando Private Accessors (o que eu não recomendo!), é cria-los no Visual Studio 2010 e utilizá-los nas versões mais recentes do Visual Studio. Obviamente essa opção é arriscada, uma vez que nós não sabemos quando a Microsoft vai simplesmente decidir não suportá-los nas próximas versões do Visual Studio. Então, este artigo é para os que, como eu, não querem se arriscar com algo não mais suportado e preferem utilizar a solução recomendada pela Microsoft.
Para quem não sabe, os Private Accessors serviam para conseguirmos acessar métodos, propriedades e atributos privados de uma classe em um projeto de unit tests de forma extremamente simplificada. Bastava utilizar aquele item do menu de contexto “Create Private Accessor” e pronto, o Visual Studio cuidava de tudo pra gente. Agora, as coisas complicaram um pouco, já que temos que utilizar as classes PrivateObject e PrivateType.
Nada melhor que entendermos o funcionamento dessas classes utilizando um exemplo prático, certo? Então, vamos criar um projeto novo do tipo Class Library, chamado ClassLibraryASerTestada. Nesse projeto, renomeie “Class1” para “ClasseASerTestada“. Nessa classe, vamos criar alguns métodos, atributos e propriedades privados, inclusive alguns estáticos:
public class ClasseASerTestada { private bool MetodoPrivado() { return true; } private bool MetodoPrivado(bool parametro) { return parametro; } private string _atributoPrivado = "Teste"; public int PropriedadeComSetterPrivado { get; private set; } private static string _atributoEstaticoPrivado = "Teste estático"; private static bool MetodoEstaticoPrivado() { return false; } }
Agora adicione um novo projeto de unit tests à solution:
Renomeie a classe “UnitTest1” para “ClasseASerTestadaTest“. A propósito, pelo que percebi, essa é uma padronização bem utilizada no mercado (nome da classe a ser testada + “Test”). Enfim, dentro dessa classe, renomeie “TestMethod1” para “TesteDaAPIPrivada“, e adicione uma referência à ClassLibraryASerTestada no nosso projeto de unit tests. Feito isso, crie uma instância da ClasseASerTestada e, logo em seguida, instancie um PrivateObject passando a instância de ClasseASerTestada que acabou de ser criada:
ClassLibraryASerTestada.ClasseASerTestada obj = new ClassLibrary1.ClasseASerTestada(); PrivateObject privateObj = new PrivateObject(obj);
Uma vez tendo criado o PrivateObject, sua utilização é muito tranquila. Basicamente ele possui os métodos Get* (GetField, GetProperty, etc), Set* (SetField, SetProperty, etc) e Invoke. Os nomes são autoexplicativos. Veja como invocamos e acessamos o retorno do método “MetodoPrivado“:
// Testando MetodoPrivado. var retorno = (bool)privateObj.Invoke("MetodoPrivado"); Assert.IsTrue(retorno);
E se quisermos invocar o overload de “MetodoPrivado” que recebe um parâmetro? Simples, basta passar o parâmetro na chamada do método Invoke:
// Testando MetodoPrivado com parâmetro. retorno = (bool)privateObj.Invoke("MetodoPrivado", false); Assert.IsFalse(retorno);
Para acessar o atributo privado, utilize os métodos GetField e SetField do PrivateObject:
// Testando _atributoPrivado. Assert.AreEqual("Teste", privateObj.GetField("_atributoPrivado")); privateObj.SetField("_atributoPrivado", "Novo valor"); Assert.AreEqual("Novo valor", privateObj.GetField("_atributoPrivado"));
Quer alterar a propriedade que tem um setter privado? Sem problema, é só utilizar o método SetProperty do PrivateObject:
// Testando PropriedadeComSetterPrivado. privateObj.SetProperty("PropriedadeComSetterPrivado", 51); Assert.AreEqual(51, obj.PropriedadeComSetterPrivado);
E, por fim, como fazemos para acessar informações estáticas privadas de um tipo? Para isso, temos que utilizar a classe PrivateType, ao invés de PrivateObject. O construtor dessa classe recebe o tipo da classe a ser testada:
PrivateType privateType = new PrivateType(typeof(ClassLibraryASerTestada.ClasseASerTestada)); // Testando _atributoEstaticoPrivado. Assert.AreEqual("Teste estático", privateType.GetStaticField("_atributoEstaticoPrivado")); privateType.SetStaticField("_atributoEstaticoPrivado", "Novo valor estático"); Assert.AreEqual("Novo valor estático", privateType.GetStaticField("_atributoEstaticoPrivado")); // Testando MetodoEstaticoPrivado. retorno = (bool)privateType.InvokeStatic("MetodoEstaticoPrivado"); Assert.IsFalse(retorno);
E com isso abordamos a utilização das classes PrivateObject e PrivateType que, caso vocês não tenham percebido, são basicamente wrappers da API de reflection do .NET, de forma que consigamos acessar as informações privadas sem nos esforçarmos muito. Infelizmente a nova funcionalidade de Code Lens do Visual Studio 2013 não detecta que esses atributos, métodos e propriedades privados estão, de fato, sendo testados:
Mas, fazer o quê. Nem tudo é perfeito. Quem sabe em uma próxima versão do Visual Studio?
Bonus: antes de me despedir, quero apresentar uma dica muito interessante para vocês. Não sei por qual motivo (talvez dados de telemetria mal utilizados, alguma limitação tecnológica devido à renovação da engine de testes do Visual Studio ou até mesmo falta de tempo) a Microsoft removeu o item “Create Unit Tests” do menu de contexto:
Nota: imagem retirada deste post do StackOverflow
Inclusive, o post de anúncio das novidades sobre Unit Testing no Visual Studio 2012 aborda essa limitação:
In VS2010 you could right click on a method in your code and we would generate a unit test into your test project. This wizard was very tightly coupled to MS-Test and depended on features like Private Accessors to do its work, so it was cut. We are exploring alternatives here, but don’t have any good solutions yet.
Essa opção era realmente muito prática. Acho realmente uma pena terem removido sem antes terem implementado uma alternativa. No Visual Studio 2012 até existe uma gambiarra para adicionar de volta esse item ao menu de contexto, entretanto, ela não funciona mais no Visual Studio 2013. Mas, não se preocupem, “seus problemas estão acabados”! O pessoal do Visual Studio ALM Rangers desenvolveram uma extensão que adiciona essa funcionalidade novamente no Visual Studio 2012 e 2013: ela se chama Unit Test Generator! Não perca tempo e instale agora mesmo, vale a pena!
E com essa dica, vou me despedindo. Espero que vocês tenham gostado. Caso queiram conferir, o código deste artigo está disponível no meu repositório do GitHub.
Por favor, preciso do feedback de vocês! Caso tenham gostado (ou até mesmo se não gostaram), deixem um comentário aí embaixo pra eu saber como estou me saindo nessa onda de artigos.
Até o próximo post!
André Lima
Criando AppBarButtons no Windows 8.1 Nos vemos em 2014!
[…] Eu já abordei anteriormente aqui no blog o funcionamento da classe PrivateObject, que serve para conseguirmos acessar informações privadas de uma classe nos nossos projetos de Unit Tests. Com essa classe nós também conseguimos acessar elementos que tenham o modificador de visibilidade internal. Porém, caso sua necessidade seja somente acessar elementos internal, existe uma maneira muito mais fácil: o atributo InternalsVisibleTo. E é sobre isso que vamos falar no artigo de hoje. […]
[…] 2013 do Team Foundation Server foi um pouco tensa devido a essa remoção de funcionalidades. Outra funcionalidade que foi removida e que eu já comentei aqui no blog foram os “private acc…. Veja o trecho da documentação que fala que as “test lists” não são mais […]
[…] já escrevi uns tempos atrás sobre a substituição de Private Accessors por PrivateObjects quando escrevemos unit tests a partir do Visual Studio 2013. A Microsoft eliminou a funcionalidade […]
[…] Eu já abordei anteriormente aqui no blog o funcionamento da classe PrivateObject, que serve para conseguirmos acessar informações privadas de uma classe nos nossos projetos de Unit Tests. Com essa classe nós também conseguimos acessar elementos que tenham o modificador de visibilidade internal. Porém, caso sua necessidade seja somente acessar elementos internal, existe uma maneira muito mais fácil: o atributo InternalsVisibleTo. E é sobre isso que vamos falar no artigo de hoje. […]