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.

Napisz odpowiedź

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax