测试使用System.console()的Groovy类 [英] Test Groovy class that uses System.console()
问题描述
我有一个groovy脚本,它使用readline方法通过java.io.console对象向用户询问一些问题。另外,我用它来请求密码(用于设置HTTPS)。我如何使用Spock来测试这个代码?目前它抱怨该对象是Java最终对象,无法测试。显然,我不是第一个尝试这个,所以我想问问。
代码的草图看起来像这样:
class MyClass {
def cons
MyClass(){
cons = System。 console()
}
def getInput = {prompt,defValue - >
def input =(cons.readLine(prompt).trim()?:defValue)?. toString()
def inputTest = input?.toLowerCase()
input
}
}
我想单元测试测试一些模拟响应可以返回,可以返回默认值。注意:这是简化的,所以我可以弄清楚如何进行单元测试,getInput方法中还有更多的代码需要测试,但是一旦我清除了这个应该没有问题的障碍。
akhikhl编辑的答案
在建议之后,我做了一个简单的界面:
接口TestConsole {
String readLine(String fmt,Object ... args)
字符串readLine()
char [] readPassword(String fmt,Object。 .. args)
char [] readPassword()
}
然后我尝试了这样的测试:
def验证输入法嘲笑作品(){
def consoleMock = GroovyMock(TestConsole)
1 * consoleMock.readLine(_)>> 'validResponse'
inputMethods = new MyClass()
inputMethods.cons = consoleMock
当:
def testResult = inputMethods.getInput('testPrompt' ,'testDefaultValue')
then:
testResult =='validResponse'
}
我选择不改变构造函数,因为我不想改变我的实际代码来测试它。幸运的是,Groovy让我只用'def'来定义控制台,所以我的工作很好。
问题是,上述不起作用!我无法抗拒 - 这不是逻辑! Spock在GroovyMockMetaClass的某个地方变得迷失。如果我在代码中改变一行,并在测试中改变一行,它就可以工作。
代码更改:
From:
def input =(cons.readLine(提示符).trim()?:defValue)?. toString()
To :(添加null参数)
def input =(cons.readLine(prompt,null)。 trim()?:defValue)?. toString()
测试更改:
发件人:
1 * consoleMock.readLine(_)>> 'validResponse'
To :(再一次,添加一个null参数)
1 * consoleMock.readLine(_,null)>> 'validResponse'
然后测试终于起作用了。这是Spock中的一个错误还是我只是在左边的领域?我不介意在测试工具中需要做任何事情,但必须修改代码才能使这项工作真的很糟糕。
解决方案方案你是对的:因为Console类是final的,所以不能扩展。所以,解决方案应该朝另一个方向发展:
-
创建新的类MockConsole,不是从控制台继承的,而是具有相同的方法。
-
以这种方式更改MyClass的构造函数:
MyClass(cons = null){
this.cons = cons?:System.console()
-
在spock测试中实例化MockConsole并将其传递给MyClass构造函数。
update-201312272156
<我用spock玩了一下。嘲笑readLine(String fmt,Object ... args)的问题似乎是特定于可变参数(或最后一个参数是一个列表,这与groovy相同)。我设法减少了以下情况的问题:
定义一个接口:
interface IConsole {
String readLine(String fmt,Object ... args)
}
定义测试:
class TestInputMethods extends Specification {
def'test控制台输入'(){
setup:
def consoleMock = GroovyMock(IConsole)
1 * consoleMock.readLine(_)>> 'validResponse'
当:
//在这里,我们得到异常错误的参数数量:
def testResult = consoleMock.readLine('testPrompt')
then:
testResult =='validResponse'
}
}
这个测试的变体失败,例外是参数数量错误。特别是,spock认为readLine接受2个参数并忽略了第二个参数是vararg的事实。证明:如果我们从IConsole.readLine中删除Object ... args,测试会成功完成。
$ b 这是解决方法希望暂时)问题:改变调用readLine为:
def testResult = consoleMock.readLine('testPrompt',[] as对象[])
然后测试成功完成。
我也对spock 1.0-groovy-2.0-SNAPSHOT尝试了相同的代码 - 问题是一样的。
update-201312280030
varargs的问题已解决!非常感谢@charlesg,他回答了我的相关问题: Spock:模拟可变参数的方法
解决方案如下:用Mock替换GroovyMock,然后varargs被正确解释。
I have a groovy script that asks some questions from the user via a java.io.console object using the readline method. In addition, I use it to ask for a password (for setting up HTTPS). How might I use Spock to Unit Test this code? Currently it complains that the object is a Java final object and can not be tested. Obviously, I'm not the first one trying this, so thought I would ask.
A sketch of the code would look something like:
class MyClass {
def cons
MyClass() {
cons = System.console()
}
def getInput = { prompt, defValue ->
def input = (cons.readLine(prompt).trim()?:defValue)?.toString()
def inputTest = input?.toLowerCase()
input
}
}
I would like Unit Tests to test that some mock response can be returned and that the default value can be returned. Note: this is simplified so I can figure out how to do the Unit Tests, there is more code in the getInput method that needs to be tested too, but once I clear this hurdle that should be no problem.
EDITED PER ANSWER BY akhikhl Following the suggestion, I made a simple interface:
interface TestConsole {
String readLine(String fmt, Object ... args)
String readLine()
char[] readPassword(String fmt, Object ... args)
char[] readPassword()
}
Then I tried a test like this:
def "Verify get input method mocking works"() {
def consoleMock = GroovyMock(TestConsole)
1 * consoleMock.readLine(_) >> 'validResponse'
inputMethods = new MyClass()
inputMethods.cons = consoleMock
when:
def testResult = inputMethods.getInput('testPrompt', 'testDefaultValue')
then:
testResult == 'validResponse'
}
I opted to not alter the constructor as I don't like having to alter my actual code just to test it. Fortunately, Groovy let me define the console with just a 'def' so what I did worked fine.
The problem is that the above does not work!!! I can't resist - this is NOT LOGICAL! Spock gets 'Lost' in GroovyMockMetaClass somewhere. If I change one line in the code and one line in the test it works.
Code change:
From:
def input = (cons.readLine(prompt).trim()?:defValue)?.toString()
To: (add the null param)
def input = (cons.readLine(prompt, null).trim()?:defValue)?.toString()
Test change:
From:
1 * consoleMock.readLine(_) >> 'validResponse'
To: (again, add a null param)
1 * consoleMock.readLine(_, null) >> 'validResponse'
Then the test finally works. Is this a bug in Spock or am I just out in left field? I don't mind needing to do whatever might be required in the test harness, but having to modify the code to make this work is really, really bad.
You are right: since Console class is final, it could not be extended. So, the solution should go in another direction:
Create new class MockConsole, not inherited from Console, but having the same methods.
Change the constructor of MyClass this way:
MyClass(cons = null) { this.cons = cons ?: System.console() }
Instantiate MockConsole in spock test and pass it to MyClass constructor.
update-201312272156
I played with spock a little bit. The problem with mocking "readLine(String fmt, Object ... args)" seems to be specific to varargs (or to last arg being a list, which is the same to groovy). I managed to reduce a problem to the following scenario:
Define an interface:
interface IConsole {
String readLine(String fmt, Object ... args)
}
Define test:
class TestInputMethods extends Specification {
def 'test console input'() {
setup:
def consoleMock = GroovyMock(IConsole)
1 * consoleMock.readLine(_) >> 'validResponse'
when:
// here we get exception "wrong number of arguments":
def testResult = consoleMock.readLine('testPrompt')
then:
testResult == 'validResponse'
}
}
this variant of test fails with exception "wrong number of arguments". Particularly, spock thinks that readLine accepts 2 arguments and ignores the fact that second argument is vararg. Proof: if we remove "Object ... args" from IConsole.readLine, the test completes successfully.
Here is Workaround for this (hopefully temporary) problem: change the call to readLine to:
def testResult = consoleMock.readLine('testPrompt', [] as Object[])
then test completes successfully.
I also tried the same code against spock 1.0-groovy-2.0-SNAPSHOT - the problem is the same.
update-201312280030
The problem with varargs is solved! Many thanks to @charlesg, who answered my related question at: Spock: mock a method with varargs
The solution is the following: replace GroovyMock with Mock, then varargs are properly interpreted.
这篇关于测试使用System.console()的Groovy类的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!