一.Android单元测试 Mockito的简单用法

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/ligen52/article/details/54124488

二.Android单元测试 Mockito的更多用法(1)
三.Android单元测试 PowerMock给私有变量赋值
四.Android单元测试 PowerMock mock静态方法

基于以下开源框架
1.Mockito
2.PowerMock

本篇文章主要讲Android开发时,如何使用以上开源框架来进行单元测试。

正文:
创建一个工程,引入一下dependency

testCompile 'junit:junit:4.12'

    testCompile ('org.powermock:powermock-api-mockito:1.6.2') {
        exclude module: 'hamcrest-core'
        exclude module: 'objenesis'
    }

    testCompile ('org.powermock:powermock-module-junit4:1.6.2') {
        exclude module: 'hamcrest-core'
        exclude module: 'objenesis'
    }

    testCompile "org.mockito:mockito-core:1.10.19"

这个程序是我之前做过的一个视频会议App,这里拿来做例子的是会议界面的代码。这里我们关注的主要是被测试类MeetingPresenterImp,所以这个被测试的类所依赖的类(MeetingAudioManager,MeetingController)的具体功能代码并不需要关注.
事实是有了Mokito来mock这些类之后,也并不需要关注这些依赖类的方法具体实现,我们只关注这些依赖类的是否在某些情况下被调用,或者指定它的方法的返回值.
我们这里程序使用的是MVP架构,即MODEL,VIEW,PRESENTER,我们为了测试轻量级,Presenter一定要尽量写成纯java类,这样才能方便测试,如果引入了过多的Android类,会非常难mock,测起来十分麻烦,那么就需要使用Robolectric来测试了,这里我们暂时不讲它的用法.
MainActivity.java
进行按钮的初始化,及点击事件绑定
点击cameraButton,会让MeetingPresenter进行摄像头的开关操作
点击speakerButton,会让MeetingPresenter进行扬声器的开关操作

public class MainActivity extends AppCompatActivity implements MeetingContract.MeetingView, View.OnClickListener {

    Button cameraButton;

    Button speakerButton;

    MeetingContract.MeetingPresenter meetingPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        meetingPresenter = new MeetingPresenterImp(new MeetingController(), new MeetingAudioManager());

        initViews();
    }

    private void initViews() {
        cameraButton = (Button) findViewById(R.id.camera_button);
        speakerButton = (Button) findViewById(R.id.camera_button);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.camera_button:
                meetingPresenter.toggleCamera();
                break;
            case R.id.speaker_button:
                meetingPresenter.toggleSpeaker();
                break;
        }
    }
}

MeetingContract.java
接口定义

public class MeetingContract {

    interface MeetingView {
    }

    interface MeetingPresenter {
        void toggleCamera();

        void toggleSpeaker();
    }
}

MeetingPresenterImp.java
MeetingPresenter的实现类,负责逻辑的处理.

MeetingContract.MeetingPresenter {

    private MeetingController meetingController;

    private MeetingAudioManager audioManager;

    public MeetingPresenterImp(MeetingController meetingController, MeetingAudioManager audioManager) {
        this.meetingController = meetingController;
        this.audioManager = audioManager;
    }

    @Override
    public void toggleCamera() {
        meetingController.toggleCamera();
    }

    @Override
    public void toggleSpeaker() {
        if (audioManager.isSpeakerOn()) {
            audioManager.turnOffSpeaker();
        } else {
            audioManager.turnOnSpeaker();
        }
    }
}

MeetingAudioManager.java
音频管理类

public class MeetingAudioManager {

    public boolean isSpeakerOn() {
        //判断是否扬声器开启
        return false;
    }

    public void turnOffSpeaker() {
        //关闭扬声器
    }

    public void turnOnSpeaker() {
        //开启扬声器
    }
}

会议Controller,与C++层通信的类

MeetingController.java
package sample.mars.com.androidutsample;

public class MeetingController {

    public void toggleCamera() {
        //请求C++层切换摄像头
        toggleCameraNative();
    }

    private native void toggleCameraNative();
}

我们现在要测试的就是上面的MeetingPresenterImp类,在此分享一个mac中AS的快捷键,快速新建test类

选中要测试的方法,并把测试类放到跟MeetingPresenter相同的包中

在新建好的测试类上方加上注解@RunWith(MockitoJUnitRunner.class)
我们要测试的是MeetingPresenterImp类,为了构造一个可测试的环境,让我们测试时不被一些它的依赖类的复杂情况所影响,可任意的控制它的依赖对象的状态或者方法的返回值,我们这里对MeetingPresenterImp的两个依赖类MeetingController和meetingAudioManager进行mock,在后面,我们就可以控制这两个类的方法的返回值,并可以检验这两个mock对象的某些方法是否被调用.

@RunWith(MockitoJUnitRunner.class)
public class MeetingPresenterImpTest {

    @Mock
    MeetingController meetingController;

    @Mock
    MeetingAudioManager meetingAudioManager;

    MeetingPresenterImp meetingPresenterImp;

