Вспомогательные элементы для тестирования модулей или неинтерфейсные черты в Scala

Этот вопрос связан с тестированием классов, которые смешиваются в неинтерфейсных чертах, это черты, содержащие некоторые функциональные возможности. При тестировании функциональность класса должна быть изолирована от функциональности, предоставляемой признаком смешения (который предположительно тестируется отдельно).

У меня есть простой класс Crawler, который зависит от набора функций утилиты HttpConnection и HttpHelpers. Теперь сосредоточьтесь на HttpHelpers.

В Java HttpHelpers, возможно, будет классом утилиты и передаст его singleton в Crawler в качестве зависимости, либо вручную, либо с некоторой картой IoC. Тестирование Crawler является простым, так как зависимость легко издевается.

В Scala кажется, что вспомогательный признак является более предпочтительным способом компоновки функциональности. В самом деле, его проще использовать (методы, автоматически импортируемые в пространство имен при расширении, могут использовать withResponse ... вместо httpHelper.withResponse ... и т.д.). Но как это влияет на тестирование?

Это мое решение, с которым я столкнулся, но, к сожалению, он поднимает некоторые шаблоны на стороне тестирования.

Характеристика помощника:

trait HttpHelpers {
  val httpClient: HttpClient
  protected def withResponse[A](resp: HttpResponse)(fun: HttpResponse => A): A = // ...
  protected def makeGetRequest(url: String): HttpResponse = // ...
}

Код для проверки:

class Crawler(val httpClient: HttpClient) extends HttpHelpers {
  // ...
}

Тест:

// Mock support trait
// 1) Opens up protected trait methods to public (to be able to mock their invocation)
// 2) Forwards methods to the mock object (abstract yet)
trait MockHttpHelpers extends HttpHelpers {
  val myMock: MockHttpHelpers
  override def makeGetRequest(url: String): HttpResponse = myMock.makeGetRequest(url)
}

// Create our mock using the support trait
val helpersMock = Mockito.mock(classOf[MockHttpHelpers])

// Now we can do some mocking
val mockRequest = // ...
Mockito when (helpersMock.makeGetRequest(Matchers.anyString())) thenReturn mockRequest

// Override Crawler with the mocked helper functionality
class TestCrawler extends Crawler(httpClient) with MockHttpHelpers {
  val myMock = helpersMock
}

// Now we can test
val crawler = new TestCrawler()
crawler.someMethodToTest()

Вопрос

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

  • Это правильный подход?
  • Если да, может ли его цель быть достигнута более эффективно (синтаксическая магия, плагин компилятора и т.д.)?

Любая обратная связь приветствуется. Спасибо!

Ответ 1

Вы можете написать черт Помощника, который должен быть смешан с HttpHelpers и переопределить его методы с макетным эквивалентом:

trait HttpHelpersMock { this: HttpHelpers =>

  //MOCK IMPLEMENTATION
  override protected def withResponse[A](resp: HttpResponse)(fun: HttpResponse => A): A = // ...

  //MOCK IMPLEMENTATION
  override protected def makeGetRequest(url: String): HttpResponse = // ...
}

Затем, при тестировании искателя, вы смешиваете фиктивный признак при создании экземпляра:

val crawlerTestee = new Crawler(x) with HttpHelpersMock

И макетные методы просто заменят вспомогательные методы в экземпляре crawlerTestee.

Изменить: Я не думаю, что это хорошая идея проверить, как класс взаимодействует со вспомогательной чертой. На мой взгляд, вы должны протестировать поведение Crawler, а не его внутреннюю детализацию реализации. Реализации могут измениться, но поведение должно оставаться как можно более стабильным. Процесс, описанный выше, позволяет переопределить вспомогательные методы, чтобы сделать их детерминированными и избежать реальной сети, тем самым помогая и ускоряя тесты.

Однако, я считаю, что имеет смысл проверить сам Помощник, поскольку он может быть повторно использован в другом месте и имеет правильное поведение.

Ответ 2

Как насчет:

val helpers = new HttpHelpers {
  //override or define stuff the trait needs to work properly here
}

helpers.someMethodToTest

Посмотрите, Ма, не нужны промежуточные черты и насмешливые библиотеки!

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