【问题标题】:How to use Mockito for AsyncTask mocking?如何使用 Mockito 进行 AsyncTask 模拟?
【发布时间】:2013-05-28 12:56:26
【问题描述】:

我正在尝试改进我所做的一些单元测试,所以我正在使用 mockito 来模拟一些对象。所以现在我有两个问题:我是否应该尝试模拟 AsyncTask 以及(如果是)如何使用这个模拟对象来调用模拟对象的 doInBackground() 方法?

我已经尝试了几件事,但仍然无法正常工作。我的测试仍然给我一个断言失败:

AsyncTask 的执行方式(在 StudioActivity.java 中):

.....

LocationTask task = new LocationTask();
task.execute((LocationManager) this.getSystemService(Context.LOCATION_SERVICE));

我的异步任务(在 StudioActivity.java 中):

.....

public class LocationTask extends AsyncTask<LocationManager, Void, String> {
    private LocationManager mLocationManager;

    // private TextView mLocText;

    @Override
    protected void onPreExecute() {
        super.onPreExecute();

        // mLocText = (TextView) findViewById(R.id.textView_location);
        // mLocText.setVisibility(View.VISIBLE);
    }

    @Override
    public String doInBackground(LocationManager... params) {
        mLocationManager = params[0];
        Location loc = mLocationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
        Geocoder gcd = new Geocoder(getApplicationContext(), Locale.getDefault());
        List<Address> addresses;
        try {
            addresses = gcd.getFromLocation(loc.getLatitude(), loc.getLongitude(), 1);
            if (addresses.size() > 0) {
                return (addresses.get(0).getLocality() + ", " + addresses.get(0).getCountryName());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        mPreviewFragment.setLocation(result);
        mLocation = result;
    }

}

这是我尝试过的。

我的测试方法(在 TestStudioActivity.java 中):

@UiThreadTest
public void testGeolocalistationLabel() throws InterruptedException, ExecutionException{
    LocationTask mockLocationTask = mock(StudioActivity.LocationTask.class);

    when(mockLocationTask.doInBackground((LocationManager[]) any())).thenReturn("JUnit, Location"); 

    mStudioBoastPreviewFragment.getGeoloc().performClick();       
    assertEquals("JUnit, Location",mStudioBoastPreviewFragment.getGeolocTextView().getText());

    verify(mockLocationTask).doInBackground((LocationManager[]) any());
}

@UiThreadTest
public void testGeolocalistationLabel() throws InterruptedException, ExecutionException{
    LocationTask mockLocationTask = mock(StudioActivity.LocationTask.class);

    when(mockLocationTask.doInBackground((LocationManager) mStudioActivity.getSystemService(Context.LOCATION_SERVICE))).thenReturn("JUnit, Location"); 

    mStudioBoastPreviewFragment.getGeoloc().performClick();       
    assertEquals("JUnit, Location",mStudioBoastPreviewFragment.getGeolocTextView().getText());

    verify(mockLocationTask).doInBackground((LocationManager) mStudioActivity.getSystemService(Context.LOCATION_SERVICE));
}

失败:

junit.framework.AssertionFailedError: expected:<JUnit, Location> but was:<>
at  com.c4mprod.bhost.test.TestStudioActivity.testGeolocalistationLabel(TestStudioActivity.java:255)

没有模拟,它正在工作。但是测试执行时间很长。这就是我想使用 Mockito 的原因:提高测试速度并避免连接问题或服务器错误。我只是想测试一下位置是否显示在mStudioBoastPreviewFragment.getGeolocTextView()中。

在这两种情况下,我都有断言失败,所以我想知道哪种方法最好,以及如何使用它来模拟这个doInBackground()。或者,如果有另一种模拟 AsyncTask 的方法。

感谢您的帮助!

【问题讨论】:

  • 我不确定我是否正确理解了您的测试。您要测试的课程是什么? “mStudioBoastPreviewFragment”还是 LocationTask?我看不到您的模拟 LocationTask 是如何注入“mStudioBoastPreviewFragment”的。从我可以看到你的嘲笑 LocationTask 根本没有使用。
  • @joerx mStudioBoastPreviewFragment 是一个片段,其中包含我要测试的 TextView。这个片段是一个名为 StudioActivity 的 Activity 的一部分,LocationTask 是 StudioActivity 的一个内部类。当我单击 StudioBoastPreviewFragment 中的 ToggleButton 时,这将启动 AsyncTask LocationTask,它将定位用户并在我之前提到的 TextView 中显示他的位置。所以回答你的问题:我正在测试的类是 StudioBoastPreviewFragment。

标签: android junit android-asynctask mockito


【解决方案1】:

不幸的是,我对 Android 了解不多,但我对 Mockito 了解一些。

我必须问的一个问题是:

您的 StudioBoastPreviewFragment 是如何实际获取 LocationTask 的实例的?从您的测试代码中,我只能猜测您的模拟 LocationTask 没有注入 - mStudioBoastPreviewFragment 似乎是您的 Test 类的成员,而 mockLocationTask 在方法范围内声明和实例化,所以我无法想到您的mStudioBoastPreviewFragment 实际上正在使用您的模拟。

然后是 Mocks 的问题:

我看到 LocationTask 继承自 AsyncTask。 doInBackground(...) 如何被调用?是你调用它,还是被 AsyncTask 的内部逻辑调用(类似于 Thread.run() 被 Thread.start() 调用)?请记住,Mockito 创建了一个没有逻辑的哑代理对象。因此,如果您依赖 AsyncTask 调用 doInBackground(...) 的某些内部逻辑,则该内部逻辑将不会存在于您的 mock 中。

换句话说,如果这是一个单元测试,你不必关心是否首先调用 doInBackground(...),因为你的外部客户端代码不应该依赖于 AsyncTask 的内部或你的具体子类定位任务。回到 Thread 示例:您模拟 Thread.start(),而不是 Thread.run()。

如果你真的想进行单元测试,你应该分别测试这两个类,隔离任何一个类中的潜在错误。因此,如果操作正确,模拟 LocationTask 是一个好主意。

另一方面,如果您想测试两个组件之间的交互,这不再是真正的单元测试,而是完全不同的故事。

【讨论】:

  • 我的 doInBackground() 是这样调用的: - 在我的测试用例中,我通过以下方式单击 geoloc 按钮:solo.clickOnView(mStudioBoastPreviewFragment.getGeoloc()); - 在 StudioBoastPreviewFragment 中,我的 geoloc 按钮有一个 OnCheck 侦听器:geoloc.setOnCheckedChangeListener(((StudioActivity) getActivity())); -此侦听器在 StudioActivity 中调用一个方法,该方法执行:LocationTask task = new LocationTask(); task.execute((LocationManager) ... ); - 以此类推,doInBackground() 被调用...
  • 那么你真正需要存根的是 LocationTask.execute() - 就像这样:doReturn(...).when(LocationTask).execute(...)。调用 doInBackground() 的事实是 LocationTask 的一个实现细节,只要您测试 ViewFragment,您就不必担心。
  • 刚刚在您的代码中看到了另一个问题:似乎在您的侦听器中,您使用“New LocationTask()” - 如果您在 inside 下的类中创建依赖项的新实例测试,您根本无法模拟该依赖项,因为您无法实际告诉您的类使用模拟而不是真实对象。换句话说,你的耦合太紧了。这是一个设计问题。您应该尝试找到一种方法来注入 LocationTask 或将任务的内部逻辑移到其他地方,例如一些服务并模拟那个服务。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-08
  • 2012-04-05
  • 1970-01-01
相关资源
最近更新 更多