Android下白盒自动化回归测试junit

本文介绍了JUnit单元测试和Robotium自动化测试的应用方法,包括针对方法的单元测试、逻辑测试的编写技巧,以及Robotium测试中常见问题的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近在一个项目中使用了junit 测试,总结一下。

Junit, 白盒的,自动化的,单元测试,回归测试。

Junit 最开始是用在java开发中的,android 也支持该测试框架。在android中我们使用junit一般有两种测试,一个是针对方法的单元测试,一个针对逻辑测试,而逻辑测试我们使用Robotium测试。

 

一、针对方法的单元测试

1、纯java的单元测试

这是一个只会做两数加减的超级简单的计算器(小学一年级必备极品)。代码如下:

publicclass SampleCalculator 

public int add(int augend , int addend) 

return augend + addend ; 
}

public int subtration(int minuend , int subtrahend)

{

return minuend - subtrahend ;

}

}

  将上面的代码编译通过。下面就是我为上面程序写的一个单元测试用例:

//请注意这个程序里面类名和方法名的特征 
importjunit.framework.TestCase; 
publicclass TestSample extends TestCase

{

public void testAdd()

{

SampleCalculatorcalculator = new SampleCalculator();

int result = calculator.add(50 , 20);

assertEquals(70 , result);

}

test开头的方法为一个测试项。

集成测试

import junit.framework.Test;

importjunit.framework.TestSuite;

publicclass TestAll{

public static Test suite(){

TestSuite suite = new TestSuite("TestSuite Test");

suite.addTestSuite( TestSample.class);

return suite;

}

}

 

2、在android中的单元测试

基本上和是ava单元测试,不过要考虑到ui界面的选择等操作。

public class TestUtil extendsActivityInstrumentationTestCase2 {

         // 声明一个Solo对象,Solo实例封装了所有Robotium的可用方法

         privateSolo solo = null;

         privatefinal int WAITING_SEARCH_AP_TIME = 10000; // 40S

// 声明一个Class类型的变量,用于ActivityInstrumentationTestCase2加载启动被测程序

         privatestatic Class launcherActivityClass;

         // 声明一个标签用于日志的输出控制,便于调试

         finalString TAG = "shareMusicTest";

         // 静态加载auncherActivityClass也就是被测程序主类

         static{

try {

                            launcherActivityClass= Class

                                               .forName("com.huaqin.wifi.apps.listen_music_together.TogetherListenerActivity");

                   }catch (ClassNotFoundException e) {

                            thrownew RuntimeException(e);

                   }

         }

// 构造函数,传入TARGET_PACKAGE_ID,launcherActivityClass即可

         publicTestUtil() {

                   super(

                                     "com.huaqin.wifi.apps.listen_music_together.TogetherListenerActivity",

                                     launcherActivityClass);

         }

// 这个必须有,在测试用例初始时执行,我们在这里初始化了Solo实例

         publicvoid setUp() throws Exception {

                   solo= new Solo(getInstrumentation(), getActivity());

         }

// 这个也必须有,在测试用例执行完毕执行,我们在这里销毁了测试中建立的所有实例,清除垃圾

         publicvoid tearDown() throws Exception {

                   solo.finishOpenedActivities();

         }

public void testGetLocalHostName() throws Exception{

                   assertFalse(Util.getLocalHostName().isEmpty());

                   assertFalse(Util.getLocalHostName().equals(""));

                   assertFalse(Util.getLocalHostName().equals(""));

         }

public void testGetFileExt() throws Exception {

                   Filefile1 = new File("/data/aaa.xml");

                   assertTrue(Util.getFileExt(file1).equals("xml"));

                   Filefile2 = new File("/data/bbb.mp3");

                   assertTrue(Util.getFileExt(file2).equals("mp3"));

                   file1.delete();

                   file2.delete();

         }

public void testIsShowing()throws Exception {

                   Viewv = (View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.tune_wifi_quick);  //一键收听按钮

                   assertTrue(Util.isShowing(v));

                   v=(View)solo.getCurrentActivity().findViewById(com.huaqin.wifi.apps.listen_music_together.R.id.wifi_hot_bg);  //雷达view

                   assertFalse(Util.isShowing(v));

         }

}

 

定义了三个测试项:testGetLocalHostName  testGetFileExt  testIsShowing

其中testIsShowing 使用solo 对象。

二、逻辑测试

逻辑测试可以自动模拟用户使用系统的情况。我们可以更具功能说明书和黑盒测试用例来编写逻辑测试代码。就是点击什么,进入什么界面,出现什么状态。

我这里采用Instrumentation.Android单元测试的主入口是InstrumentationTestRunner。它相当于JUnit当中TestRunner的作用。你可以将Instrumentation理解为一种没有图形界面的,具有启动能力的,用于监控其他类(用TargetPackage声明)的工具类。任何想成为Instrumentation的类必须继承android.app.Instrumentation

public class TestWifiStatus extendsActivityInstrumentationTestCase2 {

// 声明一个Solo对象,Solo实例封装了所有Robotium的可用方法

         privateSolo solo = null;

 

         // 声明一个Class类型的变量,用于ActivityInstrumentationTestCase2加载启动被测程序

         privatestatic Class launcherActivityClass;

         // 声明一个标签用于日志的输出控制,便于调试

         finalString TAG = "shareMusicTest";

         // 静态加载auncherActivityClass也就是被测程序主类

static {

                   try{

                            launcherActivityClass= Class

                                               .forName("com.huaqin.wifi.apps.listen_music_together.TogetherListenerActivity");

                   }catch (ClassNotFoundException e) {

                            thrownew RuntimeException(e);

                   }

         }

// 构造函数,传入TARGET_PACKAGE_ID,launcherActivityClass即可

         publicTestWifiStatus() {

                   super(

                                     "com.huaqin.wifi.apps.listen_music_together.TogetherListenerActivity",

                                     launcherActivityClass);

         }

// 这个必须有,在测试用例初始时执行,我们在这里初始化了Solo实例

         publicvoid setUp() throws Exception {

                   solo= new Solo(getInstrumentation(), getActivity());

         }

// 这个也必须有,在测试用例执行完毕执行,我们在这里销毁了测试中建立的所有实例,清除垃圾

         publicvoid tearDown() throws Exception {

                   solo.finishOpenedActivities();

         }

}

 

下面给出几个测试用例

/主界面ui

                   publicvoid testUI() throws Exception {

                            Activityactivity = solo.getCurrentActivity();

                            assertTrue(solo.waitForText(

                                               activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.music_title)));   //"Music Share"

                            assertTrue(solo.waitForText(

activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.open_noaml_search)));  //tune

                            assertTrue(solo.waitForText(

                                               activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.create_naomal_hot)));   //Broadcast

                           

                       View v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.phone_name);  //大厅名称view

