提问人:q-q 提问时间:2/12/2022 更新时间:10/17/2023 访问量:916
使用 Groovy 和 Spock 参数捕获访问 Lambda 参数
Access Lambda Arguments with Groovy and Spock Argument Capture
问:
我正在尝试使用包含 lambda 函数的方法对 Java 类进行单元测试。我正在使用 Groovy 和 Spock 进行测试。由于专有原因,我无法显示原始代码。
Java 方法如下所示:
class ExampleClass {
AsyncHandler asynHandler;
Component componet;
Component getComponent() {
return component;
}
void exampleMethod(String input) {
byte[] data = input.getBytes();
getComponent().doCall(builder ->
builder
.setName(name)
.data(data)
.build()).whenCompleteAsync(asyncHandler);
}
}
其中具有以下签名:component#doCall
CompletableFuture<Response> doCall(Consumer<Request> request) {
// do some stuff
}
时髦测试如下所示:
class Spec extends Specification {
def mockComponent = Mock(Component)
@Subject
def sut = new TestableExampleClass(mockComponent)
def 'a test'() {
when:
sut.exampleMethod('teststring')
then:
1 * componentMock.doCall(_ as Consumer<Request>) >> { args ->
assert args[0].args$2.asUtf8String() == 'teststring'
return new CompletableFuture()
}
}
class TestableExampleClass extends ExampleClass {
def component
TestableExampleClass(Component component) {
this.component = component;
}
@Override
getComponent() {
return component
}
}
}
如果我在该行上放置一个断点,捕获的参数 , 将在调试窗口中显示如下:args
assert
args = {Arrays$ArrayList@1234} size = 1
> 0 = {Component$lambda}
> args$1 = {TestableExampleClass}
> args$2 = {bytes[]}
有两点让我感到困惑:
当我尝试将捕获的参数转换为 either 或它抛出一个 .我相信这是因为它正在期待,但我不确定如何投射它。
args[0]
ExampleClass
TestableExampleClass
GroovyCastException
Component$Lambda
使用 访问属性似乎不是一个干净的方法。这可能与上述选角问题有关。但是有没有更好的方法可以做到这一点,例如使用 ?
data
args[0].args$2
args[0].data
即使无法给出直接答案,指向某些文档或文章的指针也会有所帮助。我的搜索结果分别讨论了 Groovy 闭包和 Java lambda 的比较,但没有讨论在闭包中使用 lambda。
答:
为什么你不应该做你正在尝试的事情
这种侵入性测试是一场噩梦!对不起,我的措辞很强硬,但我想明确指出,您不应该过度指定这样的测试,在 lambda 表达式的私有最终字段上断言。为什么 lambda 中的内容甚至很重要?只需验证结果即可。为了进行这样的验证,您
- 需要了解 lambda 在 Java 中是如何实现的内部结构,
- 这些实现细节必须在 Java 版本之间保持不变,并且
- 在JVM类型(如Oracle Hotspot,OpenJ9等)中,实现甚至必须相同。
否则,您的测试很快就会中断。你为什么要关心一个方法在内部如何计算它的结果?一种方法应该像黑匣子一样进行测试,只有在极少数情况下才应该使用交互测试,这对于确保对象之间的某些交互以某种方式发生(例如,为了验证发布-订阅设计模式)是绝对关键的。
无论如何你怎么能做到这一点(不要!!)
说了这么多,只是假设像这样测试确实有意义(它真的没有!),提示:除了访问字段,您还可以访问索引为 1 的声明字段。当然,也可以按名称访问。无论如何,您必须反思 lambda 的类,获取您感兴趣的声明字段,使它们可访问(记住,它们是),然后对它们各自的内容进行断言。您还可以按字段类型进行筛选,以降低对字段顺序的敏感度(此处未显示)。args$2
private final
此外,我不明白你为什么要创建一个而不是使用原始版本。TestableExampleClass
在此示例中,我使用显式类型,而不仅仅是为了让更容易理解代码的作用:def
then:
1 * mockComponent.doCall(_ as Consumer<Request>) >> { args ->
Consumer<Request> requestConsumer = args[0]
Field nameField = requestConsumer.class.declaredFields[1]
// Field nameField = requestConsumer.class.getDeclaredField('arg$2')
nameField.accessible = true
byte[] nameBytes = nameField.get(requestConsumer)
assert new String(nameBytes, Charset.forName("UTF-8")) == 'teststring'
return new CompletableFuture()
}
或者,为了避免明确支持 Spock 式条件:assert
def 'a test'() {
given:
String name
when:
sut.exampleMethod('teststring')
then:
1 * mockComponent.doCall(_ as Consumer<Request>) >> { args ->
Consumer<Request> requestConsumer = args[0]
Field nameField = requestConsumer.class.declaredFields[1]
// Field nameField = requestConsumer.class.getDeclaredField('arg$2')
nameField.accessible = true
byte[] nameBytes = nameField.get(requestConsumer)
name = new String(nameBytes, Charset.forName("UTF-8"))
return new CompletableFuture()
}
name == 'teststring'
}
评论
ExampleClass
void
args$2
评论