Groovy DSL vs Antlr用于人类可读的配置文件

时间:2012-09-14 10:03:52

标签: selenium groovy antlr dsl

希望这不是一个愚蠢的问题。

对于回归测试,我编写了一个小工具,它使用Selenium调出屏幕,根据数据库(多个表)验证加载的屏幕数据,并生成带有屏幕截图的报告(如果有错误)。

我是一个懒惰的人,而不是为单个用例编写一个类(大约60个用例用于屏幕),我编写了一个可以接受多个配置文件作为参数的单个类。 配置文件指示测试用例的流程(步骤),表单字段的xpath / id与数据库查询的映射,查询等。

一切正常,但问题是配置文件是XML。相邻的项目很感兴趣,并希望使用该工具,我希望他们能够轻松地理解该工具并根据他们的需求进行定制。在我看来,XML在这里是不合适的。此外,屏幕表单字段和数据库列之间的映射对于使用相同屏幕用于不同用例组合的许多测试用例是相同的。如果那里可以继承而不是复制内容会很棒。

所以,我希望写一个类似

的小型DSL
open application

load editClient window

switchTo generalTab

verify generalTab{

    if dataValidFor clientName then addInfoToReport else addErrorToReport
    if dataValidFor clientAddress then addInfoToReport else addErrorToReport
    if confidentialData visible then addInfoToReport else addErrorToReport

}
...

...

你明白了。 我打算做的就是在后台将DSL转换为Java(或Groovy,如果需要)方法调用。我知道要求强大的库像Antlr这样的要求并不是那么大。但是,我对Groovy的体验非常有限,我甚至不知道它是否可以在Groovy中使用。

我提到了这个presentation,看起来很神奇。但是,我担心能够在DSL中包含块,如

verify generalTab{
...
}
PS:我不是Lexers and Parsers(非计算机科学研究生)的专家,但我设法自学ANTLR并在几年前玩了几个星期。再一次,我对Groovy的经验很少。

1 个答案:

答案 0 :(得分:1)

我也不是groovy DSL的专家,但是我一直在玩它,我觉得你的情况是可行的。但它很大。

写作

verify generalTab { ... }

Groovy似乎已解决

verify( generalTab({ ... }) )

所以接近你想要的方法就是截取方法缺少调用('generalTab'对我来说似乎是一个html组件id,如果我错了请纠正我。)

您需要:verify()方法和methodMissing()方法。

您的ifelse ...呃,我们可以将其换成whenotherwise吗?只是为了避免groovy自己的保留词; - )

if之后的那些双字让整个事情变得非常难看。如果你可以使用一个点或只是一个单词会更好。

when dataValidFor clientName then addInfoToReport otherwise addErrorToReport

解析为

when(dataValidFor).clientName(then).addInfoToReport(otherwise).addErrorToReport

解析会很奇怪。如果你可以这样做,那就更好了:

when dataValidFor('clientName') then addInfoToReport otherwise addErrorToReport

我做了以下事情:

report = [:]

// the closure passed as a parameter to the html component
Closure runningVerification

// the closure that handles adding info to report
Closure addInfoToReport = { Data data -> 
  report[data] = "[INFO] Field '$data.field' from component '$data.component' valid: $data.valid"
}

// the closure that handles adding errors to report
Closure addErrorToReport = { Data data -> 
  report[data] = "[ERROR] Field '$data.field' from component '$data.component' valid: $data.valid"
}

/*
 * The when() method will receive a data object and returns
 * a map to be handled by the 'then' and the 'otherwise' cases
 *
 * The 'then' and 'otherwise' must passes closures to this method
 */
def when(Data data) {
  data.component = runningVerification.binding.htmlComponent

  [ then: 
    { Closure thenAction -> 

      if (data.valid) thenAction(data) 

      [ otherwise: 
        { Closure otherwiseAction -> 
          if (!data.valid) otherwiseAction(data) 
        }
      ]
    }
  ]
}


/*
 * Handles missing method calls. We need this to keep track of the 
 * 'generalTab', the HTML component whose tests are being ran against
 */
def methodMissing(String method, args) 
{
  runningVerification = args[0]
  runningVerification.delegate = this
  runningVerification.binding.htmlComponent = method // awful
  runningVerification()
}


/*
 * Verify the status of the validation for html component. The
 * argument is useless, it needs to access the report variable in 
 * the binding
 */
def verify(dataValidation) {
  def errors = report.findAll { !it.key.valid }.size()
  report.each { println it.value }
  print "Result: "
  if (errors == report.size()) {
    println "Every test failed"
  } else if (errors == 0) {
    println "Success"
  } else {
    println "At least one test failed"
  }
}

class Data { String component; String field; Boolean valid }

Data dataValidFor(String property) { 
  new Data(valid: new Random().nextInt() % 2, field: property)
}

Data confidentialData(String property) { 
  new Data(valid: new Random().nextInt() % 2, field: property)
}


verify generalTab {
  when dataValidFor('clientName') then addInfoToReport otherwise addErrorToReport
  when dataValidFor('clientCountry') then addInfoToReport otherwise addErrorToReport
  when confidentialData('clientId') then addInfoToReport otherwise addErrorToReport
}

它有效。它打印(随机):

[INFO] Field 'clientName' from component 'generalTab' valid: true
[ERROR] Field 'clientCountry' from component 'generalTab' valid: false
[INFO] Field 'clientId' from component 'generalTab' valid: true
Result: At least one test failed

它非常难看。这更像是一个概念证明。您需要使用BaseScripts,GroovyShell将其分离,将其委托给其他类等。考虑到报告类等等,您还需要对其进行整齐的建模。但到目前为止,我认为这是可行的。但是相当大。

我的阅读建议:

Guillaume Laforge在火星上展示了一个机器人的DSL脚本: http://www.slideshare.net/glaforge/going-to-mars-with-groovy-domainspecific-languages

Groovy的命令表达式的艺术: http://www.canoo.com/blog/2011/12/08/the-art-of-groovy-command-expressions-in-dsls/

这是我今天发送到groovy列表的电子邮件,一旦我设法完成了一个关于JFugue的DSL供我个人使用: http://groovy.329449.n5.nabble.com/Method-chaining-in-DSL-Instructions-td5711254.html

它在github上: https://github.com/wpiasecki/glissando