你有多久没有写单元测试了?(二)

[toc]

上一篇说道如何在AndroidStudio中配置Espresso,本篇主要介绍使用Espresso时的思考和示例(示例已托管在github);

关于Espresso的一些杂谈

Espresso被习惯的成为浓缩咖啡,据说原发明国是意大利。不知道为啥Google要将这么有灵魂的测试框架取名叫浓缩咖啡,"浓缩的就是精华"?:)。

本文愿景

愿你沉醉在单元测试中日渐消瘦,无法自拔~
愿你写完单元测试归来时,仍是少年。

对Espresso的基本印象

其实在Android 测试支持库中就已经包括如下的一些自动化测试工具:

  • AndroidJUnitRunner:适用于 Android 且与 JUnit 4 兼容的测试运行器
  • Espresso:UI 测试框架;适合应用中的功能性 UI 测试
  • UI Automator:UI 测试框架;适合跨系统和已安装应用的跨应用功能性 UI 测试

Espresso 测试框架提供了一组 API 来构建 UI 测试,用于测试应用中的用户流。利用这些 API,可以编写简洁、运行可靠的自动化 UI 测试。

Espresso 测试框架的主要功能

视图匹配

操作 API

UI 线程同步


系好安全带,开车了

单一特性查找控件

现在有这么一个情况,代码刚写完,迫不及待的想检查一下里边的控件(视图)表现是否得体。

怎么能设计一个套路,来检测一下?

首先,得找到你要的控件(视图)。
那么问题来了,茫茫人海,一叶扁舟,怎么才能找到想要的控件(视图)?

俗话说:“*****,捉贼拿脏”,先找一下控件的特征:)

  • onView(withId(R.id. jack)) 【在知道控件id的情况下】

  • onView(withText("Jack"))【有文本"Jack"这几个字的文本控件】

  • onView(withTagKey("full_name"));【根据setTag(key,value)来查找】

  • onView(withTagValue("Jack_Dawson"));【Jack的全名叫杰克.道森,tag的值】

  • onView(withHint("提示词")); 【忘词了,居然有提示词 eg.EditText】

    ….

and so on 可以根据指定的特征来对控件进行筛选查找【如图】

没有什么问题是一张图解决不了的,要是有的话,那就两张:)

  • 附图
image.png

多个特性查找控件

但是,有时候我们会遇到这种情况:

多个页面中控件的id是一样的,这个时候就要需要根据多个特性来筛选了。

毫无疑问,一个惊天地、泣鬼神的API就要出世了。不错,它就是智慧与美貌并存,正义与侠义的化身的 allof()

Matchers类下边有一个方法allOf(),参数是一个可变参数。我的个乖乖,这样就可以根据多个特性来筛选了;

具体示例如下:

onView(allOf(withId(R.id.jack),withText("Jack")));[注:可变参数可以传递多个参数]
一个ID叫jack,并且显示了“Jack”文字的控件,当然可以加很多限制条件,只有你有都可以传递在allOf的可变参数中

至此,顺藤摸瓜,根据蛛丝马迹在Espresso中锁定一个控件不在是难事了:)


东风已借,完事具备,开测了、开测了!

show me the code!

测RecyclerView引发的血案
public class RecyclerViewTest extends ActivityInstrumentationTestCase2<RecyclerViewActivity> {

    private static final String BOOK_TITLE = "Clean Code";
    private static final String BOOK_AUTHOR = "Robert C. Martin";

    public RecyclerViewTest() {
        super(RecyclerViewActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        getActivity();//设置运行上下文
    }
   
    //RecyclerViewActions为Espresso的提供API
    /**
     * 根据Item的position来进行测试
     */
    public void testClickAtPosition() {        
   onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition(0,  click()));//点击RecyclerView的第一个条目
        onView(withId(R.id.book_title)).check(matches(withText(BOOK_TITLE)));//检查控件文本内容是否和BOOK_TITLE内容一致
        onView(withId(R.id.book_author)).check(matches(withText(BOOK_AUTHOR)));//同上
    }

