Testar com todas as dependências existentes ainda é importante, mas está mais no campo dos testes de integração, como Jeffrey Faust disse.
Um dos aspectos mais importantes do teste de unidade é tornar seus testes confiáveis. Se você não acredita que um teste de aprovação realmente significa que as coisas estão boas e que um teste de falha realmente significa um problema no código de produção, seus testes não são tão úteis quanto podem ser.
Para tornar seus testes confiáveis, você precisa fazer algumas coisas, mas vou me concentrar em apenas uma para esta resposta. Você precisa garantir que eles sejam fáceis de executar, para que todos os desenvolvedores possam executá-los facilmente antes de fazer o check-in do código. "Fácil de executar" significa que seus testes são executados rapidamente e não é necessária nenhuma configuração ou configuração extensa para fazê-los funcionar. Idealmente, qualquer pessoa deve poder verificar a versão mais recente do código, executar os testes imediatamente e vê-los passar.
Abstrair as dependências de outras coisas (sistema de arquivos, banco de dados, serviços da web etc.) permite evitar a configuração e torna você e outros desenvolvedores menos suscetíveis a situações nas quais você é tentado a dizer "Ah, os testes falharam porque eu não" não tenho o compartilhamento de rede configurado. Oh, bem. Vou executá-los mais tarde. "
Se você deseja testar o que faz com alguns dados, seus testes de unidade para esse código de lógica de negócios não devem se importar com a forma como você obtém esses dados. Ser capaz de testar a lógica principal do seu aplicativo sem depender de itens como bancos de dados é impressionante. Se você não está fazendo isso, está perdendo.
PS Devo acrescentar que é definitivamente possível fazer engenharia em nome da testabilidade. Testar o aplicativo ajuda a atenuar isso. Mas, de qualquer forma, implementações ruins de uma abordagem não tornam a abordagem menos válida. Qualquer coisa pode ser mal usada e exagerada se não continuar perguntando "por que estou fazendo isso?" durante o desenvolvimento.
No que diz respeito às dependências internas, as coisas ficam um pouco confusas. O jeito que eu gosto de pensar é que quero proteger minha classe o máximo possível de mudanças pelas razões erradas. Se eu tiver uma configuração como algo assim ...
public class MyClass
{
private SomeClass someClass;
public MyClass()
{
someClass = new SomeClass();
}
// use someClass in some way
}
Eu geralmente não me importo como SomeClass
é criado. Eu só quero usá-lo. Se SomeClass mudar e agora exigir parâmetros ao construtor ... isso não é problema meu. Eu não deveria ter que mudar o MyClass para acomodar isso.
Agora, isso é apenas um toque na parte do design. No que diz respeito aos testes de unidade, também quero me proteger de outras classes. Se estou testando o MyClass, gosto de saber que não há dependências externas, que SomeClass não introduziu em algum momento uma conexão com o banco de dados ou outro link externo.
Mas um problema ainda maior é que eu também sei que alguns dos resultados dos meus métodos dependem da saída de algum método no SomeClass. Sem zombar / excluir SomeClass, talvez eu não tenha como variar essa entrada na demanda. Se tiver sorte, talvez eu consiga compor meu ambiente dentro do teste de maneira que ele acione a resposta certa do SomeClass, mas dessa maneira introduz complexidade nos meus testes e os torna quebradiços.
Reescrever MyClass para aceitar uma instância de SomeClass no construtor me permite criar uma instância falsa de SomeClass que retorna o valor que eu quero (por meio de uma estrutura de simulação ou com uma simulação manual). Geralmente, não preciso introduzir uma interface neste caso. Fazer isso ou não é, sob muitos aspectos, uma escolha pessoal que pode ser ditada pelo seu idioma preferido (por exemplo, as interfaces são mais prováveis em C #, mas você definitivamente não precisaria de uma em Ruby).