2

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

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

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

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

所以,我希望写一个类似于

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 中是否可行。

我提到了这个演示文稿,它看起来很棒。但是,我担心在 DSL 中包含块的能力,如

verify generalTab{
...
}

PS:我不是 Lexers 和 Parsers 方面的专家(非计算机科学本科生),但我设法自学了 ANTLR,并在几年前玩了几个星期。同样,我对 Groovy 的经验很少。

4

1 回答 1

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

于 2012-09-16T00:53:03.180 回答