测试使用System.console()的Groovy类

测试使用System.console()的Groovy类,groovy,spock,Groovy,Spock,我有一个groovy脚本,它使用readline方法通过java.io.console对象向用户询问一些问题。此外,我使用它来请求密码(用于设置HTTPS)。如何使用Spock对代码进行单元测试?目前,它抱怨该对象是Java最终对象,无法测试。显然,我不是第一个尝试这个的人,所以我想我会问 代码的草图如下所示: class MyClass { def cons MyClass() { cons = System.console() } def getInp

我有一个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的回答编辑 根据建议,我制作了一个简单的界面:

interface TestConsole {
    String readLine(String fmt, Object ... args)
    String readLine()
    char[] readPassword(String fmt, Object ... args)
    char[] readPassword()
}
interface IConsole {
  String readLine(String fmt, Object ... args)
}
然后我尝试了这样的测试:

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'
}
我选择不改变构造函数,因为我不喜欢仅仅为了测试它而改变我的实际代码。幸运的是,Groovy让我用一个“def”来定义控制台,所以我所做的工作很好

问题是,上述方法不起作用!!!我无法抗拒——这是不合逻辑的!斯波克在GroovyMockMetaClass的某个地方“迷失”了方向。如果我在代码中更改一行,在测试中更改一行,它就会工作

代码更改:

From:
    def input = (cons.readLine(prompt).trim()?:defValue)?.toString()
To: (add the null param)
    def input = (cons.readLine(prompt, null).trim()?:defValue)?.toString()
测试更改:

From:
    1 * consoleMock.readLine(_) >> 'validResponse'
To: (again, add a null param)
    1 * consoleMock.readLine(_, null) >> 'validResponse'

然后测试终于开始了。这是斯波克的错误还是我只是在左外野?我不介意需要在测试工具中执行任何可能需要的操作,但是必须修改代码以使其正常工作是非常非常糟糕的。

你是对的:因为Console类是最终类,所以它不能扩展。因此,解决方案应该转向另一个方向:

  • 创建新类MockConsole,它不是从Console继承的,而是具有相同的方法

  • 通过以下方式更改MyClass的构造函数:

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

  • 在spock测试中实例化MockConsole并将其传递给MyClass构造函数

  • 更新-2013122272156

    我和斯波克玩了一会儿。模拟“readLine(String fmt,Object…args)”的问题似乎特定于varargs(或者最后一个arg是一个列表,这与groovy相同)。我设法将问题简化为以下情况:

    定义一个接口:

    interface TestConsole {
        String readLine(String fmt, Object ... args)
        String readLine()
        char[] readPassword(String fmt, Object ... args)
        char[] readPassword()
    }
    
    interface IConsole {
      String readLine(String fmt, Object ... args)
    }
    
    定义测试:

    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'
      }
    }
    
    此测试变体失败,例外情况是“参数数量错误”。特别是,斯波克认为readLine接受两个参数,而忽略了第二个参数是vararg这一事实。证明:如果我们从IConsole.readLine中删除“Object…args”,测试将成功完成

    这里是解决此问题(希望是暂时的)的解决方法:将对readLine的调用更改为:

    def testResult = consoleMock.readLine('testPrompt', [] as Object[])
    
    然后测试成功完成

    我还针对spock 1.0-groovy-2.0-SNAPSHOT尝试了相同的代码-问题是相同的

    更新-20131280030

    varargs的问题解决了!非常感谢@charlesg,他回答了我的相关问题:


    解决方案如下:将GroovyMock替换为Mock,然后正确解释varargs。

    尝试遵循此建议,但仍然无效。请看上面对我问题的编辑。@JoeG,我通过对这个问题的研究扩展了我的答案。这也证实了我所看到的。不幸的是,这种变通方法对我不起作用——我从不直接调用mock,因为测试中的代码调用它认为是真实对象的东西。就像我将null放在那里一样,要使测试工作,需要更改被测代码作为框架中此问题的解决方法。@JoeG,varargs的问题现在已经解决:)请参阅我的最新更新以获得答案。仅供参考:忘了提一下,我使用的是Spock-0.7-groovy-2.0.jar。您断言“必须修改代码才能进行此[测试]工作真的,真的很糟糕。”。那要看情况。做好TDD/BDD通常需要代码经过良好的设计和模块化,以便有效地进行测试,特别是如果您想使用模拟和存根进行测试。因此,如果一个测试框架促使您使代码更加模块化或可配置,那么它可能会被认为“非常非常好”。要求将协作者作为构造函数参数而不是硬编码的参数传递可能适合这一类别。看看优秀的GOOS图书,通过测试不断增长的面向对象软件。你的陈述没有问题,我的断言(方式)太强了。尽管如此,在这个例子中,我仍然觉得仅仅为了帮助测试框架识别正确的模拟方法而需要更改代码是不好的。。。