Pergunta

Ouvi dizer que o teste de unidade é "totalmente demais", "muito legal" e "toda sorte de coisas boas", mas 70% ou mais dos meus arquivos envolvem acesso a banco de dados (alguns ler e escrever) e eu não tenho certeza de como escrever um teste de unidade para esses arquivos.

Eu estou usando PHP e Python, mas eu acho que é uma questão que se aplica à maioria/todos os idiomas que utilizam banco de dados access.

Foi útil?

Solução

Gostaria de sugerir zombando de fora as chamadas ao banco de dados.As simulações são basicamente objetos que se parecem com o objeto que você está tentando chamar um método, no sentido de que eles têm as mesmas propriedades, métodos, etc.disponível para o chamador.Mas em vez de executar qualquer ação que eles são programados para fazer quando um determinado método é chamado, ele ignora que, no total, e apenas retorna um resultado.O resultado é, normalmente, definido por você antes do tempo.

Para criar seus objetos de zombaria, você provavelmente terá que usar algum tipo de inversão de controle/ padrão de injeção de dependência, como no seguinte pseudo-código:

class Bar
{
    private FooDataProvider _dataProvider;

    public instantiate(FooDataProvider dataProvider) {
        _dataProvider = dataProvider;
    }

    public getAllFoos() {
        // instead of calling Foo.GetAll() here, we are introducing an extra layer of abstraction
        return _dataProvider.GetAllFoos();
    }
}

class FooDataProvider
{
    public Foo[] GetAllFoos() {
        return Foo.GetAll();
    }
}

Agora, em sua unidade de teste, você pode criar uma simulação de FooDataProvider, que permite que você chamar o método GetAllFoos sem ter visitas, na verdade, o banco de dados.

class BarTests
{
    public TestGetAllFoos() {
        // here we set up our mock FooDataProvider
        mockRepository = MockingFramework.new()
        mockFooDataProvider = mockRepository.CreateMockOfType(FooDataProvider);

        // create a new array of Foo objects
        testFooArray = new Foo[] {Foo.new(), Foo.new(), Foo.new()}

        // the next statement will cause testFooArray to be returned every time we call FooDAtaProvider.GetAllFoos,
        // instead of calling to the database and returning whatever is in there
        // ExpectCallTo and Returns are methods provided by our imaginary mocking framework
        ExpectCallTo(mockFooDataProvider.GetAllFoos).Returns(testFooArray)

        // now begins our actual unit test
        testBar = new Bar(mockFooDataProvider)
        baz = testBar.GetAllFoos()

        // baz should now equal the testFooArray object we created earlier
        Assert.AreEqual(3, baz.length)
    }
}

Comum zombando cenário, em poucas palavras.É claro que você ainda vai provavelmente querer teste de unidade real de seu banco de dados de chamadas, para que você vai precisar para atingir o banco de dados.

Outras dicas

Idealmente, os objetos devem ser persistente ignorantes.Por exemplo, você deveria ter uma "camada de acesso a dados", que você gostaria de fazer solicitações, que iria retornar objetos.Desta forma, você pode deixar a parte de seus testes de unidade, ou testá-los em isolamento.

Se os objetos estão intimamente ligadas à sua camada de dados, é difícil fazer a devida testes de unidade.a primeira parte do teste de unidade, é a "unidade".Todas as unidades devem ser capazes de ser testado isoladamente.

No meu c# projects, eu uso o NHibernate com um totalmente separada da camada de Dados.Meus objetos de viver no núcleo de modelo de domínio e são acessados a partir de minha camada de aplicação.A camada de aplicação palestras tanto para a camada de dados e o modelo de domínio da camada.

A camada de aplicação é também às vezes chamada de "Camada de Negócios".

Se você estiver usando o PHP, crie um determinado conjunto de classes para APENAS acesso a dados.Certifique-se de que os objetos têm idéia de como eles são persistentes e o fio até a sua aplicação classes.

Outra opção seria usar o escárnio/stubs.

A maneira mais fácil de teste de unidade de um objeto com acesso a banco de dados está usando escopos de transação.

Por exemplo:

    [Test]
    [ExpectedException(typeof(NotFoundException))]
    public void DeleteAttendee() {

        using(TransactionScope scope = new TransactionScope()) {
            Attendee anAttendee = Attendee.Get(3);
            anAttendee.Delete();
            anAttendee.Save();

            //Try reloading. Instance should have been deleted.
            Attendee deletedAttendee = Attendee.Get(3);
        }
    }

