英文:
Do you write helper methods when invoking production code in tests to hide some irrelevant details?
问题
以下是您要求的翻译内容:
假设我有一个示例测试用例(用Kotlin编写,但在这里无关紧要)
@Test
fun `should do something`() {
// given
val expected = "expected result"
// when
val result = productionClass.process("foo", null)
//then
assertThat(result).isEqualTo(expected)
}
正如您所见,我将null
值作为第二个参数传递给process
方法,因为在这种情况下它不相关,所以我可以忽略它。
但我担心的是,每次阅读这个测试时,我都会想知道为什么传递这个null
值,以及在这个上下文中它是什么意思,所以我解决这个问题的方法是创建一些帮助方法来隐藏这个null
。然后我的测试将如下所示
@Test
fun `should do something`() {
// given
val expected = "expected result"
// when
val result = process("foo")
//then
assertThat(result).isEqualTo(expected)
}
private fun process(foo: String): String {
return productionClass.process(foo, null)
}
这样,当我阅读测试时,我清楚地知道发生了什么,但通常这样的解决方案都有利有弊。以下是我发现的:
优点
- 提高了测试的可读性
- 轻微解耦测试中的生产代码(更改生产API仅会导致此助手出现问题,而不是其他100个测试)
- 从测试中隐藏了底层细节,我只关心结果,在这里发生的方式并不重要(也许...或者我在这里理解错误)
缺点
- 我不太确定测试在做什么,因为它在这个助手中可以做很多奇怪的事情
- 创建一个助手可能会导致创建许多其他助手,然后我将找不到正在发生什么
我真的很想知道您的意见。您是如何处理这个问题的?或者也许这根本不是问题?
英文:
Let's say that I have an example test case (written in Kotlin, but it doesn't matter here)
@Test
fun `should do something`() {
// given
val expected = "expected result"
// when
val result = productionClass.process("foo", null)
//then
assertThat(result).isEqualTo(expected)
}
As you can see I'm passing a null
value as a second parameter to process
method because it's not relevant in this case so I can ommit this.
But my concern is that every time I'm reading this test I'm wondering why this null
value is passed and what does it mean in this context, so my solution to this problem is to create some helper method which hides this null for me. Then my test would looke like this
@Test
fun `should do something`() {
// given
val expected = "expected result"
// when
val result = process("foo")
//then
assertThat(result).isEqualTo(expected)
}
private fun process(foo: String): String {
return productionClass.process(foo, null)
}
Then when I'm reading the test it's clear for me what is happening, but as always there are pros & cons of such solutions. These are the ones that I found:
Pros
- improved readability of the test
- small decoupling of production code from tests (change of the production API causes only this helper to break, not 100 other tests)
- hiding low level details from test, I'm only intrested in the result, it's not important here how it happens (maybe... or maybe I'm wrong here)
Cons
- I'm not really sure what the test does as it can do many weird things in this helper
- Creating one helper can lead to creation of many other helpers, and then I won't be able to find what is going on
I'm really curious about your opinion. How do you handle this problem? Or maybe it's not a problem at all?
答案1
得分: 1
我将列举一些反对所列优点的论点...
这些测试(在我看来)应该准确地显示测试中when条件所执行的方法(在Give-When-Then设置中)。因此,从可读性的角度来看,如果引入另一个方法(或多个具有不同参数组合的包装方法),我必须阅读更多的代码,并且这些变化很少(如果有的话)会改善情况,因为现在代码行数变多了,而不是减少了,如果有多个变化,情况会变得非常混乱。对我来说,可读性通常意味着更简洁/命名更好的代码,而不是额外的冗余层,这会隐藏重要细节。除非参数列表特别长。那么,针对这些情况,还有其他充分记录的解决方案,如构建器/工厂等。
"小解耦" - 解耦涉及代码片段对其他代码的依赖性。在今天的大多数集成开发环境(IDE)中,通过正确的知识和方法,重构是非常容易的...因此没有必要有一个可以使生活变得更轻松的方法 - 生活已经相对容易。看看“选择下一个实例”功能,用于突出显示一些代码,或者“重构方法”,通常允许开发人员重新排序或更改方法名称/参数列表,以从方法中删除参数,并将该更改级联到所有其他调用点。有一种被称为“扇入/扇出”的度量可以显示一个类在进行或接收方法调用时依赖另一个类的程度。这可以凸显高度耦合的代码...将代码委托给另一个方法不会减少代码类之间的耦合。
隐藏低层细节 - 单元测试是“白盒”测试。测试源代码时,您确实想要知道细节。在这个层次上,您不希望隐藏细节,因为细节很重要。间接性层次越多,隐藏缺陷的地方就越多。如果API难以使用或让人讨厌,单元测试应该能够显示出这一点,您可以/应该在注意到时进行重构,以使编写测试更清晰/更容易。
也许认为有这种方法的需求是您需要将该方法拆分成多个方法的线索,以便更清楚地测试功能?
英文:
I'll give some arguments against the Pros listed...
The tests are (in my view) supposed to show exactly the method that is being exercised by the when condition for a test (in a Give-When-Then setup). So in terms of readability it means I have to read more code if another method is introduced (or several wrappers with various argument combinations) and does very little (if anything) to improve the situation, as now there's more lines of code rather than less and if there are several its going to get confusing real fast. Readability for me typically means more concise code/better named code, rather than an additional layer of redundancy that hides important details. Unless the argument list was particularly long. Then there are other well documented solutions for those situations like builders/factories/etc.
"Small decoupling" - Decoupling is to do with the dependence of one bit of code on another. Refactoring with most IDEs today is very easy with the right know-how... so its not necessary to have a method to make life easier - life already is relatively easy. Look at "select next instance of" functionality for when you've highlighted some bit of code, or "refactor method" which will typically allow the developer to reorder or change a method name/argument list to remove an argument from a method and cascade that change to all other call-sites. There's a measure known as fan-in/fan-out which can show how much one class depends on another when making or receiving method calls. This can highlight highly coupled code... delegating to another method does nothing to decouple code class-to-class.
Hiding low level details - unit testing is "white-box" testing. Testing source code where you do want to know the details. You don't really want to be hiding details at this level as the details matter. The more layers of indirection, the more places for bugs to hide. If the API is difficult or annoying to work with, the unit-testing should make this apparent and you can/should refactor, if you notice it, to make writing the tests clearer/easier.
Perhaps thinking there's a need for this sort of method is a clue that you need to break the method apart into multiple methods, so you can test functions more cleanly???
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论