• Android测试:从零开始2——local单元测试


    上一篇分析了android项目的测试分类,这一篇讲local单元测试。

    参考android官方文档。

    测试前需要配置测试环境,新建项目后,目录下会出现app/src/test/java/文件夹,这个文件夹是用于存放local单元测试代码的(就是与androidAPI无关的测试)。

    在build.gradle配置文件中增加测试依赖:

    dependencies {
        // Required -- JUnit 4 framework
        testCompile 'junit:junit:4.12'
        // Optional -- Mockito framework
        testCompile 'org.mockito:mockito-core:1.10.19'
    }

     使用JUnit4测试需要了解下几个基本的注解:

    @Before:

    使用这个注解可以做一些在测试之间的准备工作。在每一个测试方法之前都会调用这块代码。可以定义多个@Before块,但是多个的调用顺序是不确定的。

    @After:

    这个注解定义一些测试结束执行的清除工作。在每个测试方法结束后都会调用这块代码。可以定义多个@After块,但是多个的调用顺序是不确定的。

    @Test:

    这个注解标记测试的方法,一个测试类当中可以定义多个测试方法,每个测试方法都需要使用@Test标记。

    @Rule:

    使你可以以复用的方式灵活的增加和重新定义每个测试方法的行为。在Android测试中会使用Android Testing Support Library提供的rules,如:ActivityTestRule 或者 ServiceTesteRule。

    @BeforeClass:

    使用这个注解为测试类设置一个静态方法,一个测试类里面只执行一次。这个方法通常注解一些耗时比较长的操作,如连接数据库。

    @AfterClass:

    使用这个注解设置一个测试类运行结束后执行的方法。这个方法通常用来释放@BeforeClass里面分配的资源。

    @Test(timeout=)

    这个注解用来设置一个超时参数,如果测试在设置的时间内没有执行完,就会抛出异常。

    另外有时候需要模拟Android的代码,可以使用@Mock注解来模拟。

    以下是官网上的一个简答实例:

    import org.junit.Test;
    import java.util.regex.Pattern;
    import static org.junit.Assert.assertFalse;
    import static org.junit.Assert.assertTrue;
    
    public class EmailValidatorTest {
    
        @Test
        public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
            assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
        }
        ...
    }
    

    使用junit.Assert方法来比较运行结果和期望值。为了使测试更直观,可以使用Hamcrest(is()和equal())来匹配结果和期望值。

    接下来以googlesamples的Android Architecture Blueprints为例,分析下单元测试在项目中的使用。

    我取的是todo-mvc-dagger分支的代码,项目有两个Variant(mock和prod)。mock的项目结构如下:

    prod的项目结构如下

    项目是MVP架构,将业务逻辑从Activity中独立出来,放在presenter里面,对presenter非常适合local单元测试,执行相对比较快。

    看下mock下的test里面的TaskDetailPresenterTest测试:

    package com.example.android.architecture.blueprints.todoapp.taskdetail;
    
    import com.example.android.architecture.blueprints.todoapp.data.Task;
    import com.example.android.architecture.blueprints.todoapp.data.source.TasksDataSource;
    import com.example.android.architecture.blueprints.todoapp.data.source.TasksRepository;
    
    import org.junit.Before;
    import org.junit.Test;
    import org.mockito.ArgumentCaptor;
    import org.mockito.Captor;
    import org.mockito.InOrder;
    import org.mockito.Mock;
    import org.mockito.MockitoAnnotations;
    
    import static org.mockito.Matchers.eq;
    import static org.mockito.Mockito.inOrder;
    import static org.mockito.Mockito.never;
    import static org.mockito.Mockito.verify;
    import static org.mockito.Mockito.when;
    
    /**
     * Unit tests for the implementation of {@link TaskDetailPresenter}
     */
    public class TaskDetailPresenterTest {
    
        public static final String TITLE_TEST = "title";
    
        public static final String DESCRIPTION_TEST = "description";
    
        public static final String INVALID_TASK_ID = "";
    
        public static final Task ACTIVE_TASK = new Task(TITLE_TEST, DESCRIPTION_TEST);
    
        public static final Task COMPLETED_TASK = new Task(TITLE_TEST, DESCRIPTION_TEST, true);
    
        @Mock
        private TasksRepository mTasksRepository;
    
        @Mock
        private TaskDetailContract.View mTaskDetailView;
    
        /**
         * {@link ArgumentCaptor} is a powerful Mockito API to capture argument values and use them to
         * perform further actions or assertions on them.
         */
        @Captor
        private ArgumentCaptor<TasksDataSource.GetTaskCallback> mGetTaskCallbackCaptor;
    
        private TaskDetailPresenter mTaskDetailPresenter;
    
        @Before
        public void setup() {
            // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
            // inject the mocks in the test the initMocks method needs to be called.
            MockitoAnnotations.initMocks(this);
    
            // The presenter won't update the view unless it's active.
            when(mTaskDetailView.isActive()).thenReturn(true);
        }
    
        @Test
        public void getActiveTaskFromRepositoryAndLoadIntoView() {
            // When tasks presenter is asked to open a task
            mTaskDetailPresenter = new TaskDetailPresenter(
                    ACTIVE_TASK.getId(), mTasksRepository, mTaskDetailView);
            mTaskDetailPresenter.start();
    
            // Then task is loaded from model, callback is captured and progress indicator is shown
            verify(mTasksRepository).getTask(eq(ACTIVE_TASK.getId()), mGetTaskCallbackCaptor.capture());
            InOrder inOrder = inOrder(mTaskDetailView);
            inOrder.verify(mTaskDetailView).setLoadingIndicator(true);
    
            // When task is finally loaded
            mGetTaskCallbackCaptor.getValue().onTaskLoaded(ACTIVE_TASK); // Trigger callback
    
            // Then progress indicator is hidden and title, description and completion status are shown
            // in UI
            inOrder.verify(mTaskDetailView).setLoadingIndicator(false);
            verify(mTaskDetailView).showTitle(TITLE_TEST);
            verify(mTaskDetailView).showDescription(DESCRIPTION_TEST);
            verify(mTaskDetailView).showCompletionStatus(false);
        }
    
    }
    

    这对任务详细页进行了完整的测试(我只粘贴了一个@Test方法)。在@Before方法里面调用

    MockitoAnnotations.initMocks(this);

    来模拟@Mock标记的TaskRepository和TaskDetailContract.View。

    在@Test测试方法里面,实例化presenter,并且调用start方法,使用mockito的verify方法来校验是否按预期的调用了TaskRepository和TaskDetailContract.View里面的方法。

    googlesamples里面有详尽完整的测试,路漫漫,其修远兮,囧囧囧囧。

  • 相关阅读:
    MD5加密 + 盐
    SQLite数据库--C#访问加密的SQLite数据库
    SQLite问题笔记
    微信开发--Two.菜单生成
    NOIP2018游记(更新完毕)
    HNOI2019 游记
    JXOI2017-2018 解题报告
    网络流20+4题解题报告(已更前20题)
    CodeForces528A (STLset)
    CodeForces 140C New Year Snowmen(堆)
  • 原文地址:https://www.cnblogs.com/tootwo2/p/6562099.html
Copyright © 2020-2023  润新知