私のコードには 2 つのクラスがあります。1 つは DynamicRule と呼ばれるもので、もう 1 つはルール インスタンスに GPARS activeObject ラッパーを提供する AORule と呼ばれるラッパーです。
DynamicRule はこれに少し似ています - dynamicExecute と呼ばれるメソッドを使用します
@InheritConstructors
@Component
@Slf4j //use Groovy AST to get logger
@EqualsAndHashCode
@Scope ("prototype") //IOC to deliver new instance on each injection
@Qualifier ("dynamicRule")
class DynamicRule extends BasicRule implements org.easyrules.api.Rule {
Closure dynamicEvaluation = null
Closure dynamicExecution = null
String sync = "sync"
... with method
void dynamicExecute () {
log.debug "dynamic execute: " + this.dump() + " : dynamic execute called : a rule.dynamicExecution was defined as > " + this.dynamicExecution
//if dynamic execution closure defined then call that
log.debug "dynamic execute: getting DynamicExecution reference "
def clos = this.getDynamicExecution()
//assert clos == this.dynamicExecution
log.debug "dynamic execute: rule.dynamicExecution using get() returns " + clos.dump()
if (clos) {
clos.call() //call execute action
log.debug "dynamic execute: dynamic rule closure was executed "
}
}....
GPARS activeObject を使用してDynamicRule rule
参照の状態を管理するこの AORule のラッパー クラスがあります。execute()
メソッドは非同期の activeObject メソッドを呼び出して、内部rule
参照でアクションをトリガーします。
@Component
@Scope("prototype")
@ActiveObject
@Slf4j
@EqualsAndHashCode
class AORule implements org.easyrules.api.Rule {
//setup delegate dynamicRule and make it a part of this ActiveObject
//prototyped setter injection
//@Autowired
//@Scope ("prototype")
//@Qualifier ("dynamicRule")
@Delegate DynamicRule rule
AORule (name, description=null) {
log.debug "Create aorule.rule name/desc constructor called "
rule = new DynamicRule (name, description ?: "Basic Active Object Rule")
}
AORule () {
log.debug "Create aorule.rule default constructor called "
rule = new DynamicRule ()
}
@Autowired (/*required=true*/)
AORule (@Qualifier ("dynamicRule") DynamicRule dynrule) {
log.debug "Create aorule.rule injected rule constructor called "
rule = dynrule
}
....
void execute () {
active_execute()
}
/**
* Active method manages async action through object inside through hidden actor.
* variable 'rule' is the variable we are protecting. Runs either any dynmicExecution closure
* where defined or just runs the standard class execute method
*
* @return void
*/
@ActiveMethod
void active_execute () {
//rule.execute()
log.debug "activeExec : rule dump looks like " + rule.dump()
def val = rule.dynamicExecution
assert val == true
log.debug "activeExec : ret value of rule.getDynamicExecution() was > " + val
if (val != null) {
log.debug "activeExec : running dynamicExecution closure, where rule closure was " + rule?.dynamicExecution.toString()
rule.dynamicExecute() //should call method that executes closure, where this is defined
}
else {
log.debug "activeExec : running std Execution action "
rule.execute()
}
}
これが自分自身で機能していることを示すために、いくつかのテストを作成しようとしましたが、失敗しました。mockres
私は 2 つの簡単なテストを実行しようとしましたが、1 つは dynamicRule 自体で機能します。期待値を設定し、モックにクロージャーを呼び出して変数に対するそのクロージャーの副作用をテストするように依頼します。すべて期待どおりに機能します。
def "set execution closure using spock mock but call stub directly when doing an execute action " () {
given:"setup of stub for testing "
def mockres = ""
def clos = {
mockres = "did nothing";
println "script: in stub closure, setting mockres to " + mockres + " mockres dump > " + mockres.dump();
}
//spock mock
DynamicRule stub = Mock ()
1*stub.dynamicExecute() >> {clos()} // pretend this has been set
when : "execution on stub directly "
stub.dynamicExecute()
then : "test execute closure ran as expected "
mockres == "did nothing"
}
2 番目のテストでは、モックをセットアップして aorule.rule インスタンスに設定し、モックの期待値を設定し、アクティブなメソッドaorule.execute()
を呼び出すメソッドを呼び出し、それが私のモックを呼び出しますactiveExecute()
def "set execution closure using spock mock but call aorule.execute() when doing an execute action " () {
given:"setup of stub for testing "
def mockres = ""
def clos = {
mockres = "did nothing";
println "script: in stub closure, setting mockres to " + mockres + " mockres dump > " + mockres.dump();
}
//spock mock
DynamicRule stub = Mock ()
1*stub.getDynamicExecution() >> { true }
1*stub.dynamicExecute() >> {clos()} // pretend this has been set
println "test: save stub on aorule"
aorule.rule = stub
when : "call execute() on aorule.rule for stub "
//stub.dynamicExecute()
//aorule.rule.dynamicExecute() //works
println "test: call aorule.execute"
aorule.execute() //think this must be getting a new prototype object which is why not getting called
then : "test execute closure ran as expected "
mockres == "did nothing"
}
これは失敗します - モックでクロージャーが実行されず、 then: アサーション テストが失敗する理由。
ここに出力トレースの一部を含めました - これがaorule.execute()
activeObject メソッドをトリガーすることactiveExec : rule dump looks like
がわかります。 of val など)、スタブ クロージャは呼び出されず、mockres は設定されません - テストは失敗します
...
test: save stub on aorule
13:07:45.361 [main] DEBUG org.easyrules.spring.AORule - setting aorule.rule with > Mock for type 'DynamicRule' named 'stub'
test: call aorule.execute
13:07:45.367 [Actor Thread 1] DEBUG org.easyrules.spring.AORule - activeExec : rule dump looks like <org.easyrules.spring.DynamicRule$$EnhancerByCGLIB$$9b33215e@a4770a4 CGLIB$BOUND=false CGLIB$CALLBACK_0=org.spockframework.mock.runtime.CglibMockInterceptorAdapter@4809f9c CGLIB$CALLBACK_1=net.sf.cglib.proxy.NoOp$1@bf1bd4 dynamicEvaluation=null dynamicExecution=null sync=null name=null description=null priority=0>
13:07:45.441 [main] DEBUG o.s.t.c.s.AbstractDirtiesContextTestExecutionListener - After test method: context [DefaultTestContext@5c6648b0 testClass = AORuleSpecTest2, testInstance = org.easyrules.spring.AORuleSpecTest2@d41f816, testMethod = $spock_feature_0_1@AORuleSpecTest2, testException = Condition not satisfied:
mockres == "did nothing"
| |
"" false
11 differences (0% similarity)
(-----------)
(did nothing)
, mergedContextConfiguration = [MergedContextConfiguration@6f1de4c7 testClass = AORuleSpecTest2, locations = '{}', classes = '{class org.easyrules.spring.AORule, class org.easyrules.spring.DynamicRule}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]], class annotated with @DirtiesContext [false] with mode [null], method annotated with @DirtiesContext [false] with mode [null].
Condition not satisfied:
私はこれをトレースしようとしているか、デバッガーで監視しようとしましたが、役に立ちませんでした。スタブのクロージャがトリガーされません - テストが失敗します。
デバッガーでもう一度見ようとしたところ、行を過ぎたときに
def val = rule.dynamicExecution
デバッガーはInternalActor.handleCurrentMessage()
、GPAR のどこかでエラー ハンドラーにジャンプします。これを変更しても違いはありません
def val = rule.getDynamicExecution() //try to get value via groovy auo method
私はこれを理解していません。おそらく私は何かばかげたことをしていますが、@ActiveMethod - activeExecute()
モックを使用してコードをテストするためのテストを書くことができません (手作りのクラスなどを介してこれを実行しようとしましたが、シミュレートされたテストを機能させることができません.
テストを正しく修正して実行するにはどうすればよいですか? 私が知る限り-スクリプトでコードを実行すると(「ライブ」テスト)、コードはやりたいことを実行しているように見えますが、それを示すための単体テストを作成することはできません
追記: ActiveMethod のラッピングに関係しています - よくわかりません
次のようにテスト スクリプトに 2 つのダミー ラッパー クラスを作成しましたdynamicExecute()
。AOWrappersは@ActiveMethod
class WrapperRule {
@Autowired //property injection
@Delegate DynamicRule rule
WrapperRule () {
}
WrapperRule (DynamicRule irule) {
rule = irule
assert rule
}
void execute () {
rule.dynamicExecute()
}
}
@ActiveObject
@Slf4j
@EqualsAndHashCode
class AOWrapperRule {
@Autowired //property injection
@Delegate DynamicRule rule
AOWrapperRule () {
}
AOWrapperRule (DynamicRule irule) {
rule = irule
assert rule
}
@ActiveMethod
void execute () {
rule.dynamicExecute()
}
}
同じスクリプト ファイル内の 2 つのテストは次のようになります
def "set execution closure using spock mock on simple Wrapper rule when doing an execute action " () {
given:"setup of stub for testing "
def mockres = ""
def clos = {
mockres = "did nothing";
println "script: in stub closure, setting mockres to " + mockres + " mockres dump > " + mockres.dump();
}
WrapperRule wrule = new WrapperRule ()
//spock mock
DynamicRule stub = Mock ()
1*stub.dynamicExecute() >> {clos()} // pretend this has been set
println "test: save stub on wrule"
wrule.rule = stub
when : "call execute() on wrule.rule for stub "
println "test: call wrule.execute"
wrule.execute() //think this must be getting a new prototype object which is why not getting called
then : "test execute closure ran as expected "
mockres == "did nothing"
}
def "set execution closure using spock mock on AO Wrapper rule when doing an execute action " () {
given:"setup of stub for testing "
def mockres = ""
def clos = {
mockres = "did nothing";
println "script: in stub closure, setting mockres to " + mockres + " mockres dump > " + mockres.dump();
}
AOWrapperRule aowrule = new AOWrapperRule ()
//spock mock
DynamicRule stub = Mock ()
1*stub.dynamicExecute() >> {clos()} // pretend this has been set
println "test: save stub on aowrapper"
aowrule.rule = stub
when : "call execute() on aowrule.rule for stub "
println "test: call aowrule.execute"
aowrule.execute() //think this must be getting a new prototype object which is why not getting called
then : "test execute closure ran as expected "
mockres == "did nothing"
}
テストを実行すると、最初のテストは問題なく成功し、2 番目のテストは失敗し、myclos
は呼び出されません。次に何を試せばいいですか?
質問への説明
私はこれを 1 つのグルーヴィーなテスト ファイルにまとめてみました。
package org.easyrules.spring
import groovy.mock.interceptor.StubFor
import groovy.transform.EqualsAndHashCode
import groovy.util.logging.Slf4j
import groovyx.gpars.activeobject.ActiveMethod
import groovyx.gpars.activeobject.ActiveObject
import org.junit.runner.RunWith
import org.softwood.RulesApplication
import org.springframework.beans.factory.annotation.Autowire
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.actuate.autoconfigure.ManagementContextConfiguration
import org.springframework.context.ApplicationContext
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner
import spock.lang.Specification
import spock.lang.Unroll
import spock.lang.*
/**
* Created by William on 10/03/2016.
*/
class TestRule {
Closure dynamicExecute = {println "default rule Execution "}
def exec () {
dynamicExecute()
}
}
class TestWrapperRule {
@Delegate TestRule rule
TestWrapperRule () {
rule = new TestRule()
}
void execute () {
rule.exec()
}
}
@ActiveObject
@Slf4j
@EqualsAndHashCode
class TestAOWrapperRule {
@Delegate TestRule rule
TestAOWrapperRule () {
rule = new TestRule()
}
@ActiveMethod
void execute () {
rule.exec()
}
}
class AORuleSpecAllInOneSpecTest extends Specification {
def "test simple closure setting mockres value - basics " () {
given:
def mockres = ""
def clos = {
mockres = "did nothing";
println "script: in stub closure, setting mockres to " + mockres + " mockres dump > " + mockres.dump();
}
when:
clos() //call closure
then: "test mockres was set when closure called "
mockres == "did nothing"
}
def "set execution closure using spock mock on simple Wrapper rule when doing an execute action " () {
given:"setup of stub for testing "
def mockres = ""
def clos = {
mockres = "did nothing";
println "script: in stub closure, setting mockres to " + mockres + " mockres dump > " + mockres.dump();
}
TestWrapperRule wrule = new TestWrapperRule ()
//spock mock
TestRule stub = Mock ()
//1*stub.exec() >> {clos()} // pretend this has been set
1*stub.getDynamicExecute() >> {clos()} // pretend this has been set
println "test: save stub on wrule"
wrule.rule = stub
when : "call execute() on wrule.rule for stub "
println "test: call wrule.execute"
//wrule.exec()
wrule.dynamicExecute
then : "test execute closure ran as expected "
mockres == "did nothing"
}
def "set execution closure using spock mock on AO Wrapper rule when doing an execute action " () {
given:"setup of stub for testing "
def mockres = ""
def clos = {
mockres = "did nothing";
println "script: in stub closure, setting mockres to " + mockres + " mockres dump > " + mockres.dump();
}
TestAOWrapperRule aowrule = new TestAOWrapperRule ()
//spock mock
TestRule stub = Mock ()
stub.exec() >> {clos()} // pretend this has been set
println "test: save stub on aowrapper"
aowrule.rule = stub
when : "call execute() on aowrule.rule for stub "
println "test: call aowrule.execute"
aowrule.execute()
//aowrule.exec() //if you then call the @delegate func without the activeMethod the test will work
then : "test execute closure ran as expected "
mockres == "did nothing"
}
}
注意すべき重要な点は、最後のテストです。Mock でクロージャーの出力を実行するという期待を設定しました。これによりmockres
変数が更新され、then: 句で設定されていると断言できます。
テストを呼び出すと、@ActiveMethod execute()
このように失敗します
...script: in stub closure, setting mockres to did nothing mockres dump > <java.lang.String@c141192c value=did nothing hash=-1052698324>
Condition not satisfied:
mockres == "did nothing"
| |
"" false
11 differences (0% similarity)
(-----------)
(did nothing)
これは、モックレスがクロージャー呼び出しによって設定されなかったことを示しています。
次の aowrule.exec() 行のコメントを外すexec()
と、ActiveMethod が使用する非表示のアクターを使用せずに、委任された呼び出しが直接呼び出されます。その次の行を実行すると、クロージャーが正しく呼び出され、モックが設定されます。
したがって、問題には、その背後にある隠れたアクターを介してこれを行うときにモックが機能することをテストしようとする何かがあり@ActiveMothod
ます。
実際には、スクリプトとして実行するだけでコードが期待どおりに機能していると思います(コードでの実際の使用)。ただし、ActiveMethod が呼び出されたときに期待される動作を証明するテストを作成できないようです。
GPAR のような非同期コードのモックを使用してスポック テストを作成するには、何か賢いことをしなければならないのでしょうか。