Android Junit测试

Junit单元测试

  1. 顾名思义 是对某个单元是否达到预期的测试,而单元在java里面倾向于一段代码一个方法或者一个类

  2. 基本配置

     //单元测试需要的类
     testImplementation 'junit:junit:4.13-beta-1'
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
     androidTestImplementation 'com.android.support.test:rules:1.0.2'
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.0'
     androidTestImplementation 'com.android.support.test.espresso:espresso-contrib:3.0.0'
     
    //android default 里面需要配置
     testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    
  3. 假设你写过java 的测试用例啊,那么我们安卓是有什么不同呢,这也是本文的终点所在,最重要的是我们写了一个单元测试,如何和我们要测试的四大组件进行绑定,如何查找里面的控件以及如何对其进行我们想要的操作,比如点击 、设置文本、长按等等。带着问题起飞

基础API

  1. 在src下面,as会默认为我们创建测试的包,如果没有就手动创建一个。
  2. 创建如下文件
//RunWith 我们可以理解为用什么来运行测试代码,我们这里使用Junit 的所以是如下代码,后面会有不同的情况,稍后再说
@RunWith(AndroidJUnit4.class)
public class SplashActivityTest {
    //@Rule通过这个注解绑定对应的Activity,并且测试类中每个方法都可以访问和修改该Activity中的数据,
    // 同时他保证了我们在测试的时候对应的Activity一定是开启的,这样写会自动帮我们开启当前的Activity
    @Rule
    public ActivityTestRule<SplashActivity> mActivityTestRule = new ActivityTestRule<SplashActivity>(SplashActivity.class);
    SplashActivity mActivity;

    TimerHandler mHandler=new TimerHandler();
    //ViewMatcher,有withId、withText、withClassName等等方法来定位View控件
    //ViewAction,有click()、longClick()、pressBack()、swipeLeft()等等方法来操作View控件
    //ViewAssertion,有isEnabled()、isLeftOf()、isChecked()等等方法来校验View控件状态
    @Test
    public void startSplash() {
    	//Espresso是用来进行逻辑处理的辅助类,用来如下 通过onView 获取到某个view通过perform方法来执行具体的 操作
		 Espresso.onView(ViewMatchers.withId(R.id.lyContainer))
                .perform(ViewActions.click());
		 /*Assert类中主要方法如下:
        方法名	方法描述
        assertEquals	断言传入的预期值与实际值是相等的
        assertNotEquals	断言传入的预期值与实际值是不相等的
        assertArrayEquals	断言传入的预期数组与实际数组是相等的
        assertNull	断言传入的对象是为空
        assertNotNull	断言传入的对象是不为空
        assertTrue	断言条件为真
        assertFalse	断言条件为假
        assertSame	断言两个对象引用同一个对象,相当于“==”
        assertNotSame	断言两个对象引用不同的对象,相当于“!=”
        assertThat	断言实际值是否满足指定的条件
        */
        //断言主要是用来进行代码的执行结果是否和我们预期的一样,如果一样测试通过,否则测试不通过。
		  Assert.assertThat(mActivity,Matchers.notNullValue());

    }

    /*
   * JUnit 4	描述
       @Test	将方法标记为测试方法。
       @Before	在每次测试之前执行,一般用于准备测试环境(初始化类等)。
       @After	在每次测试之后执行,用于清理测试环境 (例如删除临时数据,还原默认值等)。 它也可以拥有清理内存( It can also save memory by cleaning up expensive memory structures.)。
       @BeforeClass	在所有测试之前,执行一次。它一般用于执行time intensive activities,例如连接数据库等。使用该注解标记的方法需要定义为static void。
       @AfterClass	在所有的测试执行完成之后执行一次。 它一般用于清理一些 activities, 例如断开数据连接。使用该注解标记的方法需要定义为static void
       @Ignore or @Ignore("Why disabled")	标记该注解的测试方法是被禁用的。这对于实际代码做了修改而测试代码没有修改的情况是非常有用的,或者由于这条测试执行时间过长先不将其包含在测试中,最好是提供一下不去测试的原因。
       @Test (expected = Exception.class)	如果这个测试方法不抛出赋值的异常(Exception.class)将会失败。
       @Test(timeout=100)	如果这个测试方法执行超过100毫秒将会失败。
   * */
    @Before 
    public void setUp() throws Exception {
        //可以获取到当前Activity的对象,用来操作其内部的方法
        //这里before是测试前的准备工作,我们在这里可以利用id来查找一个View 后面可以来操作他
        mActivity = mActivityTestRule.getActivity();
    }
   static class TimerHandler extends Handler{
        public TimerHandler(){
            super(Looper.getMainLooper());
        }
    }
}
  • 如上的代码中我们基本看到了大部分的API
    绑定Activity还有另一个方式如下
