测试使用System.console()的Groovy类 [英] Test Groovy class that uses System.console()

查看:103
本文介绍了测试使用System.console()的Groovy类的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个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的,所以不能扩展。所以,解决方案应该朝另一个方向发展:


  1. 创建新的类MockConsole,不是从控制台继承的,而是具有相同的方法。

  2. 以这种方式更改MyClass的构造函数:

    MyClass(cons = null){
    this.cons = cons?:System.console()


  3. 在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,然后v​​arargs被正确解释。


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:

  1. Create new class MockConsole, not inherited from Console, but having the same methods.

  2. Change the constructor of MyClass this way:

    MyClass(cons = null) { this.cons = cons ?: System.console() }

  3. 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屋!

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