Итак первый кейс - обработка исключительный ситуаций. Читаем дальше...
К примеру метод должен валидировать входной параметр каким-то образом. Допустим ты уже постарался и у тебя уже есть набор тестов, проверяющий как работает тестируемый метод на идеальных данных, а теперь ты хочешь проверить несколько крайних условий. Допустим параметр этот - это 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
ОтветитьУдалитьДа да, уже опробовал эту фичу. Спасибо!
ОтветитьУдалить