Scala 中的单元测试助手或非接口特征
这个问题是关于处理混合非接口特征(即包含某些功能的特征)的类的测试。测试时,类功能应与混合特征提供的功能隔离(据说是单独测试的)。
我有一个简单的 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 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您可以编写一个 Helper 模拟特征,该特征应与 HttpHelpers 混合,并用模拟等效项覆盖其方法:
然后,在测试爬虫时,您可以在实例化时混合模拟特征:
并且模拟方法将仅替换实例
crawlerTestee
中的辅助方法。编辑:我认为测试类如何与辅助特征交互不是一个好主意。在我看来,您应该测试 Crawler 行为而不是其内部实现细节。实现可以改变,但行为应尽可能保持稳定。我上面描述的过程允许您覆盖帮助器方法,使其具有确定性并避免真正的网络,从而帮助和加速测试。
但是,我相信测试 Helper 本身是有意义的,因为它可以在其他地方重用并且具有正确的行为。
You can write an Helper mock trait which should be mixed with
HttpHelpers
and override its methods with mock equivalent:Then, when testing crawler, you mix the mock trait at instantiation:
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.
怎么样:
看,马,不需要中间特征和模拟库!
为了我的特质,我一直这样做,而且我对结果非常满意。
How about:
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.