@LargeTest
public class AndroidTestMainActivity extends ActivityInstrumentationTestCase2<MainActivity> {
    MainActivity mActivity;
    HandlerTask mHandlerTask = new HandlerTask();
	//注意这个构造函数,必须是这样的无参构造而且super里面传的当前Activity 的class ,否则会报错
    public AndroidTestMainActivity() {
        super(MainActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        mActivity = getActivity();
    }
    // 继承 ActivityInstrumentationTestCase2的这个写法,**方法名必须是以test开头**,否则是武大检测到有测试方法的。
    public void testMain() {
        assertThat(mActivity, notNullValue());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //Matchers.allOf 用来过滤View使用的,如果界面上有多个比如Fragment 如果Fragment有一样id 的View那么我们可以通过这个方法进行过滤,这里的条件是是否展示在桌面上。
        Fragment fragment = mActivity.getHomeFrament().getAdapter().getCurrentFragment();
        if (fragment != null && fragment instanceof NewNewsFragment) {
            ((NewNewsFragment) fragment).getDatas();
            for (int i = 0; i < 15; i++) {
                Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.rv_news_list), ViewMatchers.isDisplayed()))
                        .perform(ViewActions.swipeLeft());
                int item = getItem((NewNewsFragment) fragment);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (item > 2) {
//                    Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.rv_news_list), ViewMatchers.isDisplayed()))
//                            .perform(RecyclerViewActions.scrollToPosition(item));
                    Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.rv_news_list), ViewMatchers.isDisplayed()))
                            .perform(ViewActions.swipeUp());
                } else {
                    Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.rv_news_list), ViewMatchers.isDisplayed()))
                            .perform(ViewActions.swipeDown());
                }
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                Message message = Message.obtain();
                message.what = 100;
                message.obj = item;
                //所有修改View的 操作弹Taost都必须在主线程的,注意当前不是主线程的
                mHandlerTask.sendMessage(message);
                Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.rv_news_list), ViewMatchers.isDisplayed()))
                        .perform(RecyclerViewActions.actionOnItemAtPosition(item, ViewActions.click()));
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Espresso.pressBack();
                LogUtils.i("测试", "---------一次测试执行结束了-----");
            }
        }
    }
    int advertiseItem = 3;

    private int getItem(NewNewsFragment fragment) {
        int item = advertiseItem + 2;
        advertiseItem++;
        return item;
    }

    class HandlerTask extends Handler {
        public HandlerTask() {
            super(Looper.getMainLooper());
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 100:
                    Toast.makeText(mActivity, "点击了" + msg.obj, Toast.LENGTH_SHORT).show();
                    break;
            }
        }
    }
}
  1. 自动生成方法

     假如我们此时要去测试一个java类里面的方法,那我们as在方法上右键 Go to ->  Test然后as就把很多事情都干好
     了,你直接写测试逻辑就好了!
    

RecyclerView 处理

  1. 在android 里面列表最常见了,所以也避免不了测试RecyclerView,junit也对他做了相应的处理,上文其实是有的,这里因为重要拿出来再啰嗦一边,上代码时间不多说
	//就是这里,我们在查找到recyclerview的时候,我们对他的操作多了一个 RecyclerViewActions ,这个里面专门对他的操作
	//有3个action方法以及3个scroll方法,这里我拿scrollToPosition 举例,其他的用的话自行测试。
 Espresso.onView(Matchers.allOf(ViewMatchers.withId(R.id.rv_news_list), ViewMatchers.isDisplayed()))
                            .perform(RecyclerViewActions.scrollToPosition(item));
  • 综上 对Activity的处理我们基本覆盖了,当然还有一些情况比如Matchers里面的条件不符我们的需求,那么我们是可以自定义Matchers的。条件就根据自己的做相应的判断就行了。

