Em algum lugar da sua base de código, há uma linha de código que executa a ação real de conexão com o banco de dados remoto. Essa linha de código é, 9 vezes em 10, uma chamada para um método "interno" fornecido pelas bibliotecas de tempo de execução específicas para seu idioma e ambiente. Como tal, não é o código "your" e você não precisa testá-lo; para fins de um teste de unidade, você pode confiar que esta chamada de método será executada corretamente. O que você pode e deve ainda testar em seu conjunto de testes unitários é como garantir que os parâmetros que serão usados para esta chamada sejam o que você espera que sejam, como garantir que a cadeia de conexão esteja correta ou a instrução SQL ou nome do procedimento armazenado.
Esse é um dos propósitos por trás da restrição de que os testes de unidade não devem deixar seu "sandbox" de tempo de execução e depender do estado externo. Na verdade, é bastante prático; o objetivo de um teste de unidade é verificar se o código que você escreveu (ou está prestes a escrever, no TDD) se comporta da maneira que você pensava. O código que você não escreveu, como a biblioteca que você está usando para executar suas operações de banco de dados, não deve fazer parte do escopo de qualquer teste de unidade, pelo motivo muito simples de você não ter escrito.
No seu conjunto de testes de integração , essas restrições são relaxadas. Agora você podetestes de design que tocam o banco de dados, para garantir que o código que você escreveu seja muito bom com o código que você não escreveu. Esses dois conjuntos de testes devem permanecer segregados, no entanto, porque seu conjunto de testes de unidade é mais eficaz quanto mais rápido ele é executado (para que você possa verificar rapidamente se todas as asserções feitas pelos desenvolvedores sobre seu código ainda são válidas) e, quase por definição, um teste de integração é mais lento em ordens de grandeza devido às dependências adicionais de recursos externos. Deixe o build-bot manipular a execução de seu conjunto completo de integração a cada poucas horas, executando os testes que bloqueiam recursos externos, para que os desenvolvedores não pisem nos dedos dos outros executando esses mesmos testes localmente. E se a construção quebrar, e daí? É dada muito mais importância à garantia de que o build-bot nunca falha em uma build do que provavelmente deveria.
Agora, a aderência estrita a isso depende de sua estratégia exata para conectar e consultar o banco de dados. Em muitos casos em que você deve usar a estrutura de acesso a dados "básicos", como os objetos SqlConnection e SqlStatement do ADO.NET, um método inteiro desenvolvido por você pode consistir em chamadas de método internas e outro código que depende de um conexão com o banco de dados e, portanto, o melhor que você pode fazer nessa situação é zombar de toda a função e confiar em seus conjuntos de testes de integração. Também depende de como você está disposto a projetar suas classes para permitir que linhas de código específicas sejam substituídas para fins de teste (como a sugestão de Tobi do padrão Template Method, que é boa porque permite "zombarias parciais"
Se o modelo de persistência de dados se basear no código da camada de dados (como gatilhos, procs armazenados, etc.), simplesmente não há outra maneira de exercitar o código que você está escrevendo, além de desenvolver testes que residam na camada de dados ou cruzam a camada de dados. limite entre o tempo de execução do aplicativo e o DBMS. Um purista diria que esse padrão, por esse motivo, deve ser evitado em favor de algo como um ORM. Eu não acho que iria tão longe; mesmo na era das consultas integradas ao idioma e outras operações de persistência dependentes do domínio, verificadas pelo compilador, vejo o valor em bloquear o banco de dados apenas nas operações expostas pelo procedimento armazenado e, é claro, esses procedimentos armazenados devem ser verificados usando automação testes. Mas, esses testes não são testes de unidade . Eles são integração testes.
Se você tiver um problema com essa distinção, ela geralmente é baseada em uma importância alta colocada na "cobertura de código" completa, também conhecida como "cobertura de teste de unidade". Você deseja garantir que todas as linhas do seu código sejam cobertas por um teste de unidade. Um objetivo nobre em seu rosto, mas eu digo besteira; essa mentalidade se presta a antipadrões que vão muito além desse caso específico, como escrever testes sem asserção que executam, mas não exercemseu código. Esses tipos de execuções finais exclusivamente para obter números de cobertura são mais prejudiciais do que relaxar a cobertura mínima. Se você deseja garantir que todas as linhas da sua base de código sejam executadas por algum teste automatizado, isso é fácil; ao calcular métricas de cobertura de código, inclua os testes de integração. Você pode até dar um passo adiante e isolar esses testes "Itino" disputados ("Integração apenas no nome") e, entre o seu conjunto de testes de unidade e esta subcategoria de testes de integração (que ainda deve executar razoavelmente rápido), você deve se danar quase perto da cobertura total.