assertTrue(solo.waitForView(v));

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.tune_wifi);  //“Tune”按钮view

                       assertTrue(solo.waitForView(v));

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.listener_music);  //“Broadcast”按钮view

assertTrue(solo.waitForView(v));

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.tune_wifi_quick);  //一键收听按钮view

                       assertTrue(solo.waitForView(v));

                   }

                   //我要收听:进入客户接分享界面

public void testTune() throws Exception {

                            Activityactivity = solo.getCurrentActivity();

                            solo.clickOnButton(activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.open_noaml_search));  //tune

                            assertTrue(solo.waitForText(

                   activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.searching)));  //"Searching"

                            //assertTrue(solo.waitForText("Searchingconnectable Share"));

                            assertTrue(solo.waitForText("正在搜索音乐服务器"));  //"正在搜索音乐服务器"

View v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.wifi_hot_bg);  //雷达view

                       assertTrue(solo.waitForView(v));

                   }

                   //我要分享:进入主机分享界面

public void testBroadcast() throws Exception {

                            Activity activity =solo.getCurrentActivity();

                            solo.clickOnButton(activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.create_naomal_hot));   //Broadcast

                            assertTrue(solo.searchText(

                                               activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.all_songs)));//"所有歌曲"

assertTrue(solo.searchText(

                                               activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.all_aritis)));//"艺术家"

                            assertTrue(solo.searchText(

                                              activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.all_album)));//"专辑"

                            assertTrue(solo.searchText(

activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.defualt_song_playing)));//"Playing"

                           

                       View v = (View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.playBtn);  //播放按键view

                       assertTrue(solo.waitForView(v));

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.prevBtn);  //上一首view

                       assertTrue(solo.waitForView(v));

 v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.nextBtn);  //下一首view

                       assertTrue(solo.waitForView(v));

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.seekbar);  //进度条view

                       assertTrue(solo.waitForView(v));

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.repeatBtn);  //循环播放view

 assertTrue(solo.waitForView(v));

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.shuffleBtn);  //随机播放view

                     assertTrue(solo.waitForView(v));

                   }

                   //一键收听:搜索当前的热点,并自动加入第一个搜索到的非活动状态热点(大厅),进入U5界面等待主机播放,如果在规定时间内未搜索任何热点需有消息提示

