Czy ma sens mierzenie zasięgu warunkowego dla kodu Java 8?

19

Zastanawiam się, czy pomiar pokrycia kodu warunkowego przez obecne narzędzia dla Java nie jest przestarzały od czasu pojawienia się Java 8. Z Java 8 użytkownika Optionali Streamczęsto możemy uniknąć kodu rozgałęzień / pętle, co sprawia, że łatwo dostać bardzo wysoki zasięg bez warunkowego testowanie wszystkich możliwych ścieżek wykonania. Porównajmy stary kod Java z kodem Java 8:

Przed Javą 8:

public String getName(User user) {
    if (user != null) {
        if (user.getName() != null) {
            return user.getName();
        }
    }
    return "unknown";
}

W powyższej metodzie istnieją 3 możliwe ścieżki wykonania. Aby uzyskać 100% zasięgu warunkowego, musimy utworzyć 3 testy jednostkowe.

Java 8:

public String getName(User user) {
    return Optional.ofNullable(user)
                   .map(User::getName)
                   .orElse("unknown");
}

W tym przypadku gałęzie są ukryte i potrzebujemy tylko 1 testu, aby uzyskać 100% pokrycia i nie ma znaczenia, który przypadek przetestujemy. Chociaż nadal istnieją te same 3 logiczne gałęzie, które należy uwzględnić, wierzę. Myślę, że sprawia to, że statystyki pokrycia warunkowego są obecnie całkowicie niezaufane.

Czy ma sens mierzenie zasięgu warunkowego dla kodu Java 8? Czy są jakieś inne narzędzia wykrywające kod, który został sprawdzony?

Karol Lewandowski
źródło
5
Miary zasięgu nigdy nie były dobrym sposobem na sprawdzenie, czy kod jest dobrze przetestowany, a jedynie sposobem na określenie, co nie zostało przetestowane. Dobry programista przemyśli różne przypadki w jej umyśle i opracuje testy na wszystkie z nich - a przynajmniej na wszystko, co uważa za ważne.
kdgregory
3
Oczywiście wysoki zasięg warunkowy nie oznacza, że ​​mamy dobre testy, ale myślę, że ogromną zaletą jest wiedzieć, które ścieżki wykonania są odkryte i na tym właśnie polega pytanie. Bez uwzględnienia warunkowego znacznie trudniej jest dostrzec niesprawdzone scenariusze. Odnośnie ścieżek: [użytkownik: null], [użytkownik: notnull, nazwa.użytkownika: null], [użytkownik: notnull, nazwa.użytkownika: notnull]. Czego mi brakuje?
Karol Lewandowski,
6
Jaka jest umowa getName? Wydaje się, że jeśli userjest zerowy, powinien zwrócić „nieznane”. Jeśli usernie jest zerowy i user.getName()jest zerowy, powinien zwrócić „nieznany”. Jeśli usernie jest zerowe i user.getName()nie jest zerowe, powinno to zwrócić. Więc przetestowałbyś te trzy przypadki, ponieważ o to właśnie chodzi w umowie getName. Wygląda na to, że robisz to wstecz. Nie chcesz widzieć gałęzi i pisać testów zgodnie z nimi, chcesz pisać testy zgodnie z umową i upewnić się, że umowa jest wypełniona. To wtedy masz dobry zasięg.
Vincent Savard,
1
Znowu nie mówię, że zasięg dowodzi, że mój kod jest doskonale przetestowany, ale było to NIEZWYKLE cenne narzędzie pokazujące mi to, czego na pewno nie przetestowałem. Myślę, że testowanie kontraktów jest nierozerwalnie związane z testowaniem ścieżek wykonania (twój przykład jest wyjątkowy, ponieważ obejmuje ukryty mechanizm językowy). Jeśli nie przetestowałeś ścieżki, oznacza to, że nie w pełni przetestowałeś umowę lub umowa nie jest w pełni zdefiniowana.
Karol Lewandowski,
2
Powtórzę mój wcześniejszy punkt: tak jest zawsze , chyba że ograniczysz się tylko do podstawowych funkcji językowych i nigdy nie wywołasz żadnej funkcji, która nie została instrumentowana. Co oznacza, że ​​nie ma bibliotek stron trzecich i nie używa zestawu SDK.
kdgregory

