【问题标题】:Test Groovy class that uses System.console()测试使用 System.console() 的 Groovy 类
【发布时间】:2013-12-26 18:50:07
【问题描述】:

我有一个 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()
}

然后我尝试了这样的测试:

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”来定义控制台,所以我所做的工作正常。

问题是上面的不行!!!我无法抗拒——这不合逻辑! Spock 在 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'

然后测试终于成功了。这是 Spock 中的一个错误,还是我只是在左场?我不介意在测试工具中需要做任何可能需要做的事情,但是必须修改代码才能完成这项工作真的非常糟糕。

【问题讨论】:

  • 仅供参考:忘了说,我使用的是 Spock-0.7-groovy-2.0.jar
  • 您断言“必须修改代码才能使此 [测试] 工作真的非常糟糕。”。那要看。做好 TDD/BDD 通常需要代码设计良好且模块化以进行有效测试,尤其是如果您想使用 mock 和 stub 进行测试。因此,如果一个测试框架促使您使您的代码更加模块化或可配置,那么它可能被认为是“非常非常好的”。要求将协作者作为构造函数参数传递而不是硬编码可能适合该类别。查看优秀的 GOOS 书籍 - 通过测试发展面向对象的软件。
  • 你的说法没问题,我的断言(方式)太强了。尽管如此,在这种情况下,我仍然觉得仅仅为了帮助测试框架识别正确的模拟方法而需要更改代码是不行的......

标签: groovy spock


【解决方案1】:

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

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

  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 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'
  }
}

此测试变体失败,异常“参数数量错误”。特别是,spock 认为 readLine 接受 2 个参数并忽略了第二个参数是 vararg 的事实。证明:如果我们从 IConsole.readLine 中删除“Object ... args”,则测试成功完成。

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

def testResult = consoleMock.readLine('testPrompt', [] as Object[])

然后测试成功完成。

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

update-201312280030

可变参数的问题解决了!非常感谢@charlesg,他回答了我的相关问题:Spock: mock a method with varargs

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

【讨论】:

  • 尝试了这个建议,但还是不行。请参阅上面对我的问题的修改。
  • @JoeG,我通过对这个问题的研究扩展了我的答案。
  • 这也证实了我所看到的。不幸的是,这种解决方法对我不起作用——我从不直接调用模拟,被测代码调用它认为是真实对象的东西。就像我把 null 放在那里一样,要使测试工作需要更改被测代码作为框架中此问题的解决方法。
  • @JoeG,varargs 的问题现在已经解决了 :) 查看我对答案的最新更新。
猜你喜欢
  • 2014-02-21
  • 2016-07-13
  • 2012-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多