Итак первый кейс - обработка исключительный ситуаций. Читаем дальше...
К примеру метод должен валидировать входной параметр каким-то образом. Допустим ты уже постарался и у тебя уже есть набор тестов, проверяющий как работает тестируемый метод на идеальных данных, а теперь ты хочешь проверить несколько крайних условий. Допустим параметр этот - это Integer и он должен быть больше нуля ну естественно не null. Для каждого из этих случаев ты напишешь тест, который будет проверять наличие эксцепшена. Расскажу как я съел собаку на подобных проверках.
Самый простой способ - проверить что эксцепшен в принципе возник. Сделаем это:
public void testCheckIfLessThen0() {
Integer someParam = -1; // бага тут
try {
object.someMethod(someParam); // тестим это
} catch (Exception e) {
}
}
Скажу сразу этот тест будет зеленым даже если не возникло никакой исключительной ситуации. Это первая и часто распространенная ошибка. Чтобы ее исправить добавим одну строчку.public void testCheckIfLessThen0() {
Integer someParam = -1; // бага тут
try {
object.someMethod(someParam); // тестим это
fail("Exception expected");
} catch (Exception e) {
}
}
Так лучше. Но когда мы напишем второй тестовый случай.public void testCheckIfNull() {
Integer someParam = null; // бага тут
try {
object.someMethod(someParam); // тестим это
fail("Exception expected");
} catch (Exception e) {
}
}
То поймем (ибо тест новый сразу будет зеленый), что хорошо бы нам как-то конкретизировать какая именно ошибка возникла. Кстати, по этой причине мне не нравится подход Junit 4 к ловле exception с помощью аргумента expected аннотации @Test - не доработали ребята. В общем мы сделаем так
public void testCheckIfNull() {
Integer someParam = null; // бага тут
try {
object.someMethod(someParam); // тестим это
fail("Exception expected");
} catch (Exception exception) {
assertEquals("Мы ожидали другое.", "Аргумент не может быть null", exception.getMessage());
}
}
И то же для другого теста. public void testCheckIfLessThen0() {
Integer someParam = -1; // бага тут
try {
object.someMethod(someParam); // тестим это
fail("Exception expected");
} catch (Exception exception) {
assertEquals("Мы ожидали другое.", "Аргумент не может быть меньше нуля", exception.getMessage());
}
}
Хорошо то как! Но это довольно примитивный случай, и скорее всего сообщение об ошибке будет находиться где-то очень глубоко. К примеру все могло быть такpublic void testCheckIfLessThen0() {
Integer someParam = -1; // бага тут
try {
object.someMethod(someParam); // тестим это
fail("Exception expected");
} catch (Exception exception) {
EJBException ejbException = (EJBException)exception;
Bla1xception blaException = (BlaException)ejbException.getParent();
SqlException sqlException = blaException.getSqlException();
assertEquals("Мы ожидали другое.", "База говорит, что аргумент не может быть меньше нуля", sqlException.getMessage());
}
}
Но тут есть проблема - мы использовали множество class cast при этом не проверяя предварительно типы, и если возникнет какая-то другая исключительная ситуация, то мы увидим следствие этого провтыка а не причину слетевшего теста. Я, а может и ты тоже - пошел в свое время очевидным путем - перед каждым кейсом делал дополнительно assertEquals по классу. Вот так
public void testCheckIfLessThen0() {
Integer someParam = -1; // бага тут
try {
object.someMethod(someParam); // тестим это
fail("Exception expected");
} catch (Exception exception) {
assertEquals("Чето не тот exception пришел", EJBException.class, exception.getClass());
EJBException ejbException = (EJBException)exception;
assertEquals("Чето не тот exception пришел", Bla1xception.class, ejbException.getClass());
Bla1xception blaException = (BlaException)ejbException.getParent();
assertEquals("Чето не тот exception пришел", SqlException.class, blaException.getClass());
SqlException sqlException = blaException.getSqlException();
assertEquals("Мы ожидали другое.", "База говорит, что аргумент не может быть меньше нуля", sqlException.getMessage());
}
}
Ну и ясное дело, раз уж это будет повторяться не один раз - используем мною любимый ExtractMethod. public void testCheckIfLessThen0() {
Integer someParam = -1; // бага тут
try {
object.someMethod(someParam); // тестим это
fail("Exception expected");
} catch (Exception exception) {
assertSQLException(exception, "База говорит,что аргумент не может быть меньше нуля")
}
}
public static assertSQLException(Exception actualException, String expectedMessage) {
assertEquals("Чето не тот exception пришел", EJBException.class, actualException.getClass());
EJBException ejbException = (EJBException)actualException;
assertEquals("Чето не тот exception пришел", Bla1xception.class, ejbException.getClass());
Bla1xception blaException = (BlaException)ejbException.getParent();
assertEquals("Чето не тот exception пришел", SqlException.class, blaException.getClass());
SqlException sqlException = blaException.getSqlException();
assertEquals("Мы ожидали другое.", expectedMessage, sqlException.getMessage());
}
Долго я пользовался этим подходом, пока не заметил одну закономерность - иногда раз, когда слетал подобный assert мне приходилось лезть в дебаггер и изучать что на самом деле слетело. Я делал большую ошибку, согласен - я отлаживал тест. Но это было не так часто и для разнообразия я себе это позволял. Задумался об этом тогда, когда определенная задача заставляла сделать подобный дебаг несколько раз подряд причем в сложно запутанном коде (где нельзя было сказать сразу какая его часть вызвала исключительную ситуацию). И тут пришла идея. Если мне нужна информация про exception, отличный от ожидаемого в полной мере, то почему бы мне не прокидывать его? Так и сделал.
public static assertSQLException(Exception actualException, String expectedMessage) {
try {
assertEquals("Чето не тот exception пришел", EJBException.class, actualException.getClass());
EJBException ejbException = (EJBException)actualException;
assertEquals("Чето не тот exception пришел", Bla1xception.class, ejbException.getClass());
Bla1xception blaException = (BlaException)ejbException.getParent();
assertEquals("Чето не тот exception пришел", SqlException.class, blaException.getClass());
SqlException sqlException = blaException.getSqlException();
assertEquals("Мы ожидали другое.", expectedMessage, sqlException.getMessage());
} catch (AssertionError e) {
throw actualException;
}
}
Ну вот как бы и все. Чуть позже расскажу об тихих ассертах...

"Кстати, по этой причине мне не нравится подход Junit 4 к ловле exception с помощью аргумента expected аннотации @Test - не доработали ребята. "
ОтветитьУдалитьВроде в последних JUnit (~4.8) подход к ловле иксепшнов гораздо более продвинутый. Попробуй!
Спасибо опробую...
ОтветитьУдалитьПопробуйте вот такой подход http://weblogs.java.net/blog/johnsmart/archive/2009/09/27/testing-exceptions-junit-47
ОтветитьУдалитьДа да, уже опробовал эту фичу. Спасибо!
ОтветитьУдалить