Recentemente, discuti com alguns amigos qual dos 2 métodos a seguir é melhor para stub retornar resultados ou chamadas para métodos dentro da mesma classe e métodos dentro da mesma classe.
Este é um exemplo muito simplificado. Na realidade, as funções são muito mais complexas.
Exemplo:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionB()
{
return new Random().Next();
}
}
Então, para testar isso, temos 2 métodos.
Método 1: Use Funções e Ações para substituir a funcionalidade dos métodos. Exemplo:
public class MyClass
{
public Func<int> FunctionB { get; set; }
public MyClass()
{
FunctionB = FunctionBImpl;
}
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected int FunctionBImpl()
{
return new Random().Next();
}
}
[TestClass]
public class MyClassTests
{
private MyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new MyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionB = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Método 2: Tornar os membros virtuais, derivar classe e na classe derivada usar Funções e Ações para substituir a funcionalidade Exemplo:
public class MyClass
{
public bool FunctionA()
{
return FunctionB() % 2 == 0;
}
protected virtual int FunctionB()
{
return new Random().Next();
}
}
public class TestableMyClass
{
public Func<int> FunctionBFunc { get; set; }
public MyClass()
{
FunctionBFunc = base.FunctionB;
}
protected override int FunctionB()
{
return FunctionBFunc();
}
}
[TestClass]
public class MyClassTests
{
private TestableMyClass _subject;
[TestInitialize]
public void Initialize()
{
_subject = new TestableMyClass();
}
[TestMethod]
public void FunctionA_WhenNumberIsOdd_ReturnsTrue()
{
_subject.FunctionBFunc = () => 1;
var result = _subject.FunctionA();
Assert.IsFalse(result);
}
}
Quero saber o que é melhor e também POR QUE?
Atualização: NOTA: A FunctionB também pode ser pública
c#
design
unit-testing
tranceru1
fonte
fonte
FunctionA
retorna um bool, mas apenas define uma variável localx
e não retorna nada.public static
em uma classe diferente.FunctionB
está quebrado por design.new Random().Next()
quase sempre está errado. Você deve injetar a instância deRandom
. (Random
É também uma classe mal concebidos, o que pode causar alguns problemas adicionais)Respostas:
Editado após a atualização original do pôster.
Isenção de responsabilidade: não é um programador de C # (principalmente Java ou Ruby). Minha resposta seria: eu não testaria nada e acho que não deveria.
A versão mais longa é: métodos privados / protegidos não fazem parte da API, são basicamente opções de implementação, que você pode decidir revisar, atualizar ou jogar completamente fora sem causar nenhum impacto externo.
Suponho que você tenha um teste em FunctionA (), que é a parte da classe que é visível do mundo externo. Deve ser o único que tem um contrato para implementar (e que pode ser testado). Seu método particular / protegido não tem contrato para cumprir e / ou testar.
Consulte uma discussão relacionada lá: https://stackoverflow.com/questions/105007/should-i-test-private-methods-or-only-public-ones
Após o comentário , se FunctionB for público, testarei ambos usando o teste de unidade. Você pode pensar que o teste da FunctionA não é totalmente "unidade" (como chama FunctionB), mas eu não ficaria muito preocupado com isso: se o teste da FunctionB funcionar, mas não o da FunctionA, significa claramente que o problema não está no subdomínio da FunctionB, que é bom o suficiente para mim como discriminador.
Se você realmente deseja separar totalmente os dois testes, eu usaria algum tipo de técnica de zombaria para zombar da FunctionB ao testar a FunctionA (normalmente, retorne um valor correto conhecido fixo). Não tenho o conhecimento do ecossistema C # para aconselhar uma biblioteca de zombaria específica, mas você pode olhar para esta pergunta .
fonte
MyClass
e substituir o método pela funcionalidade que você deseja stub. Também pode ser uma boa ideia atualizar sua pergunta para incluir queFunctionB
pode ser pública.protected
Os métodos fazem parte da superfície pública de uma classe, a menos que você garanta que não haja implementações de sua classe em assemblies diferentes.Eu subscrevo a teoria de que, se uma função é importante para testar, ou é importante substituir, é importante o suficiente para não ser um detalhe de implementação privada da classe em teste, mas para ser um detalhe de implementação pública de uma classe diferente .
Então, se estou em um cenário em que tenho
Então eu vou refatorar.
Agora, tenho um cenário em que D () é testável de forma independente e totalmente substituível.
Como meio de organização, meu colaborador pode não viver no mesmo nível de namespace. Por exemplo, se
A
estiver no FooCorp.BLL, meu colaborador poderá ter outra camada, como no FooCorp.BLL.Collaborators (ou qualquer outro nome que seja apropriado). Meu colaborador pode ainda estar visível apenas dentro da montagem por meio dointernal
modificador de acesso, que eu também exporia aos meus projetos de teste de unidade por meio doInternalsVisibleTo
atributo assembly. O ponto principal é que você ainda pode manter sua API limpa, no que diz respeito aos chamadores, enquanto produz código verificável.fonte
Adicionando o que Martin aponta,
Se o seu método for privado / protegido - não o teste. É interno à classe e não deve ser acessado fora da classe.
Nas duas abordagens mencionadas, tenho essas preocupações -
Método 1 - Isso realmente altera a classe sob o comportamento do teste.
Método 2 - Na verdade, isso não testa o código de produção, mas testa outra implementação.
No problema declarado, vejo que a única lógica de A é ver se a saída da FunçãoB é par. Embora ilustrativa, a FunctionB fornece valor aleatório, o que é difícil de testar.
Espero um cenário realista em que possamos configurar o MyClass para sabermos o que FunctionB retornaria. Então nosso resultado esperado é conhecido, podemos chamar FunctionA e afirmar o resultado real.
fonte
protected
é quase o mesmo quepublic
. Somenteprivate
einternal
são detalhes de implementação.internal protected
criá -los , usar um auxiliar de reflexão particular ou criar uma classe derivada em seu projeto de teste.Eu pessoalmente uso o Method1, ou seja, transformando todos os métodos em Actions ou Funcs, pois isso melhorou bastante a testabilidade do código para mim. Como em qualquer solução, existem prós e contras com essa abordagem:
Prós
Contras
Portanto, para resumir, usar o teste Func e Ações para unidade é ótimo se você souber que suas aulas nunca serão substituídas.
Além disso, geralmente não crio propriedades para Funcs, mas as inline diretamente como tal
Espero que isto ajude!
fonte
Usando Mock é possível. Nuget: https://www.nuget.org/packages/moq/
E acredite em mim, é bastante simples e faz sentido.
O mock precisa de um método virtual para substituir.
fonte