Sí, SOLID es una muy buena manera de diseñar código que se pueda probar fácilmente. Como una breve introducción:
S - Principio de responsabilidad única: un objeto debe hacer exactamente una cosa, y debe ser el único objeto en la base de código que hace esa única cosa. Por ejemplo, tome una clase de dominio, diga una factura. La clase Factura debe representar la estructura de datos y las reglas comerciales de una factura tal como se utiliza en el sistema. Debería ser la única clase que representa una factura en la base de código. Esto puede desglosarse aún más para decir que un método debe tener un propósito y debe ser el único método en la base de código que satisfaga esta necesidad.
Siguiendo este principio, aumenta la capacidad de prueba de su diseño al disminuir la cantidad de pruebas que tiene que escribir que prueban la misma funcionalidad en diferentes objetos, y también generalmente termina con piezas de funcionalidad más pequeñas que son más fáciles de probar de forma aislada.
O - Principio abierto / cerrado: una clase debe estar abierta a la extensión, pero cerrada al cambio . Una vez que un objeto existe y funciona correctamente, idealmente no debería haber necesidad de volver a ese objeto para realizar cambios que agreguen nuevas funciones. En cambio, el objeto debe extenderse, ya sea derivándolo o conectando implementaciones de dependencia nuevas o diferentes, para proporcionar esa nueva funcionalidad. Esto evita la regresión; puede introducir la nueva funcionalidad cuando y donde sea necesario, sin cambiar el comportamiento del objeto, ya que ya se usa en otros lugares.
Al adherirse a este principio, generalmente aumenta la capacidad del código para tolerar "simulacros", y también evita tener que reescribir las pruebas para anticipar un nuevo comportamiento; todas las pruebas existentes para un objeto aún deberían funcionar en la implementación no extendida, mientras que las nuevas pruebas para nuevas funcionalidades usando la implementación extendida también deberían funcionar.
L - Principio de sustitución de Liskov: Una clase A, que depende de la clase B, debería poder usar cualquier X: B sin saber la diferencia. Esto básicamente significa que cualquier cosa que use como dependencia debería tener un comportamiento similar al que ve la clase dependiente. Como un breve ejemplo, supongamos que tiene una interfaz IWriter que expone Write (string), que es implementado por ConsoleWriter. Ahora tiene que escribir en un archivo, por lo que debe crear FileWriter. Al hacerlo, debe asegurarse de que FileWriter se pueda usar de la misma manera que lo hizo ConsoleWriter (lo que significa que la única forma en que el dependiente puede interactuar con él es llamando a Write (string)), por lo que es posible que FileWriter necesite información adicional para hacerlo. El trabajo (como la ruta y el archivo para escribir) debe proporcionarse desde otro lugar que no sea el dependiente.
Esto es enorme para escribir código comprobable, porque un diseño que se ajusta al LSP puede tener un objeto "simulado" sustituido por el objeto real en cualquier momento sin cambiar el comportamiento esperado, lo que permite probar pequeñas piezas de código de forma aislada con la confianza que el sistema funcionará con los objetos reales conectados.
I - Principio de segregación de interfaz: una interfaz debe tener tan pocos métodos como sea posible para proporcionar la funcionalidad del rol definido por la interfaz . En pocas palabras, más interfaces más pequeñas son mejores que menos interfaces más grandes. Esto se debe a que una interfaz grande tiene más razones para cambiar y provoca más cambios en otras partes de la base de código que pueden no ser necesarios.
La adhesión al ISP mejora la capacidad de prueba al reducir la complejidad de los sistemas bajo prueba y las dependencias de esos SUT. Si el objeto que está probando depende de una interfaz IDoThreeThings que expone DoOne (), DoTwo () y DoThree (), debe burlarse de un objeto que implemente los tres métodos, incluso si el objeto solo usa el método DoTwo. Pero, si el objeto depende solo de IDoTwo (que expone solo DoTwo), puede burlarse más fácilmente de un objeto que tenga ese método.
D - Principio de inversión de dependencia: las concreciones y abstracciones nunca deberían depender de otras concreciones, sino de abstracciones . Este principio hace cumplir directamente el principio del acoplamiento suelto. Un objeto nunca debería tener que saber qué es un objeto; en cambio, debería importarle lo que HACE un objeto. Por lo tanto, el uso de interfaces y / o clases base abstractas siempre es preferible al uso de implementaciones concretas cuando se definen propiedades y parámetros de un objeto o método. Eso le permite cambiar una implementación por otra sin tener que cambiar el uso (si también sigue LSP, que va de la mano con DIP).
Nuevamente, esto es enorme para la capacidad de prueba, ya que le permite, una vez más, inyectar una implementación simulada de una dependencia en lugar de una implementación de "producción" en el objeto que se está probando, mientras sigue probando el objeto en la forma exacta que tendrá mientras en producción. Esta es la clave para las pruebas unitarias "de forma aislada".