2

スポックの単体テストに関連するかなり奇妙な閉鎖の問題に遭遇し、誰かがこれを説明できるかどうか疑問に思いました。

dao、モデル、およびサービスを次のように想像すると、次のようになります。

interface CustomDao {
List<Integer> getIds();
Model getModelById(int id);
}

class CustomModel {
int id;
}

class CustomService {
CustomDao customDao

public List<Object> createOutputSet() {
    List<Model> models = new ArrayList<Model>();
    List<Integer> ids = customDao.getIds();
    for (Integer id in ids) {
        models.add(customDao.getModelById(id));
    }
    return models;
}
}

CustomService.createOutputSet を単体テストしたいと思います。次の仕様を作成しました。

class TestSpec extends Specification {

def 'crazy closures'() {
    def mockDao = Mock(CustomDao)
    def idSet = [9,10]

    given: 'An initialized object'
        def customService = new CustomService
        customService.customDao = mockDao

    when: 'createOutput is called'
        def outputSet = customService.createOutputSet()

    then: 'the following methods should be called'
        1*mockDao.getIds() >> {
            return idSet
        }

        for (int i=0; i<idSet.size(); i++) {
            int id = idSet.get(i)
            1*mockDao.getModelById(idSet.get(i)) >> {
                def tmp = new Model()
                int tmpId = id // idSet.get(i)
                return tmp
            }
        }

    and: 'each compute package is accurate'
        2 == outputSet.size()
        9 == outputSet.get(0).getId()
        10 == outputSet.get(1).getId()

}
}

ここで 2 つのことをテストしていることに注意してください。まず、モックで dao を初期化し、daos が正しく呼び出されて適切なデータを返すことを確認します。次に、適切な出力 (つまり " and:") が得られることを確認します。

トリッキーな部分は、メソッド パラメーターに関連するモック dao からモデルを返す必要がある for ループです。上記の例で単純な を使用するfor (__ in idSet)と、モデルは id 10: のみを返します outputSet.get(0).getId() == outputSet.get(1).getId() == 10。従来の for ループを使用し、モデルを で設定するとidSet.get(i)、 が得られますIndexOutOfBoundsException。これを機能させる唯一の方法は、ローカル変数 ( ) で値を取得し、id上記のように変数で設定することです。

私はこれが groovy クロージャーに関連していることを知っており、spock はそれらを実行する前に一連のクロージャーにモック呼び出しをキャプチャするのではないかと考えています。つまり、モデルの作成はクロージャーの外部状態に依存していることを意味します。int id = idSet.get(i)IndexOutOfBoundsException が発生する理由は理解していますが、クロージャーによってキャプチャされないのに、なぜキャプチャされるのかわかりiません。

違いはなんですか?

注: これは実際のコードではなく、私の課題の要点を示すために簡略化したものです。getIds() と getModelById() で 2 つの後続の dao 呼び出しを行うことはありません。

4

2 に答える 2

2

実行が延期されたクロージャから変更可能なローカル変数にアクセスすることは、Spock に固有ではないエラーの一般的な原因です。

int id = idSet.get(i) がクロージャによってキャプチャされるのに、i がキャプチャされない理由がわかりません。

前者は、値が定数である反復ごとに個別の巻き上げられた変数を生成します。後者は、値が時間の経過とともに (結果ジェネレーターが実行される前に) 変化する単一の巻き上げられた変数を生成します。

一時変数を導入して問題を解決する代わりに、より良い解決策 (@dmahapatro によって既に提供されています) は、int id ->クロージャー パラメーターを宣言することです。強制せずに呼び出しをスタブするのに十分であると判断された場合は、ループを完全に省略できます。さらに別の潜在的な解決策は、戻り値を熱心に構築することです。

idSet.each { id ->
    def model = new Model()
    model.id = id
    1 * mockDao.getModelById(id) >> model
}
于 2013-07-04T19:53:00.243 に答える
2

クロージャーによるスタブ中getModelById、クロージャーへの引数はメソッドの引数と一致する必要があります。以下のようなことを試してみると、ローカル変数はもう必要ありidませforん。

for (int i=0; i<idSet.size(); i++) {
            //int id = idSet.get(i)
            mockDao.getModelById(idSet.get(i)) >> {int id ->
                def tmp = new Model()
                tmp.id = id // id is closure param which represents idSet.get(i)
                return tmp
            }
        }

簡易版を使用することになりますeach

idSet.each {
    mockDao.getModelById(it) >> {int id ->
        def tmp = new Model()
        tmp.id = id // id is closure param which represents idSet.get(i)
        tmp
    }
}

メソッドがスタブ化されている場合、メソッドが呼び出される回数を気にする必要がありますか?

于 2013-06-30T03:54:41.680 に答える