Groovy: проверить создание заглушки URL - PullRequest
3 голосов
/ 14 июня 2009

Тестовый класс ниже проверяет, что простой HttpService получает контент с заданного URL. Обе показанные реализации делают тест пройденным, хотя одно явно неверно, потому что он создает URL с неверным аргументом.

Чтобы избежать этого и правильно указать желаемое поведение, я хотел бы проверить, что в блоке использования тестового примера я создаю один (и только один) экземпляр класса URL и что url аргумент конструктора верен. Улучшение Groovy , похоже, позволило бы мне добавить утверждение

mockURLContext.demand.URL { assertEquals "http://www.foo.com", url }

но что я могу сделать без этого улучшения Groovy?

Обновление: Заменено "mock" на "заглушка" в заголовке, так как меня интересует только проверка состояния, не обязательно детали взаимодействий. В Groovy есть механизм StubFor, который я не использовал, поэтому я оставлю свой код как есть, но я думаю, что вы можете просто заменить MockFor на StubFor.

import grails.test.*
import groovy.mock.interceptor.MockFor

class HttpServiceTests extends GrailsUnitTestCase {
    void testGetsContentForURL() {
        def content = [text : "<html><body>Hello, world</body></html>"]

        def mockURLContext = new MockFor(URL.class)
        mockURLContext.demand.getContent { content }
        mockURLContext.use {
            def httpService = new HttpService()
            assertEquals content.text, httpService.getContentFor("http://www.foo.com")
        }
    }
}    

// This is the intended implementation.
class HttpService {
    def getContentFor(url) {
        new URL(url).content.text
    }
}

// This intentionally wrong implementation also passes the test!
class HttpService {
    def getContentFor(url) {
        new URL("http://www.wrongurl.com").content.text
    }
}

Ответы [ 4 ]

3 голосов
/ 25 июня 2009

Что дает вам пародия на URL? Это затрудняет написание теста. Вы не сможете реагировать на обратную связь, которую ложные объекты сообщают вам о дизайне API класса URL, потому что он не находится под вашим контролем. И если вы точно не фальсифицируете поведение URL-адреса и то, что он показывает в отношении протокола HTTP, тест не будет надежным.

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

Для HTTP я пишу тест, который создает HTTP-сервер, подключает сервлет к серверу, который будет возвращать некоторые стандартные данные, передает URL-адрес сервлета объекту, чтобы он загрузил данные, проверяет, что загруженный результат так же, как консервированные данные, используемые для инициализации сервлета и остановки сервера в демонтаже устройства. Я использую Jetty или простой HTTP-сервер, поставляемый в комплекте с JDK 6.

Я бы использовал только фиктивные объекты для проверки поведения объектов, взаимодействующих с интерфейсом (интерфейсами) этого объекта, который я тестировал при интеграции.

2 голосов
/ 29 июня 2009

Надев мои шляпы "Программирование в малом" и "Юнит тест 100%", вы могли бы рассматривать это как единый метод, который делает слишком много вещей. Вы можете выполнить рефакторинг HttpService для:

class HttpService {
  def newURLFrom(urlString) {
    new URL(urlString)
  }
  def getContentText(url) {
    url.content.text
  }
  def getContentFor(urlString) {
    getContentText(newURLFrom(urlString))
  }
}

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

class HttpServiceTests extends GroovyTestCase {

  def urlString = "http://stackoverflow.com"
  def fauxHtml = "<html><body>Hello, world</body></html>";
  def fauxURL = [content : [text : fauxHtml]]

  void testMakesURLs() {
    assertEquals(urlString, 
                 new HTTPService().newURLFrom(urlString).toExternalForm()) 
  }
  void testCanDeriveContentText() {
    assertEquals(fauxHtml, new HTTPService().getContentText(fauxURL));
  }
  // Going a bit overboard to test the line combining the two methods
  void testGetsContentForURL() {
    def service = new HTTPService()
    def emc = new ExpandoMetaClass( service.class, false )
    emc.newURLFrom = { input -> assertEquals(urlString, input); return fauxURL }
    emc.initialize()
    service.metaClass = emc
    assertEquals(fauxHtml, service.getContentFor(urlString))
  }
}

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

Я бы согласился с Нэтом в том, что это имеет смысл в качестве интеграционного теста. (Вы интегрируетесь с библиотекой URL-адресов Java на каком-то уровне.) Но, предполагая, что этот пример упрощает некоторую сложную логику, вы можете использовать метакласс для переопределения класса экземпляров, эффективно пародируя экземпляр.

0 голосов
/ 24 июня 2009

Трудно смоделировать JDK-классы, которые объявлены как final ... Ваша проблема, когда вы ссылаетесь через усовершенствование, состоит в том, что нет другого способа создать URL, кроме вызова конструктора.

Я пытаюсь отделить создание таких объектов от остальной части моего кода; Я бы создал фабрику, чтобы отделить создание URL-адресов. Это должно быть достаточно просто, чтобы не допустить проверки. Другие используют типичные подходы к созданию оболочек и декораторов. Или вы можете применить шаблон адаптера для перевода в доменные объекты, которые вы пишете.

Вот аналогичный ответ на на удивление похожую проблему: Насмешка URL в Java

Я думаю, что это демонстрирует то, что многие люди изучают после проведения большего количества тестирования: код, который мы пишем, чтобы сделать вещи более тестируемыми, предназначен для того, чтобы изолировать то, что мы хотим протестировать, от того, что мы можем с уверенностью сказать, уже тестировалось где-то еще. Это фундаментальное предположение, которое мы должны сделать, чтобы выполнить модульное тестирование. Это также может послужить хорошим примером того, почему хорошие модульные тесты не обязательно охватывают 100% кода. Они тоже должны быть экономными.

Надеюсь, это поможет.

0 голосов
/ 23 июня 2009

Что именно вы ожидаете потерпеть неудачу? Не совсем понятно, что вы пытаетесь проверить с помощью этого кода. Под издевкой URL.getContent вы говорите Groovy всегда возвращать переменную content, когда URL.getContent() is invoked. Желаете ли вы сделать возвращаемое значение URL.getContent() условным на основе строки URL? Если это так, то это достигается следующим образом:

import grails.test.*
import groovy.mock.interceptor.MockFor

class HttpServiceTests extends GrailsUnitTestCase {
    def connectionUrl

    void testGetsContentForURL() {
        // put the desired "correct" URL as the key in the next line
        def content = ["http://www.foo.com" : "<html><body>Hello, world</body></html>"]

        def mockURLContext = new MockFor(URL.class)
        mockURLContext.demand.getContent { [text : content[this.connectionUrl]] }
        mockURLContext.use {
            def httpService = new HttpService()
            this.connectionUrl = "http://www.wrongurl.com"
            assertEquals content.text, httpService.getContentFor(this.connectionUrl)
        }
    }
}
...