    public void testClickOnViewInRow() {
        //点击一个有具体描述的文本
        onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItem(
                hasDescendant(withText(BOOK_TITLE)), click()));//
        onView(withId(R.id.book_title)).check(matches(withText(BOOK_TITLE)));
    }
}
附:
1.RecyclerViewActivity
public class RecyclerViewActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_recycler_view);
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.setAdapter(new RecyclerBooksAdapter(this));
    }
}

2.item_book.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="12dp">

    <ImageView
        android:id="@+id/icon"
        android:src="@drawable/book"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_marginRight="8dp" />

    <TextView
        android:id="@+id/book_title"
        android:layout_toRightOf="@id/icon"
        android:layout_toEndOf="@id/icon"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        tools:text="Some Cool Book" />

    <TextView
        android:id="@+id/book_author"
        android:layout_below="@id/book_title"
        android:layout_toRightOf="@id/icon"
        android:layout_toEndOf="@id/icon"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textSize="12sp"
        tools:text="by Awesome Author" />

</RelativeLayout>

由一句话引发的思考

onView(withId(R.id.recyclerView)).perform(RecyclerViewActions.actionOnItemAtPosition(0,  click()));
关于perform()函数的二三事:

到底这神乎其技的perform()函数凭借什么超能力来拯救世界的?
在上文中锁定要测试的控件之后,"该配合演出的控件就要尽情表演了",作为编剧应该给演员一个剧本吧。

冥冥中早有安排,一切的剧情都可以放在perform函数的传参(可变参数)中,来制造一系列的矛盾。perform()函数就只干了一件事:就是编写剧本(View的动作轨迹)。

perform()的动作(Action|ViewAction)

ViewAction中都有哪些动作呢?

  • clearText()清空文本
    
  • click()点击动作
    
  • swipe系列 [猛击,这个厉害了]
    swipeLeft()       原版解释:swipe right-to-left across the vertical center of the view
    swipeRight()    原版解释:swipe left-to-right across the vertical center of the view
    swipeDown()       原版解释:       top-to-bottom
    swipeUp()       原版解释:       bottom-to-top 
    
  • closeSoftKeyboard() 这个不用多说了吧,关闭软键盘
    
  • pressBack() 相当于点击了返回键了
    
  • pressKey(keyCode)  还可以根据keycode来点击按键
    
  • doubleClick() 双击
    
  • longClick()
    

等一系列操作,限于篇幅就不在过多的展开了,想看详细的可以直接去ViewAction类下边去看,只有你想不到的没有你看不到的~

后话:其他的Action多多少少和ViewAction有一些说不清道不明的关系,相当之暧昧,比如:RecyclerViewActions、EditorAction、CloseKeyboardAction、ScrollToAction等,有兴趣的可以看一下它们的实现.

check()函数的二三事

演员演完了,这时候作为Director(导演)就得看一下演员演的是否到位。
关键是否达到了预期的效果,如果你要加点潜规则啥的都看你自己了:)

来定义一点规则吧,不然演员就瞎演了。

  • check函数的传参ViewAssertion(Checks the state of the given view (if such a view is present).)【大佬们的命名真腻害,这不就是对这个控件的断言吗?】

那么问题来了,check到底能检查一些什么信息?且听我细细道来。

先举个简单的例子吧:

onView(withId(R.id.jack)).check(matches(isDisplayed()));//检查控件是否显示了
onView(withId(R.id.jack)).check(matches(not(isDisplayed()));//not
onView(withId(R.id.jack)).check(matches(withHint("Jack")));//检查提示词是否正确
onView(withId(R.id.jack)).check(matches(withText("Jack")));//检查文本

当然还有很多可以检测的类型,例如isEnabled()、isFocusable()、hasFocus()等等

所有的可检测的类型都在ViewMatchers类里边了,想了解的少年可以去瞅瞅

【附】
  • 示例传送门

https://github.com/ZoranLi/EspressoExample github地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容