Как выполнить модульный тест доступа к файлам (Java)? - PullRequest
6 голосов
/ 22 февраля 2011

Я знаю, что хороший юнит-тест никогда не должен обращаться к файловой системе. Так что я также знаю, что вы можете использовать Mockito и PowerMock, например, для макетирования класса File.

А как же следующий код:

public ClassLoaderProductDataProvider(ClassLoader classLoader, String tocResourcePath, boolean checkTocModifications) {
    // ...
    this.cl = classLoader;
    tocUrl = cl.getResource(tocResourcePath);
    if (tocUrl == null) {
        throw new IllegalArgumentException("Can' find table of contents file " + tocResourcePath);
    }
    this.checkTocModifications = checkTocModifications;
    toc = loadToc();
    // ...
}

private ReadonlyTableOfContents loadToc() {
    InputStream is = null;
    Document doc;
    try {
        is = tocUrl.openStream();
        doc = getDocumentBuilder().parse(is);
    } catch (Exception e) {
        throw new RuntimeException("Error loading table of contents from " + tocUrl.getFile(), e);
    } finally {
        if (is != null) {
            try {
                is.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
    try {
        Element tocElement = doc.getDocumentElement();
        ReadonlyTableOfContents toc = new ReadonlyTableOfContents();
        toc.initFromXml(tocElement);
        return toc;
    } catch (Exception e) {
        throw new RuntimeException("Error creating toc from xml.", e);
    }
}

Этот класс инициализирует свой атрибут toc содержимым файла, расположенного в tocResource.

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

Однако тогда код конструктора исходного класса вообще не тестируется. Что делать, если есть ошибка?

Ответы [ 5 ]

9 голосов
/ 22 февраля 2011

Вот в чем дело: обычно для правильной работы модульного тестирования необходимо предоставить своим классам интерфейсы , а не конкретные классы, чтобы вы могли гибко выполнять разные задачи для тестирования.Глядя на ваш пример, мне кажется, что вы должны взять на себя ответственность за загрузку Document в какой-то другой класс ... с помощью интерфейса с именем DocumentSource, скажем.

Тогда ваш код здесь не будетне зависит от файловой системы вообще.Это может выглядеть примерно так:

public SomethingProductDataProvider(DocumentSource source, String tocDocumentName,
                                    boolean checkTocModifications) {
  this.source = source;
  this.tocDocumentName = tocDocumentName;
  this.checkTocModifications = checkTocModifications;
  this.toc = loadToc();
}

private ReadonlyTableOfContents loadToc() {
  Document doc = source.getDocument(tocDocumentName);
  if (doc == null) {
    throw new IllegalArgumentException("Can' find table of contents file " + 
        tocResourcePath);
  }

  try {
    Element tocElement = doc.getDocumentElement();
    ReadonlyTableOfContents toc = new ReadonlyTableOfContents();
    toc.initFromXml(tocElement);
    return toc;
  } catch (Exception e) {
    throw new RuntimeException("Error creating toc from xml.", e);
  }
}

В качестве альтернативы, вы можете получить класс, взяв Document или даже InputStream непосредственно в своем конструкторе.Конечно, в какой-то момент вам нужен реальный код, который загружает InputStream из ресурса, используя ClassLoader ... но вы можете вставить этот код во что-то простое, что только делает это.Тогда становится ясно, что при любом тестировании того, что класс должен использовать реальный файл ... но на тестирование других классов это не влияет.

Как примечание, это плохой признакдля тестируемости класса, если он работает (например, загрузка оглавления в данном случае) в его конструкторе.Вероятно, существует гораздо лучший способ разработки классов, задействованных здесь, который устраняет необходимость в этом и является более тестируемым, но трудно точно сказать, что именно этот дизайн дает именно этому.

Существуют различные другие вариантычто вы могли бы сделать, в том числе использовать что-то вроде интерфейса InputSupplier в Guava в сочетании с уже проверенным заводским методом, таким как Resources.newInputStreamSupplier (URL) для получения экземпляра InputSupplierдля использования в производстве.Главное, чтобы ваши классы всегда зависели от интерфейсов, чтобы вы могли легко использовать альтернативные реализации в тестировании.

4 голосов
/ 22 февраля 2011

Доступ к файловой системе вполне приемлем для модульных тестов. Фактически, довольно часто иметь целый набор файлов, которые вы используете в качестве приспособлений для тестируемой системы. Это позволяет легко добавлять новые тесты, потому что вам не нужно добавлять новый код, только данные.

3 голосов
/ 22 февраля 2011

Откуда вы взяли, что «хороший» модульный тест не должен обращаться к файловой системе?В этом нет ничего плохого, если тест воспроизводим в нескольких средах.Таким образом, в этом случае это означает, что вы создаете статический файл в тестовом пути к классам и передаете путь этого файла в конструктор ClassLoaderProductDataProvider.Не нужно усложнять.

2 голосов
/ 22 февраля 2011

Модульное тестирование - это всего лишь один из инструментов в вашем наборе инструментов для тестирования.Но, как и все инструменты, он имеет целевое назначение и ограниченную сферу применения. Искусство юнит-тестирования Роя Ошерова объясняет, что юнит-тесты плохо подходят, когда задействованы внешние зависимости.Это происходит по причинам, которые были изложены в этом вопросе: ускорение выполнения теста, повторное выполнение тестов в среде разработчика, устранение ложных сбоев тестирования и т. Д.

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

2 голосов
/ 22 февраля 2011

Вы можете передать пользовательский ClassLoader, который предоставляет тестовый экземпляр tocUrl при вызове.Однако зачем вообще переходить в загрузчик классов?Если все, что вы используете, это tocUrl, просто передайте это вместо ClassLoader и заглушите это.Это сильно упрощает вещи.

public ClassLoaderProductDataProvider(ClassOfToUrl tocUrl, String tocResourcePath, boolean checkTocModifications) {
// ...
this.tocUrl = tocUrl;

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...