Scala 中的单元测试助手或非接口特征

发布于 2024-11-30 02:27:07 字数 1904 浏览 3 评论 0原文

这个问题是关于处理混合非接口特征(即包含某些功能的特征)的类的测试。测试时,类功能应与混合特征提供的功能隔离(据说是单独测试的)。

我有一个简单的 Crawler 类,它依赖于 HttpConnection 和实用函数的 HttpHelpers 集合。现在让我们关注 HttpHelpers。

在 Java 中,HttpHelpers 可能是一个实用程序类,并且会手动或使用某些 IoC 框架将其单例作为依赖项传递给 Crawler。测试 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()

问题

这种方法可以工作,但是需要为每个辅助特征提供模拟支持特征有点乏味。但是我看不到任何其他方法可以实现这一点。

  • 这是正确的方法吗?
  • 如果是的话,是否可以更有效地实现其目标(语法魔术、编译器插件等)?

欢迎任何反馈。谢谢你!

This question is about dealing with testing of classes which mix in non-interface traits, that is traits containing some functionality. When testing, the class functionality should be isolated from the functionality provided by the mix-in trait (which is supposedly tested separately).

I have a simple Crawler class, which depends on a HttpConnection and a HttpHelpers collection of utility functions. Let's focus on the HttpHelpers now.

In Java, HttpHelpers would possibly be a utility class, and would pass its singleton to Crawler as a dependency, either manually or with some IoC framework. Testing Crawler is straightforward, since the dependency is easy to mock.

In Scala it seems that a helper trait is more preferred way of composing functionality. Indeed, it is easier to use (methods automatically imported into the namespace when extending, can use withResponse ... instead of httpHelper.withResponse ..., etc.). But how does it affect testing?

This is my solution I came up with, but unfortunately it lifts some boilerplate to the testing side.

Helper trait:

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

Code to test:

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

Test:

// 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()

Question

This approach does the work, but the need to have a mock support trait for each helper trait is a bit tedious. However I can't see any other way for this to work.

  • Is this the right approach?
  • If it is, could its goal be reached more efficiently (syntax magic, compiler plugin, etc)?

Any feedback is welcome. Thank you!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

甜心 2024-12-07 02:27:07

您可以编写一个 Helper 模拟特征,该特征应与 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 行为而不是其内部实现细节。实现可以改变,但行为应尽可能保持稳定。我上面描述的过程允许您覆盖帮助器方法,使其具有确定性并避免真正的网络,从而帮助和加速测试。

但是,我相信测试 Helper 本身是有意义的,因为它可以在其他地方重用并且具有正确的行为。

You can write an Helper mock trait which should be mixed with HttpHelpers and override its methods with mock equivalent:

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 = // ...
}

Then, when testing crawler, you mix the mock trait at instantiation:

val crawlerTestee = new Crawler(x) with HttpHelpersMock

And the mock methods will just replace the helper methods in instance crawlerTestee.

Edit: I don't think its a good idea to test how a class interacts with an helper trait. In my opinion, you should test Crawler behavior and not its internal implementation detail. Implementations can change but the behavior should stay as stable as possible. The process I described above allows you to override helper methods to make them deterministic and avoid real networking, thus helping and speeding tests.

However, I believe it make sense to test the Helper itself, since it may be reused elsewhere and has a proper behavior.

神仙妹妹 2024-12-07 02:27:07

怎么样:

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

helpers.someMethodToTest

看,马,不需要中间特征和模拟库!

为了我的特质,我一直这样做,而且我对结果非常满意。

How about:

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

helpers.someMethodToTest

Look, Ma, no intermediate traits and mocking libraries needed!

I do that all the time for my traits and I've been pretty happy with the result.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文