Miałbym dużo do napisania. Może zacznijmy od tego:
Wszędzie czytałem, że gdy aplikacja ma bugi to jeszcze pół biedy, ale jak już ma crashe to jest katastrofa.
To zależy od rodzaju aplikacji, tego jak ważne jest jej nieprzerwane działanie i bezbłędność wyników.
W wielu przypadkach jest na odwrót niż piszesz - crash może być niewielkim problemem i szybko i bardzo wyraźnie dawać znać co jest nie tak i że trzeba to naprawić. "Ciche" błędy mogą mieć o wiele gorsze skutki - przykładowo zapisywanie tylko części edytowanego dokumentu, ujawnianie danych innych użytkowników, nadpisywanie istotnych informacji itd. Czasami crash jest ostatnią deską ratunku mogącą zapobiec wystąpieniu błędu który byłby destrukcyjny i przez to dużo bardziej od niego kosztowny.
Testy jednostkowe nie służą do zabezpieczania się przed crashami. Służą przede wszystkim do ustalania tego jak w najprostszy (a więc zazwyczaj najlepszy) sposób osiągnąć cel, a także do "ochrony" poprawności kodu który już stworzyłeś. Ochrony przed zmianami psującymi go, tak abyś mógł swobodnie bez jakiegokolwiek strachu wprowadzać takie zmiany które go nie psują.
W UML nie musisz planować klasy dokładnie, ze szczegółami. Dokładne interfejsy między bytami mogą wyewoluować naturalnie, w miarę tego jak stają się potrzebne. Staraj się nie pisać ani linijki zanim nie jest ona niezbędna do tego żeby jakiś test który obecnie się nie kompiluje lub nie przechodzi zaczął przechodzić. Bardzo polecam serię Clean Code - jest w części poświęcona TDD. Odcinki są drogie jak na studencką kieszeń, ale zapewne zgadujesz gdzie można dostać je bez opłat.
Z czasem zacznie ci to przychodzić naturalnie, nawet bez pisania testów z góry. Po prostu zaczniesz zauważać że pisząc coś "na zapas", zanim jest to użyte, często będziesz musiał dużo w nim zmieniać kiedy użycie już się pojawi. Sam się dużo razy o tym przekonałem. Czasami interfejs klasy zaplanowany bardzo logicznie i bez widocznych problemów okazuje się po prostu nie pasować do zastosowania które programuje się dopiero później. Trzeba go zmieniać, nieraz dużo kodu leci do kosza. Lepsze efekty osiąga się stosując odwrotną strategię - najpierw pisząc użycia, a dopiero później na ich podstawie klasę która je zaspokoi.