Если нельзя, но очень хочется, то нужно обязательно и ничего в мире не стоит того, чтобы делать из этого проблему!


Интересна Java? Кликай по ссылке и изучай!
Если тебе полезно что-то из того, чем я делюсь в своем блоге - можешь поделиться своими деньгами со мной.
с пожеланием
столько времени читатели провели на блоге - 
сейчас онлайн - 

пятница, 10 сентября 2010 г.

JUnit хитрости: Объединяем stack traces

Junit несовершенен. Тот кто его использует каждый день, знает все его боки. Вот так, с мыслью "а как мне это исправить?" рождаются всевозможные идеи-экстэншены, одним из которых я сейчас поделюсь. Не так давно я писал в рубрике JUnit хитрости про Tихие asserts. Идея была в том, чтобы собрать все ошибки воедино. Так мы повысили информативность пользовательских ассертов.

В Junit runner для Ecpipse есть одна очень удобная фича. На View JUnit есть область Failure Trace. В ней отображается информация о stack trace в случае падения теста (1). Если там кликнуть дважды по какой-то строчке (2), то Eclipse отыщет файл и установит курсор в заданной строке (3).



Очень удобно! Но... Читать дальше...
Но в случае использования тихих asserts такая возможность пропадает - там в Failure Trace выведется путь к методу public static void throwAllAssertionErrors(AssertionError... errors), который и генерирует исключение.

А что если я напишу свое исключение (Exception), которое будет давать возможность добавлять не только разные сообщения но и stack traces? Сказано сделано. Покопавшись немного в дебрях класса Throwable, я написал свою реализацию, которая реализует необходимое мне свойство.
package test;

import java.io.PrintWriter;

public class CombinedAssertionError extends AssertionError {

    private Throwable[] throwables;

    public CombinedAssertionError(String message, Throwable... throwables) {
        super(message);
        this.throwables = throwables;
    }

    public void printStackTrace(PrintWriter s) {
        synchronized (s) {
            super.printStackTrace(s);

            for (Throwable throwable : throwables) {
                if (throwable != null) {
                    printStackTraceAsCause(throwable, s);
                }
            }
        }
    }

    private void printStackTraceAsCause(Throwable cause, PrintWriter s){
        StackTraceElement[] trace = cause.getStackTrace();

        s.println("By exception: " + cause);
        for (StackTraceElement element : trace) {
            s.println("\tat " + element);
        }

        // Recurse if we have a cause
        Throwable ourCause = cause.getCause();
        if (ourCause != null) {
            printStackTraceAsCause(ourCause, s);
        }
    }
}

Вот, как это выглядит на практике. Допустим есть два тихих assert's и оба слетели в тесте (1). В данном случае CombinedAssertionError примет на вход два этих assert's и сформирует stack trace таким образом, что в нем будет 3 раздела. Первый будет сообщать о месте расположения CombinedAssertionError (2), второй - об месте расположения первого слетевшего assert (3) и третий - информацию про второй assert (4).



А вот пример для поиграться.

package test;

import test.CombinedAssertionError;
import junit.framework.TestCase;

public class MyTest extends TestCase {
    private int count = 0;

    public void test1() {
        count++;
        assertCount(0);
    }

    private void assertCount(int expected) {
        AssertionError error1 = assertQuietEquals(expected, count);

        AssertionError error2 = assertQuietEquals(expected + 1, count + 1);

        if (error1 != null && error2 != null) {
            throw new CombinedAssertionError("Bad count", error1, error2);
        }
    }

    private AssertionError assertQuietEquals(int expected, int actual) {
        try {
            assertEquals(expected, actual);
        } catch (AssertionError error) {
            return error;
        }
        return null;
    }

    public void test2(){
        count++;
        assertCount(1);
    }
}

Enjoy!

Комментариев нет:

Отправить комментарий