Tak, SOLID to bardzo dobry sposób na zaprojektowanie kodu, który można łatwo przetestować. Jako krótki podkład:
S - Zasada pojedynczej odpowiedzialności: Obiekt powinien zrobić dokładnie jedną rzecz i powinien być jedynym obiektem w bazie kodu, który wykonuje tę jedną rzecz. Weźmy na przykład klasę domeny, powiedzmy fakturę. Klasa Faktura powinna reprezentować strukturę danych i reguły biznesowe faktury stosowanej w systemie. Powinna to być jedyna klasa reprezentująca fakturę w bazie kodu. Można to dalej rozbić, aby powiedzieć, że metoda powinna mieć jeden cel i powinna być jedyną metodą w bazie danych, która spełnia tę potrzebę.
Postępując zgodnie z tą zasadą, zwiększasz testowalność swojego projektu, zmniejszając liczbę testów, które musisz napisać, które testują tę samą funkcjonalność na różnych obiektach, a także zazwyczaj uzyskujesz mniejsze części funkcjonalności, które są łatwiejsze do przetestowania w izolacji.
O - Zasada otwarta / zamknięta: klasa powinna być otwarta na rozszerzenie, ale zamknięta na zmianę . Gdy obiekt istnieje i działa poprawnie, idealnie nie powinno być potrzeby powrotu do tego obiektu, aby wprowadzić zmiany, które dodają nową funkcjonalność. Zamiast tego obiekt należy rozszerzyć, albo poprzez jego wyprowadzenie, albo przez podłączenie do niego nowych lub różnych implementacji zależności, aby zapewnić tę nową funkcjonalność. Pozwala to uniknąć regresji; możesz wprowadzić nową funkcjonalność tam, gdzie jest potrzebna, bez zmiany zachowania obiektu, ponieważ jest on już używany gdzie indziej.
Przestrzegając tej zasady, ogólnie zwiększasz zdolność kodu do tolerowania „próbnych”, a także unikasz przepisywania testów w celu przewidywania nowego zachowania; wszystkie istniejące testy dla obiektu powinny nadal działać w przypadku nierozszerzonej implementacji, a także powinny działać nowe testy dla nowej funkcjonalności korzystającej z rozszerzonej implementacji.
L - Zasada podstawienia Liskowa: Klasa A, zależna od klasy B, powinna mieć możliwość używania dowolnego X: B bez znajomości różnicy. Zasadniczo oznacza to, że wszystko, czego użyjesz jako zależność, powinno mieć podobne zachowanie, jakie widzi klasa zależna. Jako krótki przykład powiedzmy, że masz interfejs IWriter, który udostępnia zapis (ciąg), który jest implementowany przez ConsoleWriter. Teraz musisz napisać do pliku, więc utworzysz FileWriter. Czyniąc to, musisz upewnić się, że FileWriter może być używany w taki sam sposób, jak ConsoleWriter (co oznacza, że jedyny sposób, w jaki osoba zależna może z nim współdziałać, wywołując Write (string)), a także dodatkowe informacje, które może wymagać FileWriter zadanie (takie jak ścieżka i plik do zapisu) musi być dostarczone skądinąd poza zależną.
Jest to ogromna zaleta przy pisaniu testowalnego kodu, ponieważ projekt zgodny z LSP może w dowolnym momencie zastąpić rzeczywisty obiekt „kpionym” obiektem bez zmiany oczekiwanego zachowania, umożliwiając testowanie małych fragmentów kodu w sposób pewny że system będzie wtedy pracował z podłączonymi prawdziwymi obiektami.
I - Zasada segregacji interfejsu: Interfejs powinien mieć tak mało metod, jak to możliwe, aby zapewnić funkcjonalność roli zdefiniowanej przez interfejs . Krótko mówiąc, więcej mniejszych interfejsów jest lepszych niż mniejszych interfejsów. Wynika to z faktu, że duży interfejs ma więcej powodów do zmiany i powoduje więcej zmian w innym miejscu w bazie kodu, które mogą nie być konieczne.
Zgodność z ISP poprawia testowalność poprzez zmniejszenie złożoności testowanych systemów i zależności tych SUT. Jeśli testowany obiekt zależy od interfejsu IDoThreeThings, który udostępnia DoOne (), DoTwo () i DoThree (), musisz wyśmiewać obiekt, który implementuje wszystkie trzy metody, nawet jeśli obiekt używa tylko metody DoTwo. Ale jeśli obiekt zależy tylko od IDoTwo (który udostępnia tylko DoTwo), możesz łatwiej wyśmiewać obiekt, który ma tę jedną metodę.
D - Zasada odwrócenia zależności: konkrecje i abstrakcje nigdy nie powinny zależeć od innych konkrecji, ale od abstrakcji . Ta zasada bezpośrednio egzekwuje zasadę luźnego sprzężenia. Obiekt nigdy nie powinien wiedzieć, czym JEST obiekt; zamiast tego powinno obchodzić się, co robi obiekt. Dlatego użycie interfejsów i / lub abstrakcyjnych klas bazowych zawsze powinno być preferowane w porównaniu z zastosowaniem konkretnych implementacji podczas definiowania właściwości i parametrów obiektu lub metody. Pozwala to na zamianę jednej implementacji na inną bez konieczności zmiany użycia (jeśli podążasz za LSP, co idzie w parze z DIP).
Ponownie, ma to ogromne znaczenie dla testowalności, ponieważ pozwala raz jeszcze wstrzyknąć próbną implementację zależności zamiast implementacji „produkcyjnej” do testowanego obiektu, jednocześnie testując obiekt w dokładnie takiej formie, jaką będzie miał podczas w produkcji. Jest to klucz do testów jednostkowych „w izolacji”.