public void testShotcut() throws Exception {

                            Activityactivity = solo.getCurrentActivity();

                            Viewv = (View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.tune_wifi_quick);  //一键收听按钮

                       solo.clickOnView(v);

         assertTrue(solo.waitForText(

                                               activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.searching)));  //"Searching"

                            //assertTrue(solo.waitForText("Searchingconnectable Share"));

                            assertTrue(solo.waitForText(

activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.now_quick_join)));//"正在搜索音乐服务器"

                       v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.wifi_hot_bg);  //雷达view

                       assertTrue(solo.waitForView(v));

solo.sleep(WAITING_SEARCH_AP_TIME);  

                            //booleanbSearchNothing = solo.waitForText("error 提示",1,WAITING_SEARCH_AP_TIME);

                            booleanbSearchNothing = solo.waitForText(

                                              activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.create_naomal_hot));   //Broadcast

assertTrue(bSearchNothing ||

                                               solo.searchText(activity.getString(com.huaqin.wifi.apps.listen_music_together.R.string.title_tune)));  //tune

                            v =(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.listbtn);  //歌曲列表view

                            assertTrue(bSearchNothing|| solo.waitForView(v));

                            v=(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.albumIconBg);  //CD view

assertTrue(bSearchNothing || solo.waitForView(v));

                            v=(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.seekbar);  //进度条view

                            assertTrue(bSearchNothing|| solo.waitForView(v));

                            v= (View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.saveBtn);  //保存按钮view

assertTrue(bSearchNothing || solo.waitForView(v));

                            v=(View)solo.getView(com.huaqin.wifi.apps.listen_music_together.R.id.musicIcon);  //音乐icon view

                            assertTrue(bSearchNothing|| solo.waitForView(v));

                   }

 

这几个测试用例都是用的solo机器人,Robotium测试。

 

三、常见的Robotium测试的问题总结:

1Robotium的测试类ActivityInstrumentationTestCase2继承了TestCase类,即robotiom的测试类是junit3的实例,并没有junit4的特征,比如通过annotate的方式来识别子类的新特征,没不能实现@beforeclass,@afterclass等特征。只能通过写setup和teardown,以及test开头的测试用例的方式进行测试case书写。

2、有些button没有string,没有text,只能通过index来click这样很不直观,而且button的index并不是固定的,有可能随着 控件重新加载,顺序也有可能发生变化,无法保证测试结果。查看了robotium源码,发现大多数click方法最终都是通过传入参数转成view,再调 用clickOnView,于是参照着写了一个通过button的ID来click的方法。Button的ID需要查看测试对象的源码中获取。比如导航中 就有菜单栏大多数据button就是这种类型的。

3、有的activity点击后不能获取焦点,可以通过另外的方式获取activity的内容,比如Activity act = solo.getCurrentActivity();获取当前的activity,然后通过act.findViewById的方式获取控件。

4、多个屏幕的情况,可以通过滚屏的方式滑动,solo.scrollToSide(Solo.LEFT),如果多屏属于一个activity,则不需要滑动也能运行case获取数据。

5、有时text view或者button的click方法会失效,咋办?答案是在被测程序的AndroidManifest.xml文件里加上这么一句:<supports-screens android:anyDensity="true"/>就行了。唉,当时为了找到这个解决方法可浪费了俺不少时间啊,最后在官网上找到答案了。



6、如果要想在robotium的测试程序里读写SD card肿么办?答案是在被测程序的AndroidManifest.xml文件里加上<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>

   <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"></uses-permission>。注意是在被测程序里加上,在测试程序本身的manifest文件里加会很坑爹的。

7、listview动态添加item如何判断添加成功。可从添加前及添加后Item个 数判断,先确定添加item的属性,再通过相应的方法获取item。比如添加一个item可能需要三个textview,那么通过 getCurrentTextViews(View)前后获取到的个数差就相差三个。比如添加黑名单到黑名单列表。

(1)有的listView只有web,或者主要是文本,可以通过getItemAtPosition(i).toString()的方法获取第几行的内容。

