【问题标题】:How can I use Mockito to eliminate an external dependency?如何使用 Mockito 消除外部依赖?
【发布时间】:2017-05-08 23:06:50
【问题描述】:

我在理解 Mockito 的概念时遇到了一些麻烦。我编写了一个小程序来提供帮助,但我无法让它做我想做的事情。

这是我的代码:

// WeatherDemo.java:

package com.abc;

public class WeatherDemo {
    public String getForecast() {
        // Get the high remperature for today, and return back to the caller one of these values:
        // cold, mild, or hot
        // cold will be returned if the high temp is forecast to be less than 60.
        // hot will be returned if the high temp is forecast to be more than 79.
        // Otherwise, mild will be returned (this indicates a high temp in the 60s or 70s).
        int highTemp = getHighTemp();
        if (highTemp < 60)
            return("cold");
        if (highTemp > 79)
            return("hot");
        return("mild");
    }

    public int getHighTemp() {
        // Because this is a demo, we don't have access to any source (web service, DB, etc.) to get the high temp.
        // Just hard code a value here, but remember that if this were a real application, we would be dynamically
        //   retrieving the day's high temperature from some external source.
        int highTemp = 32;
        return(highTemp);
    }
}

================================================ ===================================

// TestWeatherDemo.java:

package com.abc;

import static org.mockito.Mockito.*;
import static org.junit.Assert.*;
import org.junit.Test;
import org.mockito.Mockito;

public class TestWeatherDemo {
    @Test
    public void testWeatherReport() {
        WeatherDemo testMockito = Mockito.mock(WeatherDemo.class);
        WeatherDemo testJUnit = new WeatherDemo();

        when(testMockito.getHighTemp()).thenReturn(90);
        assertEquals("hot", testJUnit.getForecast());
    }
}

================================================ ===================================

基本上,我想在 getForecast() 上运行 JUnit。根据当天的高温,它会返回冷、温和或热。要获得高温,它调用 getHighTemp()。让我们假设 getHighTemp() 调用 Web 服务来获取温度(我硬编码一个值只是为了测试目的)。因为这是一个外部资源,所以我的 Junit 没有通过隔离测试,根本就不是真正的单元测试。更不用说 getHighTemp() 每次调用都不会返回相同的值。

因此,我想模拟 getHighTemp(),告诉它始终返回 90 的温度。

Mockito 测试从 testWeatherReport() 运行。这就是我卡住的地方。这样做时,我可以模拟 getHighTemp() 以返回 90:

当(testMockito.getHighTemp()).thenReturn(90);

但是,当从 getForecast() 调用时,我无法让它返回 90。断言变得“冷”,因为它选择的是 32,而不是 90。

Mockito 背后的整个想法不就是我可以模拟一个方法并准确地告诉它要返回什么,以消除外部依赖吗?如果从 getForecast() 调用 getHighTemp() 不会返回 90,我看不出 Mockito 的目的。我在这里想念什么?感谢您的帮助和启发。

比尔

【问题讨论】:

    标签: java unit-testing junit mocking mockito


    【解决方案1】:

    您实际上是在问“我如何在课堂上模拟一种方法,同时测试其余方法”。可以使用 Mockito - 在文档中搜索“部分模拟”。然而,它(几乎)总是表明您的代码结构不良并且需要重构。如果您正在测试访问要模拟的接口的类,则表明您应该将该接口声明为interface,然后将实现传递给该类。这有两个效果:首先,它允许您在不更改接口的情况下更改实现;其次,它使类可测试。

    所以在你的情况下:

    public interface TempSupplier {
        int getHighTemp();
        int getLowTemp();
    }
    
    public class WeatherDescriber {
        private final TempSupplier tempSupplier;
    
        public WeatherDescriber(TempSupplier tempSupplier) {
            this.tempSupplier = tempSupplier;
        }
    
        public String getForecast() {
            int highTemp = tempSupplier.getHighTemp();
            ...
        }
    }
    
    @Test
    public void testForecast() {
        TempSupplier supplier = mock(TempSupplier.class);
        when(supplier.getHighTemp()).thenReturn(90);
        WeatherDescriber describer = new WeatherDescriber(supplier);
        assertThat(describer.getForecast(), is("Hot"));
    }
    

    我通常将模拟拆分为一个单独的方法,以便您轻松测试:

    private WeatherDescriber getDescriber(int lowTemp, int highTemp) {
        TempSupplier supplier = mock(TempSupplier.class);
        when(supplier.getLowTemp()).thenReturn(lowTemp);
        when(supplier.getHighTemp()).thenReturn(highTemp);
        return new WeatherDescriber(supplier);
    }
    
    @Test
    public void testDescribeVariousTemps() {
        assertThat(getDescriber(10, 20).getForecast(), is("cold"));
        assertThat(getDescriber(30, 35).getForecast(), is("cold"));
        assertThat(getDescriber(40, 45).getForecast(), is("warmer"));
        assertThat(getDescriber(90, 130).getForecast(), is("melting"));
    } 
    

    【讨论】:

    • 感谢@Sprinter。部分模拟对我有用,而且使用起来非常简单。因为这个例子是针对我自己的 Mockito 训练的,所以我对目前的结果很满意。接下来我将重构我的代码,看看我还能从中学到什么。
    【解决方案2】:

    在您的示例中,testMockitotestJUnit 是不同的对象 - 您在 testMockito 上模拟了该方法,但 testJUnit 具有该方法的实际实现,返回 32。

    您可能需要考虑将代码分成两个类 - 然后您可以将 getHighTemp() 中的代码作为主类的依赖项。这是您将在测试中提供模拟和实际运行代码的实际实现的地方。 (也许是某种Dependency Injection

    以这种方式构建的代码似乎遵循Single Responsibility Principle - 这样做的好处是,例如,如果您需要更改天气数据供应商,它会更容易一些。当你说“因为这是一个外部资源,我的 Junit 没有通过隔离测试”时,我认为你无论如何都会沿着这些路线走。

    【讨论】:

    • 谢谢马特。为了快速修复,我使用了@Sprinter 描述的部分模拟。我还将尝试您的建议并将该类分为两个类,以将模拟方法与真实方法分开。
    猜你喜欢
    • 2011-11-22
    • 1970-01-01
    • 1970-01-01
    • 2022-12-21
    • 2011-10-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-14
    相关资源
    最近更新 更多