Unit-testów używamy tam, gdzie API komponentu jest proste, a implementacja skomplikowana, ale niewymagająca zależności. Oczywiście dążymy do tego, aby interfejs każdego komponentu było prosty, niemniej niestety nie zawsze jest to możliwe. Przykładem takiego dobrego komponentu do testowania unit-testami byłoby np. malloc(). Interfejs prosty jak konstrukcja cepa, za to implementacja może być bardzo rozbudowana i nawet składać się z wielu prywatnych pod-komponentów (które zresztą też mogą mieć swoje unit-testy).
Z drugiej strony w systemach często są komponenty, których główną rolą jest pośredniczenie między innymi komponentami. Wszelkie proxy, adaptery, fasady, klasy DTO mapowane do bazy danych, itp. Takie komponenty nie mają same w sobie zbyt dużo logiki, za to mają bardzo dużo interakcji z resztą systemu, sklejają różne kawałki w całość i w związku z tym mają wiele zależności od innych komponentów lub wiele innych komponentów od nich zależy. Przykładem czegoś takiego może być np. handler Netty implementujący jakiś protokół, który dekoduje / przyjmuje żądania, konwertuje je i przesyła do innych komponentów, obsługuje wyjątki itp. Ale sam z siebie w sumie mało robi - głównie posługuje się innymi komponentami (inaczej musiałby być jakimś God-class). Co nie oznacza, że nie ma wcale kodu. Ba, może mieć bardzo dużo kodu, tyle że ten kod wygląda tak "weź ten obiekt A stąd i wyślij tam do B, a jak wróci w postaci C, to użyj komponentu D do przekonwertowania go na obiekt Z i wyślij go do Y". I jak to przetestować? Większość błędów jakie pojawiają się w tego typu komponentach wynika z nieprawidłowego użycia komponentów, z którymi takie "proxy" współpracuje. Np. nieprawidłowej kolejności wywołań, źle zainicjowanych argumentów, plików konfiguracyjnych w złym miejscu, niezrozumienia przez programistę semantyki pewnych operacji (niekoniecznie z winy programisty - czasami dokumentacja jest do d***, albo komponent robi co innego niż dokumentacja) itp.
Pisanie mocków do czegoś takiego to po pierwsze masakra, po drugie nie wychwyci błędów. Jak mockiem wyłapiesz, że np. wysyłasz błędne zapytanie? Jeszcze rozumiem, że zapytanie generujesz w wyniku jakiegoś złożonego procesu, ale jeśli wklepałeś je z palca w kodzie? To co taki test z mockiem Ci da? Jak wyłapiesz, czy zewnętrzny system faktycznie obsłuży to zapytanie i zwróci oczekiwane wyniki? Albo jak mockiem wyłapiesz, że nieprawidłowo zainicjowałeś jakiś tam framework i że np. plik konfiguracyjny jest w złym miejscu? Do takich rzeczy IMHO nadają się tylko testy integracyjne / funkcjonalne.
Wniosek: w dużym systemie zwykle będziesz potrzebował obu rodzajów testów. Jeśli system ma charakter integracyjny, to zapewne będziesz mieć przewagę testów integracyjnych. Jeśli system ma charakter bardziej obliczeniowy, algorytmiczny (np. AI do czegoś), to wtedy pewnie będziesz mieć więcej testów jednostkowych.
Ps. Całkowicie zgadzam się z treścią podlinkowanego artykułu. Unit testy są bez sensu do testowania kodu gadającego z bazą danych.
// dopisane:
A i jeszcze jedno: klienta nie będzie obchodzić, czy Twoje oprogramowanie wywali się z powodu błędu w Twoim kodzie, błędu użycia jakiejś biblioteki, czy z powodu błędu w samym frameworku/bibliotece, którego użyłeś. Powiadacie, że popularne frameworki i biblioteki nie mają błędów? Np. ORMy? Hahahahahaha. Kiedy robiłem szkolenia z Hibernate, Hibernate był już jakieś 5-6 lat na rynku (i był must-have w niemal każdym projekcie Javowym obok Springa). Miał błędy, który kończyły się zwracaniem nieprawidłowych wyników, albo potrafił generować może i poprawne semantycznie zapytania, ale zabijające bazę np. złączeniem kartezjańskim. Powodzenia w wyłapywaniu takich rzeczy unit-testami.
Dlatego w systemie musisz mieć jakieś testy, który testują wszystkie warstwy, łącznie z bibliotekami i frameworkami. Wiem, że to kosztuje niemało, ale z tego akurat właśnie nie można zrezygnować bez ryzykowania jakością produktu. Z unit-testów za to można zrezygnować, bo integracyjne i tak powinny wszystko wyłapać. Jedyna wada posiadania tylko integracyjnych/funkcjonalnych testów jest taka, że pewne błędy unit-testy mogą wyłapać i zlokalizować znacznie wcześniej i szybciej, bo szybciej się wykonują (a zatem można je uruchamiać częściej) i mają mniejszy zasięg.