Isto irá reverter o estado do banco de dados, basicamente como uma reversão de transações, assim você pode executar o teste quantas vezes você quiser, sem quaisquer efeitos colaterais.Nós usamos esta abordagem com sucesso em projetos de grande porte.Nossa compilação faz ter um pouco de tempo para executar (15 minutos), mas não é horrível por ter 1800 testes de unidade.Além disso, se o tempo de construção é uma preocupação, você pode alterar o processo de compilação para ter várias compilações, um para a construção de src, outro que é acionado depois que trata de testes de unidade, de análise de código, embalagens, etc...

Você deve zombar de acesso ao banco de dados se desejar que a unidade de teste as suas aulas.Afinal, você não quer testar o banco de dados em um teste de unidade.Que seria um teste de integração.

Resumo as chamadas de distância e, em seguida, inserir uma simulação que retorna apenas os dados esperados.Se o seu classes não fazer mais do que a execução de consultas, pode até não valer a pena testá-los, embora...

Eu pode, talvez, dar-lhe um sabor de nossa experiência, quando começamos a olhar para o teste de unidade de nossa camada intermediária processo, que incluiu uma tonelada de "lógica de negócios" operações de sql.

Primeiro criamos uma camada de abstração que nos permitiu "slot" razoável conexão de banco de dados (no nosso caso, nós simplesmente apoiadas um único ODBC-tipo de conexão).

Uma vez que esta estava no lugar, em seguida, foram capazes de fazer algo assim em nosso código (trabalhamos em C++, mas tenho certeza de que você ter uma idéia):

GetDatabase().ExecuteSQL( "INSERT INTO foo ( blá, blá )" )

Em condições normais de tempo de execução, GetDatabase() deve retornar um objeto que alimentou a todos os nossos sql (incluindo consultas), via ODBC diretamente para o banco de dados.

