Testowanie wyjątków w prywatnych metodach
Chyba najbardziej porąbana rzecz pod słońcem, ale mi się nudzi po mocach, więc płodzę 😉
Na początek zdefiniujmy sobie problem. Mamy prywatną klasę, która zwraca IOException i chcemy ją przetestować czy na pewno robi to jak bozia przykazała.
Listing 1. Kandydatka do testów
private String getAsString(InputStream inputStream)
throws UnsupportedEncodingException, IOException {
final StringBuilder outputBuilder = new StringBuilder();
try {
String string;
if (inputStream != null) {
final BufferedReader reader = new BufferedReader(
new InputStreamReader(inputStream, ENCODING));
while (null != (string = reader.readLine()))
outputBuilder.append(string).append('\n');
}
} catch (IOException e) {
throw new IOException(
"[ERROR] I\\O Error reading translation stream.", e);
}
return outputBuilder.toString();
}
Test bezsensowny, ale prikaz 100% w Cobertura musi być na zielono. Więc kombinuję. Po pierwsze trzeba metodę w teście uczynić publiczną, a po drugie należy napisać InputStream, który na pewno się wywali. Zresztą nie taki bezsensowny, bo chcemy sprawdzić czy w rzeczywistej aplikacji jeżeli pojawi się problem IO to zostanie on zgłoszony (jest to pewne, ale strzyżonego pan buk strzyże). Pokombinowałem i wymyśliłem coś takiego:
Listing 1. Test, że aż strach się bać
@Test(expected = IOException.class)
public void getAsStringIOExceptionTest() throws Throwable {
Method getAsString = TranslatorImpl.class.getDeclaredMethod(
"getAsString", InputStream.class);
getAsString.setAccessible(true);
try {
Object o = (getAsString.invoke(translator,
new InputStream() {
@Override
public int read() throws IOException {
throw new IOException();
}
}));
} catch (InvocationTargetException e) {
throw (e.getCause());
}
}
Dlaczego ten jest taki dziwny? Na początku ustawiam publiczny dostęp do metody. Następnie karmię ją własną implementacją InputStream, która zawsze zwraca wyjątek IOException. I teraz najważniejsze. Używając refleksji jeżeli pojawi się wyjątek to zostanie zgłoszony nieweryfikowalny InvocationTargetException, którego przyczyną będzie nasz wyjątek. Dlatego należy wyłapać wyjątek z refleksji i zgłosić wyjątek, który go spowodował.
Cel
Po co to wszystko? Testowanie takiego kodu w ten sposób wydaje się być dość mocno hazardowe. Ilość błędów, które można popełnić w trakcie pisania testów jest dość duża. Jednocześnie nie ma dużo sytuacji w których test taki okazał by się przydatny.
No nie do końca…
Przedstawiłem kod, którego główną wadą jest brak publicznej metody. Można zatem spokojnie pominąć tematykę związaną z uzyskiwaniem dostępu do metody prywatnej i w ten sposób otrzymamy bardzo prosty test. Błąd przy użyciu refleksji wyjdzie od razu gdy odpalimy test. Położy się on z czymś nieoczekiwanym. W praktyce otrzymałem coś stosunkowo prostego do przetestowania co trzeba przetestować, ale obudowanego skomplikowanym problemem dostępu.
Czego się dziś nauczyliśmy? Ano tego, że ilość rzeczy, których nie można przetestować jest naprawdę mała.