56

我最近问了几个面向 jUnit 和 Mockito 的问题,但我仍然很难掌握它。这些教程都是针对非常简单的示例,所以我正在努力扩大我的测试用例以适用于我的课程。

我目前正在尝试为我在 webapp 中的一个代理中使用的方法编写一些测试用例。该方法与代理内部的其他几个方法交互以验证某些对象。我现在只想测试这种方法。

这是我尝试做的事情:

  1. 像这样创建我的代理的 Mockito 对象:

    MyProcessingAgent mockMyAgent = Mockito.mock(MyProcessingAgent.class);

  2. 使用 Mockito.when 设置存根(希望是正确的术语),如下所示:

    Mockito.when(mockMyAgent.otherMethod(Mockito.any(arg1)).thenReturn(requiredReturnArg);

  3. 尝试像这样执行我的方法:

    List myReturnValue = mockMyAgent.methodThatNeedsTestCase();

我期待的东西myReturnValue,但收到 0 代替,所以我尝试调试。当我调用该方法时,它永远不会执行。我在方法的第一行有一个永远不会被触及的调试点。

如果我想在类的一个方法中执行代码,但强制类中的其他方法(尝试与外部世界中的数据库交互的方法)返回伪造的值。Mockito 可以做到这一点吗?

看来我目前的方法不是正确的测试风格,但我不确定如何前进。我可以模拟我的类并像正常一样执行一种方法,而将其他方法存根以返回我的给定值,这样我就不必在测试这一种方法期间处理数据访问?

4

4 回答 4

85

您将 aMock与 a混淆了Spy

在模拟中,所有方法都被存根并返回“智能返回类型”。这意味着在模拟类上调用任何方法都不会执行任何操作,除非您指定行为。

在 spy 中,类的原始功能仍然存在,但您可以在 spy 中验证方法调用并覆盖方法行为。

你想要的是

MyProcessingAgent mockMyAgent = Mockito.spy(MyProcessingAgent.class);

一个简单的例子:

static class TestClass {

    public String getThing() {
        return "Thing";
    }

    public String getOtherThing() {
        return getThing();
    }
}

public static void main(String[] args) {
    final TestClass testClass = Mockito.spy(new TestClass());
    Mockito.when(testClass.getThing()).thenReturn("Some Other thing");
    System.out.println(testClass.getOtherThing());
}

输出是:

Some Other thing

注意:您真的应该尝试模拟正在测试的类的依赖关系而不是类本身。

于 2013-04-12T16:12:23.680 回答
7

因此,嘲笑被测类的想法是对测试实践的厌恶。你不应该这样做。因为你已经这样做了,所以你的测试是进入 Mockito 的模拟类而不是你的被测类。

间谍也不会起作用,因为这只提供了围绕间谍类的包装器/代理。一旦在类内部执行,它将不会通过代理,因此不会命中间谍。更新:虽然我相信 Spring 代理确实如此,但 Mockito 间谍似乎并非如此。m1()我设置了一个方法调用的场景m2()。我监视对象并存根m2()doNothing. m1()当我在测试中调用时,m2()没有达到类的。Mockito 调用存根。因此,使用间谍来完成所要求的事情是可能的。但是,我会重申,我认为这是不好的做法(恕我直言)。

您应该模拟被测类所依赖的所有类。这将允许您控制被测方法调用的方法的行为,因为您可以控制这些方法调用的类。

如果您的类创建其他类的实例,请考虑使用工厂。

于 2013-04-12T16:10:05.040 回答
4

你几乎明白了。问题是被测类CUT)不是为单元测试而构建的——它并不是真正TDD

这样想……

  • 我需要测试一个类的函数——我们称之为myFunction
  • 该函数调用另一个类/服务/数据库上的函数
  • 该函数还调用CUT上的另一个方法

在单元测试中

  • 应该创建一个具体的CUT@Spy在其上
  • 您可以@Mock使用所有其他类/服务/数据库(即外部依赖项)
  • 可以存根在CUT中调用的其他函数,但这并不是真正应该如何进行单元测试

为了避免执行您没有严格测试的代码,您可以将该代码抽象为可以@Mock编辑的东西。

在这个非常简单的示例中,创建对象的函数将很难测试

public void doSomethingCool(String foo) {
    MyObject obj = new MyObject(foo);

    // can't do much with obj in a unit test unless it is returned
}

但是使用服务获取 MyObject 的函数很容易测试,因为我们已经将难以/不可能测试的代码抽象为使该方法可测试的东西。

public void doSomethingCool(String foo) {
    MyObject obj = MyObjectService.getMeAnObject(foo);
}

因为可以模拟 MyObjectService 并验证 .getMeAnObject() 是使用foo变量调用的。

于 2013-04-12T16:29:00.197 回答
1

简短的回答

在你的情况下怎么做:

int argument = 5; // example with int but could be another type
Mockito.when(mockMyAgent.otherMethod(Mockito.anyInt()).thenReturn(requiredReturnArg(argument));

长答案

实际上你想做的事情是可能的,至少在 Java 8 中。也许你没有从其他人那里得到这个答案,因为我使用的是允许这样做的 Java 8,而这个问题是在 Java 8 发布之前(允许传递函数) ,不仅是其他函数的值)。

让我们模拟对数据库查询的调用。此查询返回 HotelTable 中所有 FreeRoms = X 和 StarNumber = Y 的行。我在测试期间期望的是,此查询将返回一个不同酒店的列表:每个返回的酒店具有相同的值 X 和 Y,而其他值,我将根据我的需要决定它们。下面的例子很简单,当然你也可以让它变得更复杂。

所以我创建了一个函数,它会返回不同的结果,但它们都有 FreeRoms = X 和 StarNumber = Y。

static List<Hotel> simulateQueryOnHotels(int availableRoomNumber, int starNumber) {
    ArrayList<Hotel> HotelArrayList = new ArrayList<>();
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Rome, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Krakow, 7, 15));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Madrid, 1, 1));
    HotelArrayList.add(new Hotel(availableRoomNumber, starNumber, Athens, 4, 1));

    return HotelArrayList;
}

也许 Spy 更好(请尝试),但我是在模拟课上这样做的。这里我是怎么做的(注意 anyInt() 值):

//somewhere at the beginning of your file with tests...
@Mock
private DatabaseManager mockedDatabaseManager;

//in the same file, somewhere in a test...
int availableRoomNumber = 3;
int starNumber = 4;
// in this way, the mocked queryOnHotels will return a different result according to the passed parameters
when(mockedDatabaseManager.queryOnHotels(anyInt(), anyInt())).thenReturn(simulateQueryOnHotels(availableRoomNumber, starNumber));
于 2018-01-19T15:54:38.450 回答