Spock groovy - 如何模拟同一类中的方法?

Kar*_*lli 1 java groovy mocking mockito spock

如何在测试类中mock私有方法和不同类中的方法?

class MyClass {
    private final Retriever<ScoreData> retriever;
    private DataStore<Model> dataStore;
    private String gameName;

    public void MyClass(Retriever<ScoreData> retriever, DataStore<Model> dataStore, String gameName) {
        this.retriever = retriever;
        this.dataStore = dataStore;
        this.gameName = gameName;
    }

    public void process(GameHolder<G> games) {
        // Business Logic
        for (Game<G> game : games){
        Integer score = game.getScore();
        Integer playerId = game.getPlayerId();
        Integer finalScore = getScore(game);
        computeScore(score, finalScore);
        }
    }

    private Integer computeScore(int score, int finalScore) {
        // Runs some business logic and returns O3
        return score + finalScore;
    }

    private Integer getScore(Game game) {
        // Runs some business logic and returns O3
        String dbName = game.getDbName();
        DBRetriever ret = new DBRetriever(dbName)
        if (dbName.equals("gameDB"){
            return ret.getFinalScore(dbName);
        }
        return -1;
    }

}
Run Code Online (Sandbox Code Playgroud)

下面是我当前对 Spock 的实现,我不确定如何实现对象的模拟。

@Subject
def obj

def "this is my test"(){
    given:
    Object1 obj1 = Mock(Object1)
    Object2 obj2 = Mock(Object2)
    Object3 obj3 = Mock(Object3)

    def myClassObject = new MyClass(obj1, obj2, obj3)

    when:
    myClassObject.process(new Object4())

    then:
    1 * getScore()
    1 * computeScore()

}
Run Code Online (Sandbox Code Playgroud)

如何模拟computeScore和getScore函数以及如何为对象obj1、obj2、obj3分配初始值?

注意:我只是想process()在这里测试方法。但 process 方法是从内部调用私有方法。我希望能够返回私有方法的模拟值,而不是执行私有方法。

编辑:Retriever 和 DataStore 是接口,它们各自的实现是 ScoreData 和 Model。

kri*_*aex 5

注意:我只是想测试process()在这里测试方法。但 process 方法是从内部调用私有方法。我希望能够返回私有方法的模拟值,而不是执行私有方法。

您不应该这样做,因为MyClass您的班级正在接受测试。如果对私有方法进行存根,则无法用测试覆盖它们内部的逻辑。相反,如果在这些私有方法中使用注入的模拟,您应该确保注入的模拟按照您希望的方式运行(通过存根方法)。不幸的是,您决定不显示代码的关键部分,即使确切的答案取决于它。相反,您用注释“一些业务逻辑”替换了它们,这不是很有帮助,因为您的业务逻辑就是您想要测试的内容。你不想把它消灭掉。


所以请不要做我在这里向您展示的事情,我只是因为您问了才回答。

为了存根一个方法,它不能是私有的,因为从技术上讲,间谍、模拟或存根始终是子类,或者原始方法和子类不能继承甚至调用私有方法。因此,您需要使方法受保护(以便子类可以使用或覆盖它们)或将其限制在包范围内。我推荐前者。

但是您不能使用普通的模拟或存根作为被测试类的替代品,因为您只想存根业务逻辑的一部分(有问题的两个方法),而不是整个逻辑(您想保留process())。因此,您需要部分模拟。为此,您可以使用间谍。

虚拟依赖类:

package de.scrum_master.stackoverflow.q60103582;

public class Object1 {}
Run Code Online (Sandbox Code Playgroud)
package de.scrum_master.stackoverflow.q60103582;

public class Object2 {}
Run Code Online (Sandbox Code Playgroud)
package de.scrum_master.stackoverflow.q60103582;

public class Object3 {}
Run Code Online (Sandbox Code Playgroud)
package de.scrum_master.stackoverflow.q60103582;

public class Object4 {}
Run Code Online (Sandbox Code Playgroud)

被测类:

package de.scrum_master.stackoverflow.q60103582;

public class MyClass {
  private Object1 o1;
  private Object2 o2;
  private Object3 o3;

  public MyClass(Object1 o1, Object2 o2, Object3 o3) {
    this.o1 = o1;
    this.o2 = o2;
    this.o3 = o3;
  }

  public void process(Object4 o4) {
    System.out.println("process - business Logic");
    Object2 result = getScore("dummy ID");
    Object3 obj = computeScore(result);
  }

  protected Object3 computeScore(Object2 result) {
    System.out.println("computeScore - business logic");
    return o3;
  }

  protected Object2 getScore(String id) {
    System.out.println("getScore - business logic");
    return o2;
  }
}
Run Code Online (Sandbox Code Playgroud)

斯波克测试:

package de.scrum_master.stackoverflow.q60103582

import spock.lang.Specification

class MyClassTest extends Specification {
  def "check main business logic"(){
    given:
    Object1 obj1 = Mock()
    Object2 obj2 = Mock()
    Object3 obj3 = Mock()

    MyClass myClass = Spy(constructorArgs: [obj1, obj2, obj3])

    when:
    myClass.process(new Object4())

    then:
    1 * myClass.getScore(_) //>> obj2
    1 * myClass.computeScore(_) //>> obj3
  }
}
Run Code Online (Sandbox Code Playgroud)

在这里您可以了解如何检查间谍上的交互。但请注意,computeScore(_)andgetScore(_)仍然会被执行,正如您在控制台日志中看到的那样:

process - business Logic
getScore - business logic
computeScore - business logic
Run Code Online (Sandbox Code Playgroud)

如果取消最后两行代码末尾的注释

    1 * myClass.getScore(_) >> obj2
    1 * myClass.computeScore(_) >> obj3
Run Code Online (Sandbox Code Playgroud)

实际上,您将避免完全执行这两个(受保护的)方法,并用存根结果替换它们。控制台日志将更改为:

process - business Logic
Run Code Online (Sandbox Code Playgroud)

但我再说一遍:不要这样做。相反,请确保注入的模拟显示正确的行为,以便您可以实际执行测试类中的方法。这就是测试的目的,不是吗?

  • 这是不可测试代码的典型案例。在该方法内部,您将创建一个依赖项,并将其分配给一个局部变量。您必须重构“DBRetriever”,使其可通过构造函数、方法参数或设置器注入。数据库连接应该被模拟,但这只有在您能够以某种方式注入模拟时才有可能。您无法测试代码告诉您要重构它。 (3认同)