早在去年的时候,经过一阶段针对“浓缩咖啡”Espresso框架的仔细品尝,浅品深品,不得不说,毕竟是谷歌力推的测试框架,就连AndroidStudio默认创建工程后都自带编译的包,果然不出意外的这个框架十分的强大,强大到什么程度,比如只在UI线程静止的时候再进行操作,自动异步更新测试监听以及UI线程,意味着只要定位准,手机不卡等一些因素不需要担心超时等页面刷新不同步等问题,不过也是有缺点的,就是学习成本比较高,特别是针对不同自定义控件以及情况的适配,网上有的文档资源特别少;当然,整体来说依旧是白盒自动化测试的首选,最好结合一下UiAutoMation使用,效果不错;
起初这个框架最让我眼前一亮的是这个框架在编写自动化代码的方式,通过直接引用静态方法来直接调用测试Api,同时同时同时(来个三遍),做到了定位+操作+检验,三个测试操作,一行代码搞定的精妙语法,哇,很强,我怎么就想不到封装成这个样儿;
后来逐步的使用,才发现这个水有点儿深,适配控件自动化着实有点儿费劲,这里先不做这方面的讨论了,只是浅浅的分享一下;
回到正题,下面简单介绍一下如何使用这个框架来编写测试代码
首先,拿到我们测试的源码,为什么要拿源码来白盒测试而不是黑盒这点会面会详细的说明,然后将项目导入到Android Studio中,首先保证导入的App能正常运行,再进行下一步的写自动化代码;
这里我也没有用什么源码,自己新建了一个Android项目,正常情况现在的AS一般会自动的将项目架构构建成如下所示的文件架构;
首先,第一个就是做Android开发使用的位置,里面会有Apk源码等等;
第二个是androidTest的文件夹所在的位置,依赖于Apk;
第三个就是Test的文件夹,主要是放一些单元测试的测试类;
我们Espresso测试类编写在androidTest的文件夹中;
简单说一下这个App的功能:
点击“Click There One”下方的TextView显示Click One Btn;
点击“Click There Two”下方的TextView显示Click Two Btn;
“Click There One”的id为:btn_one_click,text为:Click There one
“Click There Two”的id为:btn_two_click,text为:Click There two
注:页面代码以及Activity代码在最后;
页面如下图所示;
自动化测试代码如下
package heepay.com.espressoautotest;
import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see Testing documentation
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Rule
public ActivityTestRule rule = new ActivityTestRule(MainActivity.class, true);
@Test
public void useAppContext() throws Exception {
/*正常正确的情况*/
//通过text的方式定位按钮,然后click点击
onView(withText("Click There one")).perform(click());
//判断textView有没有正常改变
onView(withId(R.id.tv_test)).check(matches(withText("Clicked One Btn")));
onView(withText("Click There two")).perform(click());
onView(withId(R.id.tv_test)).check(matches(withText("Clicked Two Btn")));
//通过id的方式定位按钮,然后click点击
onView(withId(R.id.btn_one_click)).perform(click());
//判断textView有没有正常改变
onView(withId(R.id.tv_test)).check(matches(withText("Clicked One Btn")));
onView(withId(R.id.btn_two_click)).perform(click());
onView(withId(R.id.tv_test)).check(matches(withText("Clicked Two Btn")));
/*判断出异常的情况*/
//根据Id找到btn,判断text是否是Btn----后再点击
onView(withId(R.id.btn_one_click)).check(matches(withText("Btn----"))).perform(click());
//通过id的方式定位按钮,然后click点击
onView(withId(R.id.btn_two_click)).perform(click());
//判断textView的内容是否一致
onView(withId(R.id.tv_test)).check(matches(withText("Error")));
}
}
有注释,应该都很好理解,而且也是很简单的功能,暂不涉及很负责的东西;
前4条正常情况的测试,分别是根据Text获取两个按钮的定位,然后点击,之后判断文本是否正常的改变;
后2条是异常结果的测试:
首先是根据id获取到按钮1,检查按钮的text是否为Btn----,不是则不进行后面的操作;
其次是根据id获取到按钮1,点击后,检查文本是否变成“Error”
在执行结束测试后,结果如下所示
第一个异常的结果:
第二个异常的结果:
实在是太熟悉了,乍一看不知道的还以为是Junit下面做的测试,好吧,Espresso实际上是在Junit上面做的扩充,基于Junit的框架,所以当出现跟预期的值不同的时候,会出现
Expected: with text: is "Error"这样的字眼
一个简单的例子结束,这是基于源代码的白盒自动化测试;现在我们来说一下之前的问题为什么不推荐使用Espresso来进行黑盒测试;
第一:不知道大家注意到代码里面没有,Espresso是可以直接从Apk的R文件中根据Id来查询控件进行定位的,效率不知道高了多少,但是如果是黑盒测试,拿不到源码,这个withId功能将无法使用;
第二:成本很高,我也查到过网上面有的人也成功过Espresso黑盒测试,显示把测试代码所在的Apk包名与待测试的Apk包名一致,然后签名等等等等,成本实在是太高,而且还不一定成功,非常的不推荐;
第三:如果想正常的在自己的测试代码Apk中编写Espresso(就跟我之前一样),然后来测试待测试的Apk的功能,这个暂时无法实现,基本上也没有办法实现;
整体原因基于这三点,我们来着重分析一下第三点;
首先简单说一下UiAutomator跟Espresso的区别,UiAutomator不需要绑定Activity,直接操作页面显示元素来完成自动化;而Espresso是需要绑定待测试的Activity来作为入口来进行测试;
第一:这就意味着,黑盒?你不知道内部Activity的入口,就无法进行绑定;
第二:绑定?这个也是无法实现的,即使通过java的反射机制我们可以根据packagePath来对第三方App翻天覆地的搞,PackageManager获取内部的Activity名称等等,然后想的肯定是难道都拿到这些东西了,难道不能使用Intent,或者Instrumentation来启动App来进行测试吗;
首先,Intent+context是可以启动App,但是!!!无法为下一步的onView做铺垫,最后的结果一定是如下图所示;
即使打开了待测试的App,依旧是找不到可以测试的Activity来进行测试;
我也不信邪,开始各种翻源码,准备自己仿写一个ActivityRule,发现Espresso的绑定Activity的方式是基于Instrumentration实现的,然后源码中启动绑定Activity的方式正是刚才异常错误中的startActivitySync(),然后就开始钻如何使用这个方法来启动,如果是在源码中测试,什么都不需要,只需把待测试的Activity名.class传进来即可,里面的启动流程简单说是,先根据context获取App的packagePath,然后拿着这个packagePath以及这个Activity名.class去绑定Intent启动;
随后就开始了仿写,仿写的很成功,待测试的Apk的路径,context,内部所有的Activity名等等全都成功拿到,然后测试就来了个这样的一个提示,如下所示;
不能运行该应用,原因原进程试图分解到新的进程,最终,我去官方文档中去翻了翻看,发现了一句话,很感人;
大意:它不允许您启动在不同进程中运行的Activity。此外,如果给定的意图解析为多个活动,而不是显示用户选择Activity的对话框,则会引发异常。
说到底就是,不允许跨进程的启动Activity,基本上也就意味着不能跨App的绑定测试Activity;一首凉凉送给自己,多么6的框架结果不能使用起来(虽然也有缺点),很蓝瘦;
最后,测试程序的Activity源码以及页面源码还是贴一下吧,如下,直接复制即可;
MainActivity 代码:
package heepay.com.espressoautotest;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final TextView tv_test = findViewById(R.id.tv_test);
Button btn_one_click = findViewById(R.id.btn_one_click);
Button btn_two_click = findViewById(R.id.btn_two_click);
btn_one_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv_test.setText("Clicked One Btn");
}
});
btn_two_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tv_test.setText("Clicked Two Btn");
}
});
}
}
页面代码: