51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2720|回复: 2
打印 上一主题 下一主题

UiAutomator2 检测 Toast 信息方法

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2018-4-10 15:08:09 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
小弟不才,也算见识过图片识别的方法检测toast信息的,但总觉得它不可靠~自动化要真的落地,用到实处,
最重要的就是可靠性! 到底真实为项目带来多大的效率提升,才是真的!但,有一个事实:高层封装的东西
越少就越好,因为封装和集成的东西越多,可靠性就会越差!

所以,我们测试人员最好要学会开发的知识,或者说,了解一些开发用到的东西,当然,越多越好!
啰嗦了有点多,还是说主题吧:作为测试人员不要以为一个显示几秒然后就消失的一定是toast,也有可能是
开发自己封装的一个textview,一定要根据实际项目做甄别,然后写测试代码。
以下用实际遇到的问题示例。
应该大多数人都知道移动端的提示信息基本上是用toast来实现的,一个可能的实现如下:

  1. public static void showShort(final String info) {
  2.         if (StringUtils.isNotEmpty(info)) {
  3.             BaseApplication.getInstance().getHandler().post(new Runnable() {
  4.                 @Override
  5.                 public void run() {
  6.                     if (null == mToast) {
  7.                         mToast = Toast.makeText(BaseApplication.getContext(), info, Toast.LENGTH_SHORT);
  8.                     }
  9.                     mToast.setDuration(Toast.LENGTH_SHORT);
  10.                     mToast.setText(info);
  11.                     mToast.show();
  12.                 }
  13.             });
  14.         }
  15.     }
复制代码

是的,这就是对一个toast信息如何显示的简单封装。
如果你们开发是差不多以这个方式来实现的(可以跟开发问,比如哪个功能块,什么业务逻辑会显示某
个提示信息,然后是不是大概用Toast.makeText然后show的方法来实现的),那么就可以用下面的方法
在自动化代码中进行校验:

封装一个Listener来监听toast发出来的事件:
  1. private static void initToastListener() {
  2.         mInstrumentation = InstrumentationRegistry.getInstrumentation();
  3.         mInstrumentation.getUiAutomation().setOnAccessibilityEventListener(new UiAutomation.OnAccessibilityEventListener() {
  4.             @Override
  5.             public void onAccessibilityEvent(AccessibilityEvent event) {
  6.                 Log.i("AAA", "onAccessibilityEvent: " + event.toString());
  7.                 //判断是否是通知事件
  8.                 if (event.getEventType() != AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {
  9.                     return;
  10.                 }
  11.                 //获取消息来源
  12.                 String sourcePackageName = (String) event.getPackageName();
  13.                 //获取事件具体信息
  14.                 Parcelable parcelable = event.getParcelableData();
  15.                 //如果不是下拉通知栏消息,则为其它通知信息,包括Toast
  16.                 if (!(parcelable instanceof Notification)) {
  17.                     Log.i("AAA",event.getText().toString());
  18.                     toastMessage = (String) event.getText().get(0);
  19.                     toastOccurTime = event.getEventTime();
  20.                     Log.i("AAA", "Latest Toast Message: " + toastMessage + " [Time: " +         toastOccurTime + ", Source: " + sourcePackageName + "]");
  21.                 }else {
  22.                     Log.i("AAA",event.getParcelableData().toString());
  23.                 }
  24.             }
  25.         });
  26.     }
复制代码

原理:通知栏消息和toast消息都是AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED的一种,
而Uiautomation刚好可以设置此事件的监听器,然后我们只需要再继续过滤掉notification事件,那剩下
的就必然是toast信息了!
因此,如果你的APP没有启动页(或者叫闪页),那么直接在BeforeClass里初始化这个Listener即可,如果
有,可以在Before里(也就是对每个测试用例注册这个Listener)。为什么呢?因为通常的APP的启动页是用
来加载一些广告或者其他的信息,也就是说,真正的主页面通常的做法是在启动页(一般称之为SplashA
ctivity,可以简单地认为一个activity对应一个独立的页面)之后才加载的;且,我们要测的功能大部分
都在MainActivity里(也有可能是其他的Activity,根据具体情况具体选择)。总之,你要测哪个activity下
面的toast信息,就在哪个activity的测试集里初始化,否则,监听不到你想要的信息。示例代码如下:

  1. @BeforeClass
  2. public static void beforeClass(){
  3.         //如果在這里就初始化了,那么监听的会是SplashActivity的信息

  4.     }

  5. @Before
  6. public void startMainActivityFromHomeScreen() {
  7.         initToastListener();
  8.         // Initialize UiDevice instance
  9.         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());

  10.         // Start from the home screen
  11.         mDevice.pressHome();

  12.         // Wait for launcher
  13.         final String launcherPackage = getLauncherPackageName();
  14.         assertThat(launcherPackage, notNullValue());
  15.         mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), LAUNCH_TIMEOUT);

  16.         // Launch the blueprint app
  17.         Context context = InstrumentationRegistry.getContext();
  18.         final Intent intent = context.getPackageManager()
  19.                 .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE);
  20.         intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);    // Clear out any previous instances
  21.         context.startActivity(intent);

  22.         // Wait for the app to appear
  23.         mDevice.wait(Until.hasObject(By.pkg(BASIC_SAMPLE_PACKAGE).depth(0)), LAUNCH_TIMEOUT);
  24.     }
复制代码