solo.clickOnText(chooseProvPage.getListView().getItemAtPosition(i).toString());

(2)有的listView包含多个testView或者button,可以通过findViewById的方法得到某一行的一项的内容。

8、无法捕获Toast,这个有点不明白。大概实验了一下,可以使用waitForText这个函数来捕获文字,这个方法返回值是布尔型的,所以返回true就是找到了。

9、结果判断

(1)waitForText

   该方法适用于点击操作后需要一点时间才返回结果的结果判断。比如联网操作,可以设置适当的延时,等待返回结果,判断结果更加正确。

(2)assertActivity

   该方法适用于activity时,可以判断点击操作切换Activity是否正确,可以与waitfortext配合使用。

(3)searchText+assert

   当有editText时,输入内容后,可通过searchText查找输入内容是否是预期结果,再将返回结果判断。

注:有些editText的内容无法通过searchText,原因暂时没找到。比如:手动添加黑名单时的名称及号码的输入框。


10、测试类要继承ActivityInstrumentationTestCase2<测试类类名


11
、构造方法中super("包名",测试类类名.class); 


12
setUp方法中solo= new Solo(getInstrumentation(), getActivity()); 


13
tearDown方法中 
try { 
solo.finalize(); 
} catch (Throwable e) { 
e.printStackTrace(); 

getActivity().finish(); 
super.tearDown(); 


14
、点击自动化 
clickOnMenuItem("
菜单名") 
clickInList(
列表行数)注:从1开始 
clickOnText("(?i).*?test.*")
点击文本 
clickLongOnText("Note 2")
长时间点击文本 
clickOnButton("
按钮名")  点击按钮 


15
、输入自动化 
enterText(
,"输入的内容") 


16
、屏幕控制 
setActivityOrientation(Solo.LANDSCAPE
Solo.PORTRAIT)控制屏幕横向或纵向显示 


17
、跳转 
goBack()
模仿硬返回键 
goBackToActivity("Activity
") 跳到指定的Activity 


18
、判断 
判断当前是否是指定的Activity 
assertCurrentActivity("
测试提示", "Activity"); 
搜索指定文本是否存在 
searchText("
搜索文本")searchText("(?i).*?note1 test") 后面这个是正则表达式 


19
、获取 
(EditText) solo.getView(R.id.EditText01); 
(TextView) solo.getView(R.id.TextView01); 
ArrayList currentTextViews = solo.getCurrentTextViews(outputField); 


20
、点击按钮等测试中需要注意2点: 
1)真机测试时发现,屏保后点击按钮测试会报找不到该按钮,也就是点不中的意思,看来测试机器人还真仿真啊。 
2)点击按钮后有个延迟的过程,以后的测试需要循环等待一段时间,否则直接进入下面的测试后误报错错误,此处处理示例如下: 
//
点击按钮开启服务 
solo.clickOnButton(butStartService); 
//
判断指定服务是否存在 
long start = System.currentTimeMillis(); 
while (!isServiceStarted(SERVICE_PACKAGE_NAME)) { 
    try { 
        Thread.sleep(1000); 
    } catch (InterruptedException e) { 
    } 
    if ((System.currentTimeMillis() - start) > TIMEOUT) { 
        break; 
    } 

assertTrue("
没有开启服务",isServiceStarted(SERVICE_PACKAGE_NAME));

 

21.

private Map<String,Integer> jdField_b_of_type_JavaUtilMap = new HashMap();
 public View findViewById(String paramString) {
  try {
   int i;
   if(this.jdField_b_of_type_JavaUtilMap.containsKey(paramString))
    i = ((Integer) this.jdField_b_of_type_JavaUtilMap
      .get(paramString)).intValue();
   else
    i = solo.getCurrentActivity()
      .getResources()
      .getIdentifier(paramString.replace(".R.id.",":id/"),
        null, null);
   if (i > 0) {
    this.jdField_b_of_type_JavaUtilMap.put(paramString,
      Integer.valueOf(i));
    View localView1 = solo.getView(i);
    if (localView1 != null)
     return localView1;
    ArrayList localArrayList = solo.getViews();
    Iterator localIterator = localArrayList.iterator();
    while (localIterator.hasNext()) {
     View localView2 = (View) localIterator.next();
     if (localView2.getId() == i)
      return localView2;
    }
   } else {
    return null;
   }
  } catch (Exception e) {

  }
  return null;
 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值