Sim, o SOLID é uma maneira muito boa de criar código que pode ser facilmente testado. Como uma cartilha curta:
S - Princípio da responsabilidade única: um objeto deve fazer exatamente uma coisa e deve ser o único objeto na base de código que faz essa coisa. Por exemplo, pegue uma classe de domínio, por exemplo, uma fatura. A classe Fatura deve representar a estrutura de dados e as regras de negócios de uma fatura, conforme usado no sistema. Deve ser a única classe que representa uma fatura na base de código. Isso pode ser dividido em detalhes para dizer que um método deve ter um propósito e deve ser o único método na base de código que atende a essa necessidade.
Seguindo esse princípio, você aumenta a capacidade de teste do seu design, diminuindo o número de testes que precisa escrever para testar a mesma funcionalidade em objetos diferentes e, geralmente, também acaba com partes menores de funcionalidade que são mais fáceis de testar isoladamente.
O - Princípio Aberto / Fechado: Uma classe deve ser aberta para extensão, mas fechada para alteração . Uma vez que um objeto exista e funcione corretamente, o ideal é que não haja necessidade de voltar ao objeto para fazer alterações que adicionem novas funcionalidades. Em vez disso, o objeto deve ser estendido, derivando-o ou conectando implementações de dependência novas ou diferentes a ele, para fornecer essa nova funcionalidade. Isso evita regressão; você pode introduzir a nova funcionalidade quando e onde for necessária, sem alterar o comportamento do objeto, pois ele já é usado em outro lugar.
Ao seguir esse princípio, você geralmente aumenta a capacidade do código de tolerar "zombarias" e também evita a necessidade de reescrever testes para antecipar um novo comportamento; todos os testes existentes para um objeto ainda devem funcionar na implementação não estendida, enquanto novos testes para novas funcionalidades usando a implementação estendida também devem funcionar.
L - Princípio de Substituição de Liskov: Uma classe A, dependente da classe B, deve poder usar qualquer X: B sem saber a diferença. Isso basicamente significa que qualquer coisa que você use como dependência deve ter um comportamento semelhante ao da classe dependente. Como um pequeno exemplo, digamos que você tenha uma interface IWriter que expõe Write (string), implementada pelo ConsoleWriter. Agora você precisa gravar em um arquivo e criar o FileWriter. Ao fazer isso, você deve garantir que o FileWriter possa ser usado da mesma maneira que o ConsoleWriter (o que significa que a única maneira pela qual o dependente pode interagir com ele é chamando Write (string)) e, portanto, informações adicionais que o FileWriter talvez precise fazer isso O trabalho (como o caminho e o arquivo no qual gravar) deve ser fornecido de outro lugar que não o dependente.
Isso é imenso para escrever código testável, porque um design que esteja em conformidade com o LSP pode ter um objeto "zombado" substituído pela coisa real a qualquer momento sem alterar o comportamento esperado, permitindo que pequenos pedaços de código sejam testados isoladamente com confiança. que o sistema funcionará com os objetos reais conectados.
I - Princípio de Segregação de Interface: Uma interface deve ter o mínimo de métodos possível para fornecer a funcionalidade da função definida pela interface . Simplificando, interfaces menores são melhores que menos interfaces maiores. Isso ocorre porque uma interface grande tem mais motivos para mudar e causa mais alterações em outros lugares da base de código que podem não ser necessárias.
A adesão ao ISP melhora a testabilidade, reduzindo a complexidade dos sistemas em teste e as dependências desses SUTs. Se o objeto que você está testando depende de uma interface IDoThreeThings que expõe DoOne (), DoTwo () e DoThree (), você deve simular um objeto que implemente todos os três métodos, mesmo que o objeto use apenas o método DoTwo. Porém, se o objeto depender apenas do IDoTwo (que expõe apenas o DoTwo), você poderá zombar mais facilmente de um objeto que possua esse método.
D - Princípio da Inversão da Dependência: Concretizações e abstrações nunca devem depender de outras concretizações, mas de abstrações . Esse princípio reforça diretamente o princípio do acoplamento flexível. Um objeto nunca deve saber o que é um objeto; em vez disso, deveria se importar com o que um objeto FAZ. Portanto, o uso de interfaces e / ou classes básicas abstratas sempre deve ser preferido sobre o uso de implementações concretas ao definir propriedades e parâmetros de um objeto ou método. Isso permite que você troque uma implementação por outra sem precisar alterar o uso (se você também seguir o LSP, que acompanha o DIP).
Novamente, isso é imenso para a testabilidade, pois permite, mais uma vez, injetar uma implementação simulada de uma dependência em vez de uma implementação de "produção" no objeto que está sendo testado, enquanto ainda testa o objeto na forma exata que ele terá enquanto em produção. Essa é a chave para o teste de unidade "isoladamente".