    @Before
    public void setUp() throws Exception {
        meetingPresenterImp = new MeetingPresenterImp(meetingController, meetingAudioManager);
    }
}

在下面的文章中,我们将真正开始写测试代码.
在前面我们已经写好了一个用来测试的例子,并创建好了测试类,现在开始写测试代码.
首先,我们从最简单的toggleCamera()方法开始测试,那么我们如何测试MeetingPresenter类的toggleCamera()方法运行正常呢?
我们只需要验证MeetingController类的toggleCamera()方法被执行即可,这里将用到Mockito的Verify()方法.
Mockito.verify()可以检验被mock的类的特定方法是否被调用,不过前提是在测试类中能访问到该方法,如果该方法是private的或者其他情况.

    @Override
    public void toggleCamera() {
        meetingController.toggleCamera();
    }

现在写出测试代码,但是我们这里故意将验证条件写错,去验证meetingAudioManager的turnOnSpeaker()是否执行,看会发生什么:

@Test
    public void toggleCamera() throws Exception {
        meetingPresenterImp.toggleCamera();

        //Mockito.verify(meetingController).toggleCamera();
        Mockito.verify(meetingAudioManager).turnOnSpeaker();
    }

选中方法名,点击右键,选中 Run “toggleCamera()”,得到了如下错误提示:


也就是说,在meetingPresenterImp.toggleCamera()执行后,meetingAudioManager的turnOnSpeaker()并没有执行,所以测试失败.
现在在把测试代码改回验证meetingController的toggleCamera()是否执行.
然后选中方法名,点击右键,选中 Run “toggleCamera()”

@Test
    public void toggleCamera() throws Exception {
        meetingPresenterImp.toggleCamera();

        Mockito.verify(meetingController).toggleCamera();
    }

结果如下,我们的测试通过了,也就是说,在meetingPresenterImp.toggleCamera()执行后,MeetingController的toggleCamera()得到了执行.
那么,知道了我们这个测试通过了有什么用呢?举个栗子,如果在以后的代码重构中,某个开发人员不小心将meetingPresenterImp.toggleCamera()中meetingController.toggleCamera()这句代码误删了,那么我们再运行测试用例的时候,发现测试不通过,我们就知道原因是什么了,当然,真是开发中,情况不会这么简单.写测试的一个重要作用就是为了方便重构,每次改完代码准备提交到代码仓库中时,运行一下,检查是否所有测试用例都通过了,如果没有通过,就要查查是什么原因,并让它再次通过为止.

接下来,来验证MeetingPreImp.toggleSpeaker()方法

@Override
    public void toggleSpeaker() {
        if (audioManager.isSpeakerOn()) {
            audioManager.turnOffSpeaker();
        } else {
            audioManager.turnOnSpeaker();
        }
    }

这里有两个路径,根据audioManager.isSpeakerOn()的结果来执行不同操作.
那么对应的,我们就应该写两个测试用例来测试了,话不多说,我们来看看代码:
这里同样的,我们故意将验证条件写错,实际应该验证meetingAudioManager的turnOffSpeaker()方法是否执行,但是写成验证turnOnSpeaker()是否执行.

@Test
    public void should_turn_off_speaker() throws Exception {
        Mockito.when(meetingAudioManager.isSpeakerOn()).thenReturn(true);

        meetingPresenterImp.toggleSpeaker();

        //Mockito.verify(meetingAudioManager).turnOffSpeaker();
        Mockito.verify(meetingAudioManager).turnOnSpeaker();
    }

得到的结果是:

错误提示告诉我们,也是告诉我们turnOnSpeaker()并没有执行,测试不通过.

接下来,把测试用例代码改回正确的验证条件,那么,测试也就能通过了.
那么这个例子对我们实际开发有什么帮助呢?如果我们重构代码时,不小心把if判断中的操作写反了,我们就能借助单元测试,快速的发现问题了.

 @Test
    public void should_turn_off_speaker() throws Exception {                      Mockito.when(meetingAudioManager.isSpeakerOn()).thenReturn(true);

        meetingPresenterImp.toggleSpeaker();

        Mockito.verify(meetingAudioManager).turnOffSpeaker();
    }
  在以上代码中,我们使用了Mockito的另一个方法when,它能够让mock对象在调用有返回值的方法时,返回我们指定的结果,我们就不需要去操心这个mock方法内部是如何运作的,可以快速的去测试某条路径.
  有了上面的介绍,我们很快能写出以下的测试用例了,它的测试结果也是通过的.
 @Test
    public void should_turn_on_speaker() throws Exception {
        Mockito.when(meetingAudioManager.isSpeakerOn()).thenReturn(false);

        meetingPresenterImp.toggleSpeaker();

        Mockito.verify(meetingAudioManager).turnOnSpeaker();
    }

这篇文章,介绍了mockito的最基本的两种用法,在后面的文章中,将同样通过例子,介绍更复杂一些的用法.

展开阅读全文

没有更多推荐了,返回首页