下面的是测试用例,检验简单的登录功能(故意输入错误的密码),看Toast信息是否有获取到:

  1. @Test
  2. public void testLoginFailed() {
  3.         mDevice.wait(Until.findObject(By.text("未登录")), LAUNCH_TIMEOUT);
  4.         // Type text and then press the button.
  5.         mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "tv_main_username"))
  6.                 .click();
  7.         mDevice.wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE, "et_username")), LAUNCH_TIMEOUT);
  8.         UiObject2 username = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "et_username"));
  9.         username.clear();
  10.         username.setText(STRING_TO_BE_TYPED);
  11.         UiObject2 password = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "et_password"));
  12.         password.setText("1234567");
  13.         UiObject2 loginButton = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "bt_user_login"));
  14.         loginButton.click();
  15.         // Verify the test is displayed in the Ui
  16.         final long startTimeMillis = SystemClock.uptimeMillis();
  17.         boolean isSuccessfulCatchToast;
  18.         while (true) {
  19.             long currentTimeMillis = SystemClock.uptimeMillis();
  20.             long elapsedTimeMillis = currentTimeMillis - startTimeMillis;
  21.             if (elapsedTimeMillis > 5000L) {
  22.                 Log.i("AAA", "超过5s未能捕获到预期Toast!");
  23.                 isSuccessfulCatchToast = false;
  24.                 break;
  25.             }
  26.             if (toastOccurTime > startTimeMillis) {
  27.                 isSuccessfulCatchToast = "密码不正确".equals(toastMessage);
  28.                 break;
  29.             }
  30.         }
复制代码

Assert.assertTrue("捕获预期Toast失败!", isSuccessfulCatchToast);
我们看看Logcat打印出了什么:


Good,我们catch到了Toast信息。

还有一种是将textview作为toast的替代方式,这个时候就简单了,因为通常的做法是把它隐藏起来(暂时
不可见,但是uiautomator2可以发现它),然后在必要的时候显示出来而已,可能的开发实现代码如下:


它的XML布局:

  1. <TextView
  2.         android:id="@+id/tv_toast_login"
  3.         android:layout_width="@dimen/x264"
  4.         android:layout_height="@dimen/x56"
  5.         android:layout_alignBottom="@id/ll_cotnent"
  6.         android:layout_centerHorizontal="true"
  7.         android:layout_marginBottom="@dimen/x10"
  8.         android:background="@drawable/bg_toast_checkcode"
  9.         android:gravity="center"
  10.         android:text="验证码已发送到手机"
  11.         android:textColor="@color/white"
  12.         android:textSize="@dimen/x20"
  13.         android:visibility="visible"/>
复制代码

注意上面的最后一行android:visibility="visible"表示默认为可见,如果是写成android:visibility="gone"就表示默
认不可见。要实现toast的效果,只要默认设置成gone,然后在需要的时候(比如输入的错误的密码然后点击
了登录按钮这种情况下)用一个handler(这个就讲得太深了,不做细讲)将它延时展示(设置成visible)x秒再设置
成(invisible或者gone)就可以了。
注意:这里看似只有一个“验证码已发送到手机”,其实只是一个默认的文本展示值罢了,后续所有的'所谓'to
ast信息都是以这个控件来展示的,请知悉。
相应的我们的测试代码如下:

  1. @Test
  2. public void testLoginFailed() {
  3.         mDevice.wait(Until.findObject(By.text("未登录")), LAUNCH_TIMEOUT);
  4.         // Type text and then press the button.
  5.         mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "tv_main_username"))
  6.                 .click();
  7.         mDevice.wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE, "et_username")), LAUNCH_TIMEOUT);
  8.         UiObject2 username = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "et_username"));
  9.         username.clear();
  10.         username.setText(STRING_TO_BE_TYPED);
  11.         UiObject2 password = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "et_password"));
  12.         password.setText("1234567");
  13.         UiObject2 loginButton = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "bt_user_login"));
  14.         loginButton.click();
  15.         // Verify the test is displayed in the Ui
  16.         final long startTimeMillis = SystemClock.uptimeMillis();
  17.         boolean isSuccessfulCatchToast;
  18.         //这里其实应该加个超时限制,循环检查,支持超时抛出失败,时间紧迫,就不补充了~
  19.         while (true) {
  20.             UiObject2 uiObject2 = mDevice.wait(Until.findObject(By.res(BASIC_SAMPLE_PACKAGE, "tv_toast_login")), LAUNCH_TIMEOUT);
  21.             Log.i("AAA",uiObject2.toString()+uiObject2.getText()+"_1");
  22.             UiObject2 toastView = mDevice.findObject(By.res(BASIC_SAMPLE_PACKAGE, "tv_toast_login"));
  23.             Log.i("AAA",toastView.toString()+uiObject2.getText()+"_2");
  24.             }
  25.         }
复制代码

同样的,我们可以看到效果图(这里就不截图了,有兴趣的可以去试一下)里已经打印出了这个tv_toast_lo
gin(就是显示‘所谓的’toast信息的载体)的text属性为“密码不正确”,因此,测试成功!

所以,没有绝对完美且唯一的办法自动化测试一个APP,最好是要根据实际情况了解实现,对症下药,这
样,才能实现自动化的价值!最重要的是,它是可重复利用的,是可靠的!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?(注-册)加入51Testing

x
分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

该用户从未签到

2#
发表于 2018-6-11 14:55:06 | 只看该作者
我用了第一种方法,捕捉不到toast。
回复 支持 反对

使用道具 举报

该用户从未签到

3#
发表于 2018-6-11 14:55:46 | 只看该作者
是不是还有什么其他的设置?
回复 支持 反对

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-9-22 12:48 , Processed in 0.076278 second(s), 24 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表