Как вы проводите модульное тестирование методов, которые занимают много времени?

#java #eclipse #unit-testing

#java #eclipse #модульное тестирование

Вопрос:

Я новичок в модульном тестировании. Существует метод, который выглядит как:

 public Image getImage(String url) {
    Document pageSource = fecthSource(url);
    Image myImage = parseHtmlToImage(pageSource);
    return Image;
}
  

И я пишу модульный тест:

 @test
public void getRightPicture() {
    Image img = imageFetcher.getImage("http://www.123.com");
    assertEquals(img.sourceUrl, "http://www.123.com/456.png");
    Image img = imageFetcher.getImage("http://www.abc.com");
    assertEquals(img.sourceUrl, "http://www.abc.com/def.png");
}
  

Но если для доступа к Интернету требуется много времени. Обычно я хочу использовать локальный HTML-файл для тестирования этого метода, но иногда тестирую веб-версию.

Есть предложения?

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

1. Я знаю, что могу передать локальный URL в качестве параметра getImage, но это особый случай. Например, возможно, мне нужно использовать предварительно рассчитанные данные вместо медленного алгоритма для тестирования.

Ответ №1:

Вашему модульному тестированию не нужно проверять, работает ли Интернет. Вы должны предоставить несколько макетных HTML-файлов и указать на них для теста. Поскольку вы имеете дело с URL-адресом, вы должны иметь возможность указывать путь, подобный "file:///path/to/test.html" , вместо веб-URL.

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

1. Я хочу, но КАК? Это означает, что я должен сделать paresHtmlToImage () общедоступным?

2. Я обновил ответ, вы должны иметь возможность указать путь к файлу для URL.

3. Смотрите мой комментарий к OP. Я использую пример URL для удобства

Ответ №2:

Краткий ответ: инверсия зависимостей.

Несколько более длинный ответ:

Проблема, по-видимому, связана с тем, что делает fetchSource; без просмотра подробностей этот ответ должен быть немного расплывчатым. Я предполагаю, что fetchSource вызывает что-то, чтобы фактически выполнить извлечение, и что детали того, как это делается, на самом деле не то, что вас интересует в тестировании. Я также предполагаю, что вы имели в виду писать «fetch» везде, где вы пишете «fecht», но это не особенно важно для ответа.

Вы хотите абстрагироваться от конкретных фрагментов fetchSource, чтобы вы могли сосредоточиться на тестировании того, что для вас важно (parseHtmlToImage?). Есть несколько способов, которыми вы можете это сделать:

  • Создайте свой ImageFetcher с заглушкой реализации интерфейса, через который вы фактически извлекаете URL, передавая его через конструктор (инвертируя зависимость); ваша заглушка играет роль «Интернета» и возвращает готовый ответ.
  • Если вы не можете создать промежуточную реализацию интерфейса, представьте интерфейс адаптера, где у вас есть производственная реализация, которая использует текущий интерфейс, который вы используете для извлечения, и промежуточную реализацию, которую вы используете для своих тестов.

Это довольно стандартная техника написания тестируемого кода (посмотрите принцип инверсии зависимостей).

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

1. > передаете это через конструктор, что это «это»? URL-адрес? Объект реализует метод заглушки? (можете ли вы привести мне небольшой пример кода? Я прочитал DIP в wiki, но не знаю, как использовать его в этом случае)

2. Извините за задержку; все еще не привык к пользовательскому интерфейсу StackExchange для отслеживания новых комментариев. «Это» в данном случае было бы производственной реализацией интерфейса в производственном коде и заглушающей реализацией интерфейса в тестовом коде (интерфейс в данном случае абстрагируется от деталей фактического получения URI).

Ответ №3:

Здесь есть два варианта —

  1. Локальный файл, безусловно, является более быстрым способом выполнения задач; кроме того, что вы могли бы сделать, это создать макет объекта (см. Mockito или Easymock для облегчения редактирования), чтобы он мог имитировать метод fetchURL в классе, который вы пытаетесь протестировать;

  2. В других случаях, когда вы хотите получить файл из Интернета — на этом этапе это становится больше, чем модульное тестирование, поскольку вы удаляете внешние тестовые зависимости. В таких случаях вам приходится иметь дело с побочными эффектами, а именно с задержкой в сети, доступностью ресурсов и т.д. Одной из оптимизаций для этого интеграционного тестирования было бы кэширование ресурса, если от него зависит несколько тестов. Таким образом, вам не нужно каждый раз выполнять выборку по сети.

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

1. Я увижу библиотеки, которые вы упомянули в 1. Но я не понимаю 2. Вы имеете в виду, что я должен протестировать локальную версию в модульном тестировании, но протестировать сетевую версию в версии интеграции?

2. Да, вы правы. Кроме того, если у вас есть несколько тестов, тестирующих метод, который имеет <code>fetchURL(…)</code> Я бы рекомендовал кэшировать результат.

Ответ №4:

Чтобы расширить приведенный выше ответ на внедрение зависимостей:

Итак, у вас есть:

 class ImageFetcher {
    public Image getImage(String url) {
       Document pageSource = fetchSource(url);
       Image myImage = parseHtmlToImage(pageSource);
       return Image;
    }
    private Document fetchSource(String url) {
        // ... Network IO etc
    }
    private Image parseHtmlToImage(Document pageSource) {
        // Parsing logic
        // Network IO
        return image;
    }
}
  

Первый вопрос: «Действительно ли все это принадлежит «ImageFetcher». IMO fetchSource этого не делает. Это (для меня) звучит так, как будто это сделал бы другой класс «Web Utils». Я бы также перенес получение изображения из ImageFetcher в класс utils.

 class ImageFetcher {
    private WebUtils webUtils; 
    public ImageFetcher(webUtils webUtils) {
      this.webUtils = webUtils;
    }


    public Image getImage(String url) {
       Document pageSource = webUtils.fetchSource(url);
       Image myImage = parseHtmlToImage(pageSource);
       return Image;
    }

    private Image parseHtmlToImage(Document pageSource) {
        imageURL = // Parsing logic
        image = webUtils.fetchImage(imageURL);
        return image;
    }
}
  

Теперь главная задача программы выборки изображений — определить, какое изображение использовать. Ему больше не нужно заботиться о том, как вы получаете это изображение из сети. WebUtils можно использовать для модульных тестов.

 @test
public void getImage_ReturnsBannerImage() {
     Image expected = // ...


     String html = "<html><img src="fred"/>...</html>"; // Keep this short and simple.
     mockedWebUtil.fetchSource(Any.String).returns(html);
     mockedWebUtil.fetchImage("fred").returns(expected);

     var subject = new ImageFetcher(mockedWebUtil);
     var actual = subject.getImage("blah");

     Assert.AreEqual(expected, actual);
}