Pergunta

Se eu tiver a interface IFoo e várias classes que a implementam, qual é a melhor/mais elegante/mais inteligente maneira de testar todas essas classes na interface?

Eu gostaria de reduzir a duplicação de código de teste, mas ainda assim 'permanecer fiel' aos princípios dos testes unitários.

O que você consideraria a melhor prática?Estou usando o NUnit, mas suponho que exemplos de qualquer estrutura de teste de unidade seriam válidos

Foi útil?

Solução

Se você tiver classes que implementam qualquer interface, todas elas precisarão implementar os métodos nessa interface.Para testar essas classes você precisa criar uma classe de teste unitário para cada uma das classes.

Em vez disso, vamos seguir uma rota mais inteligente;se o seu objetivo é evite código e teste a duplicação de código você pode querer criar uma classe abstrata que lide com o recorrente código.

Por exemplo.você tem a seguinte interface:

public interface IFoo {

    public void CommonCode();

    public void SpecificCode();

}

Você pode querer criar uma classe abstrata:

public abstract class AbstractFoo : IFoo {

    public void CommonCode() {
          SpecificCode();
    }

    public abstract void SpecificCode();

}

Testar isso é fácil;implemente a classe abstrata na classe de teste como uma classe interna:

[TestFixture]
public void TestClass {

    private class TestFoo : AbstractFoo {
        boolean hasCalledSpecificCode = false;
        public void SpecificCode() {
            hasCalledSpecificCode = true;
        }
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        TestFoo fooFighter = new TestFoo();
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }
}

... ou deixe a classe de teste estender a própria classe abstrata, se isso for do seu interesse.

[TestFixture]
public void TestClass : AbstractFoo {

    boolean hasCalledSpecificCode;
    public void specificCode() {
        hasCalledSpecificCode = true;
    }

    [Test]
    public void testCommonCallsSpecificCode() {
        AbstractFoo fooFighter = this;
        hasCalledSpecificCode = false;
        fooFighter.CommonCode();
        Assert.That(fooFighter.hasCalledSpecificCode, Is.True());
    }        

}

Ter uma classe abstrata cuidando do código comum que uma interface implica proporciona um design de código muito mais limpo.

Eu espero que isso faça sentido para você.


Como observação lateral, este é um padrão de design comum chamado Padrão de método de modelo.No exemplo acima, o método de modelo é o CommonCode método e SpecificCode é chamado de stub ou gancho.A ideia é que qualquer pessoa possa estender o comportamento sem a necessidade de conhecer os bastidores.

Muitas estruturas dependem desse padrão comportamental, por ex. ASP.NET onde você tem que implementar os ganchos em uma página ou controles de usuário, como o gerado Page_Load método que é chamado pelo Load evento, o método de modelo chama os ganchos nos bastidores.Existem muito mais exemplos disso.Basicamente, qualquer coisa que você precise implementar usando as palavras "load", "init" ou "render" é chamada por um método de modelo.

Outras dicas

Eu discordo com Jon Limjap quando ele diz,

Não é um contrato sobre a.) como o método deve ser implementado e b.) o que esse método deve fazer exatamente (ele apenas garante o tipo de retorno), as duas razões que descobri seriam o seu motivo para querer esse tipo de teste.

Pode haver muitas partes do contrato não especificadas no tipo de devolução.Um exemplo independente de linguagem:

public interface List {

  // adds o and returns the list
  public List add(Object o);

  // removed the first occurrence of o and returns the list
  public List remove(Object o);

}

Seus testes de unidade em LinkedList, ArrayList, CircularlyLinkedList e todos os outros devem testar não apenas se as próprias listas foram retornadas, mas também se foram modificadas corretamente.

Houve um pergunta anterior no projeto por contrato, o que pode ajudar a apontar a direção certa sobre uma maneira de secar esses testes.

Se você não quer a sobrecarga dos contratos, recomendo plataformas de teste, nos moldes do que Spoike recomendado:

abstract class BaseListTest {

  abstract public List newListInstance();

  public void testAddToList() {
    // do some adding tests
  }

  public void testRemoveFromList() {
    // do some removing tests
  }

}

class ArrayListTest < BaseListTest {
  List newListInstance() { new ArrayList(); }

  public void arrayListSpecificTest1() {
    // test something about ArrayLists beyond the List requirements
  }
}

Não acho que essa seja a melhor prática.

A verdade é que uma interface nada mais é do que um contrato de implementação de um método.Isso é não um contrato sobre a.) como o método deve ser implementado e b.) o que esse método deve fazer exatamente (ele apenas garante o tipo de retorno), os dois motivos que descobri seriam o seu motivo para desejar esse tipo de teste.

Se você realmente deseja estar no controle da implementação do seu método, você tem a opção de:

  • Implementá-lo como um método em uma classe abstrata e herdar disso.Você ainda precisará herdá-lo para uma classe concreta, mas tem certeza de que, a menos que seja explicitamente substituído, esse método fará a coisa correta.
  • No .NET 3.5/C# 3.0, implementando o método como um método de extensão referenciando a Interface

Exemplo:

public static ReturnType MethodName (this IMyinterface myImplementation, SomeObject someParameter)
{
    //method body goes here
}

Qualquer implementação que faça referência adequada a esse método de extensão emitirá precisamente esse método de extensão, portanto, você só precisará testá-lo uma vez.

@Imperador XLII

Gosto do som dos testes combinatórios no MbUnit, tentei a técnica de teste de interface de classe base abstrata com NUnit e, embora funcione, você precisaria ter um dispositivo de teste separado para cada interface que uma classe implementa (já que em C# não há herança múltipla - embora classes internas possam ser usadas, o que é muito legal).Na realidade isso é bom, talvez até vantajoso porque agrupa seus testes para a classe de implementação por interface.Mas seria bom se o quadro pudesse ser mais inteligente.Se eu pudesse usar um atributo para marcar uma classe como uma classe de teste 'oficial' para uma interface, a estrutura procuraria no assembly em teste todas as classes que implementam a interface e executaria esses testes nela.

Isso seria legal.

Que tal uma hierarquia de classes [TestFixture]?Coloque o código de teste comum na classe de teste base e herde-o nas classes de teste filhas.

Ao testar uma interface ou contrato de classe base, prefiro deixar que a estrutura de teste cuide automaticamente de encontrar todos os implementadores.Isso permite que você se concentre na interface em teste e tenha certeza razoável de que todas as implementações serão testadas, sem precisar fazer muitas implementações manuais.

  • Para xUnit.net, criei um Resolvedor de tipo biblioteca para procurar todas as implementações de um tipo específico (as extensões xUnit.net são apenas um invólucro fino sobre a funcionalidade Type Resolver, portanto, pode ser adaptado para uso em outras estruturas).
  • Em Unidade Mb, você pode usar um CombinatorialTest com UsingImplementations atributos nos parâmetros.
  • Para outras estruturas, o padrão de classe base Spoike mencionado pode ser útil.

Além de testar os fundamentos da interface, você também deve testar se cada implementação individual segue seus requisitos específicos.

Não uso NUnit, mas testei interfaces C++.Eu testaria primeiro uma classe TestFoo, que é uma implementação básica dela para garantir que o material genérico funcione.Então você só precisa testar o que é exclusivo de cada interface.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top