Подтвердить что ты не робот

Как сделать unit test для исключений?

Как вы знаете, исключение выбрасывается при условии ненормальных сценариев. Итак, как аналог этих исключений? Я чувствую, что это вызов. Для таких фрагментов кода:

public String getServerName() {
    try {

        InetAddress addr = InetAddress.getLocalHost();
        String hostname = addr.getHostName();
        return hostname;
    }
    catch (Exception e) {
        e.printStackTrace();
        return "";
    }
}

Есть ли у кого-нибудь хорошие идеи?

4b9b3361

Ответ 1

В других ответах была рассмотрена общая проблема написания unit test, которая проверяет, что генерируется исключение. Но я думаю, что ваш вопрос действительно спрашивает о том, как заставить код генерировать исключение в первую очередь.

Возьмите свой код в качестве примера. Было бы очень сложно заставить ваш getServerName() внутренне генерировать исключение в контексте простого unit test. Проблема заключается в том, что для того, чтобы исключение произошло, код (обычно) должен выполняться на машине, чья сеть нарушена. Упорядочить, что это произойдет в unit test, вероятно, невозможно... вам нужно было бы неправильно настроить машину перед запуском теста.

Так в чем же ответ?

  • В некоторых случаях простой ответ заключается только в том, чтобы принять прагматическое решение, а не идти на полное покрытие тестов. Хороший пример - ваш метод. Из проверки кода должно быть ясно, что на самом деле делает этот метод. Тестирование это ничего не докажет (кроме ниже **). Все, что вы делаете, - это улучшить количество проверок и номера тестовых покрытий, ни одна из которых не должна быть целью проекта.

  • В других случаях может быть разумным разделить низкоуровневый код, в котором генерируется исключение, и сделать его отдельным классом. Затем, чтобы проверить обработку кода более высокого уровня для исключения, вы можете заменить класс макетным классом, который выведет нужные исключения.

Вот ваш пример с учетом этого "лечения". (Это немного надуманно...)

public interface ILocalDetails {
    InetAddress getLocalHost() throws UnknownHostException;
    ...
}

public class LocalDetails implements ILocalDetails {
    public InetAddress getLocalHost() throws UnknownHostException {
        return InetAddress.getLocalHost();
    }
}

public class SomeClass {
    private ILocalDetails local = new LocalDetails();  // or something ...
    ...
    public String getServerName() {
        try {
            InetAddress addr = local.getLocalHost();
            return addr.getHostName();
        }
        catch (Exception e) {
            e.printStackTrace();
            return "";
        }
    }
}

Теперь до unit test вы создаете "макет" реализации интерфейса ILocalDetails, метод getLocalHost() выдает исключение, которое вы хотите в соответствующих условиях. Затем вы создаете единичный текст для SomeClass.getServerName(), устанавливая, что экземпляр SomeClass использует экземпляр вашего класса "mock" вместо обычного. (Последний бит может быть выполнен с использованием фальшивой структуры, выставляя setter для атрибута local или используя API отражения.)

Очевидно, что вам нужно будет изменить свой код, чтобы он выглядел таким образом. И есть ограничения на то, что вы можете сделать... например, теперь вы не можете создать unit test, чтобы сделать реальный метод LocalDetails.getLocalHost() для исключения. Вы должны принять индивидуальное решение относительно того, стоит ли это делать; то есть преимущество unit test перевешивает работу (и дополнительную сложность кода), позволяя таким образом тестировать класс. (Тот факт, что в конце этого есть метод static, является большой частью проблемы.)

** Существует гипотетический момент для такого тестирования. В вашем примере тот факт, что исходный код ловит исключение и возвращает пустую строку, может быть ошибкой... в зависимости от того, как специфицирован API-метод метода... и гипотетический unit test подхватил бы его. Однако в этом случае ошибка настолько вопиющая, что вы заметили ее при написании unit test! Предполагая, что вы исправляете ошибки по мере их нахождения, unit test становится несколько избыточным. (Вы не ожидали, что кто-то повторит эту ошибку...)

Ответ 2

Вы можете сказать junit, что правильное поведение - это получить исключение.

В JUnit 4 это выглядит примерно так:

@Test(expected = MyExceptionClass.class) 
public void functionUnderTest() {
    …
}

Ответ 3

Хорошо, здесь есть несколько возможных ответов.

Тестирование самого исключения легко

import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;

@Test
public void TestForException() {
    try {
        doSomething();
        fail();
    } catch (Exception e) {
        assertThat(e.getMessage(), is("Something bad happened"));
    }
}

В качестве альтернативы вы можете использовать аннотацию исключения, чтобы отметить, что вы ожидаете, что исключение выйдет.

Теперь, что касается вашего конкретного примера, проверка того, что что-то, что вы создаете внутри своего метода, либо с помощью нового, либо с помощью статического действия, когда вы не имеете возможности взаимодействовать с объектом, является сложной задачей. Обычно вам нужно инкапсулировать этот конкретный генератор, а затем использовать некоторые издевательства, чтобы переопределить поведение для генерации ожидаемого вами исключения.

Ответ 4

Многие структурные модули тестирования позволяют вашим испытаниям ожидать исключений как часть теста. JUnit, например, позволяет это.

@Test (expected=IndexOutOfBoundsException.class) public void elementAt() {
    int[] intArray = new int[10];

    int i = intArray[20]; // Should throw IndexOutOfBoundsException
}

Ответ 5

Поскольку этот вопрос находится в вики сообщества, я добавлю новый для полноты: Вы можете использовать ExpectedException в JUnit 4

@Rule
public ExpectedException thrown= ExpectedException.none();

@Test
public void TestForException(){
    thrown.expect(SomeException.class);
    DoSomething();
}

ExpectedException делает брошенное исключение доступным для всех методов тестирования.

Можно также проверить конкретное сообщение об ошибке:

thrown.expectMessage("Error string");

или использовать матчи

thrown.expectMessage(startsWith("Specific start"));

Это короче и удобнее, чем

public void TestForException(){
    try{
        DoSomething();
        Fail();
    }catch(Exception e) {
      Assert.That(e.msg, Is("Bad thing happened"))
    }
}

потому что если вы забудете провал, тест может привести к ложному отрицанию.