Odpowiedzi:

4

Czy są jakieś narzędzia do pomiaru gałęzi logicznych, które można utworzyć w Javie 8?

Nic mi nie wiadomo. Próbowałem uruchomić kod, który masz przez JaCoCo (aka EclEmma) dla pewności, ale pokazuje 0 gałęzi w Optionalwersji. Nie znam żadnej metody jego skonfigurowania, aby powiedzieć inaczej. Jeśli skonfigurowałeś go tak, aby zawierał również pliki JDK, teoretycznie pokazywałby rozgałęzienia Optional, ale myślę, że głupio byłoby zacząć weryfikować kod JDK. Musisz tylko założyć, że to prawda.

Myślę jednak, że zasadniczą kwestią jest uświadomienie sobie, że dodatkowe gałęzie, które posiadałeś przed Javą 8, były w pewnym sensie sztucznie stworzonymi gałęziami. To, że nie istnieją już w Javie 8, oznacza po prostu, że masz odpowiednie narzędzie do tego zadania (w tym przypadku Optional). W kodzie wcześniejszym niż Java 8 trzeba było napisać dodatkowe testy jednostkowe, aby mieć pewność, że każda gałąź kodu zachowuje się w akceptowalny sposób - a to staje się nieco ważniejsze w częściach kodu, które nie są trywialne, jak w przypadku User/ getNameprzykład.

W kodzie Java 8 zamiast tego ufasz JDK, że kod działa poprawnie. Tak jak należy, należy traktować tę Optionallinię tak, jak traktują ją narzędzia pokrycia kodu: 3 linie z 0 gałęziami. To, że w poniższym kodzie znajdują się inne wiersze i gałęzie, jest czymś, na co wcześniej nie zwracałeś uwagi, ale istnieje za każdym razem, gdy używasz czegoś takiego jak ArrayListlub HashMap.

Shaz
źródło
2
„To już nie istnieje w Java 8 ...” - Nie mogę się z tym zgodzić, Java 8 i jest wstecznie kompatybilny ifi nullnadal są części języka ;-) To wciąż możliwe, aby napisać kod w starym stylu i przejść nullużytkownik lub użytkownik o nullnazwie. Twoje testy powinny po prostu udowodnić, że kontrakt jest spełniony, niezależnie od sposobu wdrożenia metody. Chodzi o to, że nie ma narzędzia, które powiedziałoby ci, czy w pełni przetestowałeś umowę.
Karol Lewandowski,
1
@KarolLewandowski Myślę, że Shaz mówi, że jeśli ufasz, jak działają Optional(i powiązane z nimi metody), nie musisz ich już testować. Nie w ten sam sposób, w jaki testowałeś if-else: każde ifbyło potencjalnym polem minowym. Optionali podobne funkcjonalne idiomy są już zakodowane i gwarantują, że cię nie potkniesz, więc zasadniczo istnieje „gałąź”, która zniknęła.
Andres F.
1
@AndresF. Nie sądzę, że Karol sugeruje, abyśmy przetestowali Optional. Jak powiedział, logicznie powinniśmy nadal sprawdzać, czy getName()obsługuje różne możliwe dane wejściowe w zamierzony sposób, niezależnie od jego implementacji. Trudniej to ustalić bez narzędzi pokrycia kodu, które pomogłyby w wersji sprzed JDK8.
Mike Partridge,
1
@MikePartridge Tak, ale chodzi o to, że nie odbywa się to za pośrednictwem zasięgu oddziału. Zasięg gałęzi jest potrzebny podczas pisania, if-elseponieważ każda z tych konstrukcji jest całkowicie ad-hoc. W przeciwieństwie do tego Optional, orElse, map, etc, są już przetestowane. W efekcie gałęzie „znikają”, gdy używasz silniejszych idiomów.
Andres F.,