@szmeterling:
Wlasnie zastanawiam sie nad czyms podobnym. Skoro:
Gdyby testy były dobre, to nie mógłbym usunąć ani jednej linijki, tak żeby testy zaczęły failować. Mógłbym jedynie refaktorować (np if na ?: jak zauważyłeś).
To przeciez w takiej sytuacji mamy kod zabetonowy testami na miare mockow. Bardziej mnie interesuje co ma sie wydarzyc, a nie jak.
Wysuwasz bardzo dobry temat: jak zapewnić, żeby dodanie buga (nawet najmniejszego) failowało testy, ale jednocześnie żeby wprowadzanie zmian w projekcie było bardzo szybkie oraz łatwe?
Testy są po to żeby wykrywać bugi. Dajmy przykład że wchodzę do projektu, i usuwam linijkę. Co się wtedy dzieje?
- Jeśli aplikacja nie działa tak jak ma działać, to ewidentnie to jest bug. A skoro tak, to jakiś test powinien się wywalić. I mam na myśli tutaj dowolną linijkę w kodzie.
- Po usunięciu linijki aplikacja nadal działa tak jak ma działać i nic się nie zepsuło, wszystko jest nadal tak jak ma być? Wtedy to nie jest bug, żaden test nie powinien sfailić, i widocznie linijka była niepotrzebna. Należy ją wywalić.
Ale z drugiej strony, wiele razy widzieliśmy takie testy, że chcemy zmienić nazwę funkcji, albo przenieść ją do innej klasy, i nie udało się to zrobić łatwo, bo wtedy były tak przyspawane do kodu, że malutka, nawet drobna zmiana powodowała fail masy testów. Coś co nazwałeś "kod zabetonowany testami".
Jak więc to pogodzić?
Trzeba być sprytnym.. Trzeba napisać testy które są czułe na zachowanie (jakie wartości są zwracane, jakie side effecty, jakie wartości parametrów, ile wywołań, jaki wyjątek), a jednocześnie całkowicie nieczułe na szczegóły (konkretna klasa, konkretna funkcja, konkretny moduł). To jak napisać takie testy to jest temat lat praktyk, czytania, szukania, doszkalania. Nie jest to prosta sprawa. To jest do zrobienia, tylko po prostu nie jest proste. (Dodatkowa miła konsekwencja jest taka, że jeśli mamy testy napisane w taki sposób, to one wymuszają loose-coupling. A skoro tak, to nasz kod naturalnie sam z siebie też jest wtedy loosly-coupled).
W ten sposób zapewnimy sobie testy które wykrywają bugi, a jednocześnie nie przeszkadzają w normalnej pracy.
- Jeśli przegniesz w jedną stronę, to masz useless testy które nic nie testują.
- Jeśli przegniesz w drugą, to masz zabetonowany kod i testy które przeszkadzają w pracy
Oczywiście, pewien poziom zabetonowania jest spodziewany - już wyjaśniam dlaczego. Czasem chcemy specjalnie dodać buga. A mówiąc dokładniej, chcemy dodać zmianę która jeszcze wczoraj byłaby bugiem, ale dzisiaj jest normalna (np wczoraj mieliśmy walidację np na wiek, nazwę, maila), a dzisiaj chcemy dodać zmianę która usuwa tą walidację. Więc oczywiście że jeśli ją usuniemy, to test sfailuje i to jest w porządku. Po prostu wtedy poprawiamy albo usuwamy taki test i jedziemy. Nie zawsze fail testu to jest coś złego, w tym wypadku to jest spodziewane. Oczywiście miejsc gdzie jest taki fail powinno być mało, żeby wprowadzenie takiej zmiany było łatwe.
Tutaj mamy kolejny problem: jak pogodzić ilośc testów. Z jednej strony powinno być testów na tyle dużo, żeby pokryć wszystkie zachowania i cechy aplikacji, ale z drugiej strony powinny być na tyle dobrze napisane, że wystąpienie jakiegoś buga nie powoduje nagle 100 faili, tylko kilka konkretnych testów które łatwo poprawić. Pisanie takich testów wymaga rozwagi i doświadczenia. Nie można ich pisać na pałę. To jest kolejny powód czemu testy pisać warto najpierw, kiedy jeszcze mamy zasoby mentale na to.