O teste com todas as dependências no local 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 com 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 essa resposta. Você precisa ter certeza de que são fáceis de executar, para que todos os desenvolvedores possam executá-los facilmente antes de verificar o código. "Fácil de executar" significa que seus testes são executados rapidamente e não há configuração extensiva ou configuração necessária para fazê-los ir. 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 em que você é tentado a dizer "Oh, os testes falharam porque Eu não tenho o compartilhamento de rede configurado. Oh, bem, eu vou executá-los mais tarde ".
Se você quiser 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 preocupar com como você obtém esses dados. Ser capaz de testar a lógica do núcleo do seu aplicativo sem depender de material de suporte, como bancos de dados, é incrível. Se você não está fazendo isso, você está perdendo.
P.S. Eu devo acrescentar que é definitivamente possível overengineer em nome da testabilidade. Testar o seu aplicativo ajuda a minimizar isso. Mas em qualquer caso, implementações pobres de uma abordagem não tornam a abordagem menos válida. Qualquer coisa pode ser mal usada e exagerada se não ficarmos perguntando "por que estou fazendo isso?" enquanto se desenvolve.
No que diz respeito às dependências internas, as coisas ficam um pouco turvas. A maneira que eu gosto de pensar sobre isso é que eu quero proteger minha classe tanto quanto possível de mudar 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 usar isso. Se SomeClass mudar e agora requer parâmetros para o construtor ... isso não é problema meu. Eu não deveria ter que mudar o MyClass para acomodar isso.
Agora, isso é apenas tocar na parte do design. No que se refere aos testes de unidade, também quero me proteger de outras classes. Se eu estou testando o MyClass, eu gosto de saber que não há dependências externas, que a SomeClass não introduziu em algum momento uma conexão de banco de dados ou algum outro link externo.
Mas um problema ainda maior é que também sei que alguns dos resultados dos meus métodos dependem da saída de algum método no SomeClass. Sem zombar / apagar o SomeClass, talvez eu não tenha como variar essa entrada na demanda. Se eu tiver sorte, talvez consiga compor meu ambiente dentro do teste de modo a acionar a resposta certa da SomeClass, mas dessa forma introduz complexidade em meus testes e os torna frágeis.
Reescrevendo MyClass para aceitar uma instância de SomeClass no construtor permite-me criar uma instância falsa de SomeClass que retorna o valor que eu quero (seja através de um quadro de zombaria ou com uma simulação manual). Eu geralmente não tenho para introduzir uma interface neste caso. O que fazer ou não é, em muitos aspectos, uma escolha pessoal que pode ser ditada pelo idioma de sua escolha (por exemplo, as interfaces são mais prováveis em C #, mas você definitivamente não precisaria de uma em Ruby).