2

これがばかげた質問ではないことを願っています。

回帰テストでは、Seleniumを使用して画面を表示し、読み込まれた画面データをデータベース(複数のテーブル)と照合し、スクリーンショット付きのレポートを生成する(エラーの場合)小さなツールを作成しました。

私は怠惰な人間なので、単一のユースケース(画面に最大60のユースケース)用に1つのクラスを作成する代わりに、パラメーターとして複数の構成ファイルを受け入れることができる単一のクラスを作成しました。 構成ファイルは、テストケースのフロー(ステップ単位)、データベースクエリ、クエリなどに対するフォームフィールドの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でそれが可能かどうかさえわかりません。

私はこのプレゼンテーションを参照しました、そしてそれは素晴らしく見えます。ただし、DSLにブロックを含める機能について心配しています。

verify generalTab{
...
}

PS:私はLexersとParsers(コンピューターサイエンス以外の学部生)の専門家ではありませんが、数週間前にANTLRを自己学習し、遊んでいました。繰り返しますが、私はGroovyの経験がほとんどありません。

4

1 に答える 1

1

私もgroovyのDSLの専門家ではありませんが、少し遊んでいるので、あなたのケースは実行可能だと思います。しかし、それは大きいです。

書き込み

verify generalTab { ... }

Groovyは次のように解決するようです

verify( generalTab({ ... }) )

したがって、必要な方法に近い方法は、メソッドが欠落している呼び出しをインターセプトすることです('generalTab'は私にはhtmlコンポーネントIDのようです。間違っている場合は、修正してください)。

必要なもの:verify()メソッドとmethodMissing()メソッド。

あなたifelses...ええと、私たちはそれをwhenとに交換できotherwiseますか?groovy自身の予約語を避けるためだけに;-)

後のこれらの二重の言葉はif、全体をかなり醜くします。ドットまたは1つの単語だけを使用できるとよいでしょう。

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/

これは、私が個人的に使用するためにJFugueを介してDSLを終了することができた後、今​​日groovyリストに送信した電子メールです: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 に答える