Scala 中的单元测试助手或非接口特征 [英] Unit testing helper or non-interface traits in Scala

查看:35
本文介绍了Scala 中的单元测试助手或非接口特征的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题是关于处理混合了非接口特征的类的测试,即包含一些功能的特征.测试时,类的功能应该与 mix-in trait 提供的功能(应该是单独测试的)隔离开来.

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).

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

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.

在 Java 中,HttpHelpers 可能是一个实用程序类,并将其作为依赖项手动或使用某些 IoC 框架传递给 Crawler.测试爬虫很简单,因为依赖很容易模拟.

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.

在 Scala 中,helper trait 似乎是组合功能的首选方式.确实,它更容易使用(扩展时自动导入命名空间的方法,可以使用withResponse ... 代替httpHelper.withResponse ... 等).但它如何影响测试?

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.

助手特征:

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 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.

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

欢迎任何反馈.谢谢!

推荐答案

您可以编写一个 Helper 模拟 trait,它应该与 HttpHelpers 混合使用,并用模拟等效物覆盖其方法:

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

模拟方法只会替换实例 crawlerTestee 中的辅助方法.

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

我认为测试类如何与辅助特征交互不是一个好主意.在我看来,您应该测试 Crawler 行为而不是它的内部实现细节.实现可以改变,但行为应该尽可能保持稳定.我上面描述的过程允许您覆盖辅助方法,使它们具有确定性并避免实际联网,从而帮助和加速测试.

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.

但是,我认为测试 Helper 本身是有意义的,因为它可以在其他地方重用并且具有适当的行为.

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

这篇关于Scala 中的单元测试助手或非接口特征的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