命令行开启测试

//gradlew 开启测试如下两种:
//1.  Local unit test	Call the test task:  ./gradlew test (可以理解为java单元测试)
//2. Instrumented unit test-->   gradlew connectedAndroidTest  (可以理解为android单元测试)
//会在 path_to_your_project/module_name/build/outputs/reports/androidTests/connected/ directory
//目录下面生成 对应的检测文件,如果有报错不通过的时候里面会有详细的记录

截取部分内容可以大概看一下,它包括了我们要测试的类的信息,以及设备信息,还有就是错误的详细信息

<testsuite name="com.donews.firsthot.LoginActivityTest" tests="5" failures="4" errors="0" skipped="0" time="32.705" timestamp="2018-12-17T10:51:12" hostname="localhost">
  <properties>
    <property name="device" value="MI 5s - 8.0.0" />
    <property name="flavor" value="QIJIAN_TEST" />
    <property name="project" value="app" />
  </properties>
  <testcase name="testLogin" classname="com.donews.firsthot.LoginActivityTest" time="1.711">
    <failure>android.support.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with id: com.donews.firsthot:id/et_login_phonenumber


at dalvik.system.VMStack.getThreadStackTrace(Native Method)
at java.lang.Thread.getStackTrace(Thread.java:1536)
at android.support.test.espresso.base.DefaultFailureHandler.getUserFriendlyError(DefaultFailureHandler.java:90)
at android.support.test.espresso.base.DefaultFailureHandler.handle(DefaultFailureHandler.java:52)
at android.support.test.espresso.ViewInteraction.waitForAndHandleInteractionResults(ViewInteraction.java:312)
at android.support.test.espresso.ViewInteraction.desugaredPerform(ViewInteraction.java:167)
at android.support.test.espresso.ViewInteraction.perform(ViewInteraction.java:110)
at com.donews.firsthot.LoginActivityTest.testLogin(LoginActivityTest.java:48)
at java.lang.reflect.Method.invoke(Native Method)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at android.support.test.internal.runner.junit4.statement.RunBefores.evaluate(RunBefores.java:80)
at android.support.test.rule.ActivityTestRule$ActivityStatement.evaluate(ActivityTestRule.java:527)
。。。

构建流程

测试的的构建流程

如何写成gradle 任务

首先,上面提到了gradlew 命令来开启测试,那么我们要做成gradle task的形式的话,那就是把命令行做成task就完事了呗。但是如何做呢?不要慌,gradlew早已具备这个功能。撸一眼。

//用于测试所有的测试用例,
//Exec 就是指执行命令的类型,其他的设定好的像Delete Copy 肯定都不陌生。
task testAll(type: Exec) {
    //分组,可以再as右侧中的app 任务中找到分组,便于查找,不然会被分到other目录下
    group = "test"
    //执行 adb devices 命令  /k 标识执行完命令之后保留窗口  ,如果需要立即关闭则用 /c  其他的可以用 cmd /?  来查看
    //gradlew 是gradlewrapper的缩写,需要在工作空间里面执行,所以两条命令必须连着执行 用&& 链接
    //cd /d E:\\wokers\\Donews 进入项目目录下面,所以要改成自己项目的目录
    def path=rootProject.projectDir
    commandLine 'cmd', '/k', 'cd /d E:\\wokers\\Donews&& gradlew connectedAndroidTest'
    //执行
//    commandLine 'cmd', '/k','gradlew connectedAndroidTest'
    println("------------------我是一个Test测试中的输出-------------")
}

同步之后我们就会发现as右侧的gradle任务里面多了一个分组叫test ,里面有个testAll的Task,我们点击就可以开始测试全部的测试用例了!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值