Nós, então, comecei a olhar em bancos de dados de memória - o melhor por um longo caminho parece ser o SQLite.(http://www.sqlite.org/index.html).É incrivelmente simples de configurar e usar, e permitiu-nos subclasse e substituir GetDatabase() para avançar sql para um banco de dados em memória que foi criada e destruída para cada teste realizado.

Nós ainda estamos nos estágios iniciais, mas é bom olhar para longe, no entanto temos a certeza de criar todas as tabelas que são necessários e preenchê-los com dados de teste - no entanto, nós reduzimos a carga de trabalho um pouco a criação de um conjunto genérico de funções auxiliares que pode fazer um monte de tudo isso para nós.

No geral, ele tem ajudado muito com a nossa TDD processo, uma vez que, o que parece bastante inócuo alterações para corrigir alguns bugs podem ter muito estranho afeta no outro (difícil de detectar) áreas de seu sistema, devido à própria natureza do sql/banco de dados.

Obviamente, as nossas experiências têm centrado em torno de um ambiente de desenvolvimento C++, no entanto, eu tenho certeza que você pode talvez obter algo semelhante a trabalhar com PHP/Python.

Espero que isso ajude.

O livro Padrões de Teste xUnit descreve algumas maneiras de lidar com teste de unidade de código que atinge um banco de dados.Concordo com as outras pessoas que estão dizendo que você não quer fazer isso, porque é lento, mas você tem que fazê-lo em algum momento, IMO.Zombando o db ligação para teste de nível superior de coisas é uma boa idéia, mas confira este livro para sugestões sobre coisas que você pode fazer para interagir com o banco de dados real.

Opções que você tem:

  • Escrever um script que irá acabar com banco de dados antes de iniciar os testes de unidade e, em seguida, preencher db com um conjunto predefinido de dados e executar os testes.Você também pode fazer isso antes de cada teste – que vai ser lento, mas menos propensa a erros.
  • Injetar o banco de dados.(Exemplo em pseudo-Java, mas se aplica a todos os OO-línguas)

    class Database {
     public Result query(String query) {... real db here ...}
    }

    classe MockDatabase se estende de Banco de dados { público o Resultado da consulta(String query) { o retorno de "simulação de resultado";} }

    classe ObjectThatUsesDB { público ObjectThatUsesDB de Dados(db) { isso.banco de dados = db;} }

    agora em produção normal de utilização do banco de dados e para todos os testes, você só injetar a simulação de banco de dados que você pode criar ad hoc.

  • Não use DB a todos durante a maior parte do código (que é uma prática ruim, de qualquer maneira).Criar um "banco de dados" objeto que, em vez de retornar com os resultados voltará ao normal objetos (por exemplo,vai voltar User em vez de uma tupla {name: "marcin", password: "blah"}) escreva todos os seus testes com o ad hoc construído real objetos e escrever um grande teste que depende de um banco de dados que faz esta conversão funciona OK.

É claro que essas abordagens não são mutuamente exclusivos, e você pode misturar e combinar como você precisa.

Eu costumo tentar quebrar meus testes entre o teste de objetos (e ORM, se houver) e o teste da db.Eu teste o objeto do lado das coisas, apresentando as chamadas de acesso a dados enquanto eu testar o db lado das coisas testando o objeto interações com o db, que é, na minha experiência, geralmente, bastante limitados.

Eu costumava ficar frustrado com a escrita de testes de unidade, até eu começar a zombar de acesso a dados parte para que eu não precise criar uma base de dados de teste ou gerar dados de teste na mosca.Por escárnio os dados que você pode gerar tudo isso em tempo de execução e certifique-se de que os objetos funcionam correctamente com entradas conhecidas.

Eu nunca fiz isso em PHP, e eu nunca usei Python, mas o que você quer fazer é zombar de fora as chamadas ao banco de dados.Para fazer isso você pode implementar algumas Coi se 3ª festa ferramenta ou gerir-se, em seguida, você pode implementar algumas simulação de versão do banco de dados do chamador, que é onde você irá controlar o resultado de chamada falsa.

Uma forma simples de Coi pode ser executada apenas por programação de Interfaces.Isto requer algum tipo de orientação a objeto acontecendo em seu código para que ele pode não se aplicar ao que você está fazendo (eu disse que desde que tudo o que eu tenho para ir é a sua menção de PHP e Python)

Espero que útil, se nada mais, você tem alguns termos para pesquisar no agora.

Eu concordo com o primeiro post - acesso ao banco de dados devem ser removidos para longe em um DAO camada que implementa uma interface.Em seguida, você pode testar a sua lógica contra um stub implementação da camada DAO.

Você poderia usar estruturas de simulação resumo o mecanismo de banco de dados.Eu não sei se o PHP/Python / tenho alguns, mas para linguagens dinâmicas (C#, Java, etc.) há uma abundância de opções

Ele também depende de como você projetado aqueles banco de dados de código de acesso, devido a alguns problemas de design são mais fáceis para o teste de unidade do que outros, como os posts anteriores já mencionados.

O teste de unidade de seu banco de dados o acesso é fácil o suficiente se o seu projeto tem alta coesão e acoplamento fraco todo.Desta forma, você pode testar apenas as coisas que cada classe faz, sem ter que testar tudo de uma vez.

Por exemplo, se você teste de unidade da sua classe de interface de usuário a testes de escrever só deve tentar verificar a lógica dentro da INTERFACE do usuário funcionou como o esperado, não a lógica de negócios de banco de dados ou ação por trás dessa função.

Se você deseja teste de unidade real de acesso ao banco de dados você vai realmente acabar com mais de um teste de integração, porque você vai ser dependente da pilha de rede e o servidor de banco de dados, mas você pode verificar que o SQL de código faz o que você pediu-lhe para fazer.

O poder oculto de testes de unidade, para mim, pessoalmente, foi que ela me força a concepção de meus aplicativos de forma muito melhor do que eu poderia sem eles.Isso é porque ele realmente me ajudou a romper com a "esta função deve fazer tudo" mentalidade.

Desculpe, eu não tenho qualquer específicos exemplos de código para PHP/Python, mas se você quiser ver um .NET exemplo, eu tenho um post que descreve uma técnica que eu usei para fazer este mesmo teste.

A configuração de dados de teste para testes de unidade pode ser um desafio.

Quando ele vem para o Java, se você usar a Primavera de APIs para o teste de unidade, você pode controlar as operações de uma unidade de nível.Em outras palavras, você pode executar testes de unidade que envolve atualizações de banco de dados/inserções/exclusões e reversão de alterações.No final da execução de deixar tudo no banco de dados como era antes de você começar a execução.Para mim, ele é tão bom quanto ele pode obter.

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