背景
本來今年計(jì)劃是要把Android和iOS都學(xué)一下,結(jié)果現(xiàn)在到年底了,還是沒點(diǎn)苗頭,再怎樣也應(yīng)該開個(gè)頭,于是就買了《第一行代碼(第二版)》開始學(xué)習(xí)Android,在學(xué)習(xí)的過程中,利用Appium來測試自己寫出來的東西,也不失為一個(gè)學(xué)習(xí)的方法。
項(xiàng)目Github地址:https://github.com/diandianhanbin/LearnAndroidDoAppium
TextView
這個(gè)控件對(duì)應(yīng)著HTML的Label,其實(shí)可以理解為一個(gè)標(biāo)簽,用來展示文本內(nèi)容,當(dāng)然,它也可以監(jiān)聽點(diǎn)擊,所以在某種程度上來說,也可以發(fā)揮一個(gè)按鈕的作用。
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="24sp"
android:gravity="center|center_horizontal"
android:text="This is a TextView!" />
TextView
控件可以定義id
,這個(gè)id
無論對(duì)于開發(fā)還是測試,都是很有用的東西,開發(fā)的時(shí)候,可以通過id來對(duì)控件監(jiān)聽、設(shè)置等操作。
比如下面的代碼就是用來關(guān)聯(lián)和設(shè)置TextView
。
textView = (TextView) findViewById(R.id.text_view);
case R.id.changeTextView:
if (textView.getText().toString() == "Change Text") {
textView.setText("This is a TextView");
} else {
textView.setText("Change Text");
}
break;
對(duì)于測試來說,這個(gè)控件就是獲取文本最方便的地方。利用uiautomatorviewer
我們可以很直接的獲取這個(gè)控件的resource-id
注:resource-id的組成是 包名 + / + id

在使用Appium測試的過程中獲取的方式就是這樣的
TextView = methodConfig.SIMPLEREAD['TextView']
def getTextView(self):
"""
獲取TextView
:return:str, 標(biāo)題文本
"""
return self.getTextOfElement(*self.TextView)
class MessageSimpleRead(AppiumBaseTest, SimpleRead):
def test001_checkTextView(self):
"""測試TextView內(nèi)容是否正確"""
textcontent = self.getTextView()
self.assertEqual('This is a TextView!', textcontent)
Activity
Activity
應(yīng)該是Android的非常重要的東西,屬于Android的四大組件之一。
簡單的來理解,Activity
可以理解為一個(gè)HTML
頁面,Android所有的顯示,都是由Activity
來承載,關(guān)于Activity
的信息就不多寫了,在網(wǎng)絡(luò)上很多,主要需要熟悉它的生命周期.
這里我用按鈕來觸發(fā)跳轉(zhuǎn)第二個(gè)Activity
,所以在測試的時(shí)候需要測試這個(gè)跳轉(zhuǎn)。
Android部分是一個(gè)顯式啟動(dòng),代碼如下:
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
startActivity(intent);
}
對(duì)應(yīng)的Appium的操作方法如下:
SecondActivity = methodConfig.SIMPLEREAD['SecondActivity']
def clickButtonStartActivity(self):
"""
點(diǎn)擊按鈕啟動(dòng)Activity
:return:
"""
self.clickElement(*self.ButtonStartActivity)
def checkSecondActivity(self):
"""
檢查是否啟動(dòng)第二個(gè)Activity
:return: True or False
"""
return self.waitActivity(self.SecondActivity)
def test003_changeActivity(self):
"""跳轉(zhuǎn)Activity測試"""
self.clickButtonStartActivity()
self.assertEqual(self.checkSecondActivity(), True)
self.assertEqual(self.getSecondActivityTextView(), u'This is Second Activity')
當(dāng)然,通過Appium
還有其他方法啟動(dòng)Activity
,源碼中對(duì)應(yīng)的還有這個(gè)方法:
def start_activity(self, app_package, app_activity, **opts):
"""Opens an arbitrary activity during a test. If the activity belongs to
another application, that application is started and the activity is opened.
This is an Android-only method.
:Args:
- app_package - The package containing the activity to start.
- app_activity - The activity to start.
- app_wait_package - Begin automation after this package starts (optional).
- app_wait_activity - Begin automation after this activity starts (optional).
- intent_action - Intent to start (optional).
- intent_category - Intent category to start (optional).
- intent_flags - Flags to send to the intent (optional).
- optional_intent_arguments - Optional arguments to the intent (optional).
- stop_app_on_reset - Should the app be stopped on reset (optional)?
"""
data = {
'appPackage': app_package,
'appActivity': app_activity
}
arguments = {
'app_wait_package': 'appWaitPackage',
'app_wait_activity': 'appWaitActivity',
'intent_action': 'intentAction',
'intent_category': 'intentCategory',
'intent_flags': 'intentFlags',
'optional_intent_arguments': 'optionalIntentArguments',
'stop_app_on_reset': 'stopAppOnReset'
}
for key, value in arguments.items():
if key in opts:
data[value] = opts[key]
self.execute(Command.START_ACTIVITY, data)
return self
不過正常來說,不建議這么處理,因?yàn)檫@樣處理,就沒有覆蓋到點(diǎn)擊按鈕跳轉(zhuǎn)Activity
這個(gè)業(yè)務(wù)場景了。
Menu
系統(tǒng)的Menu
對(duì)測試來說絕對(duì)一個(gè)大坑。
從開發(fā)上來看,是一個(gè)非常正常的開發(fā)流程,在xml
中寫一個(gè)menu
的布局、控件名稱和控件ID。然后在需要展示Menu
的Activity
中調(diào)用如下方法即可生成Menu
:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
這是一個(gè)再正常不過的開發(fā)方式了,但是到了測試這里,完全就是一個(gè)深坑。
比如,我在定義這個(gè)Menu
的時(shí)候,用的id
是這樣的:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/main_menu"></menu>
但是用uiautomatorviewer
來查看的時(shí)候,它給我顯示的卻是這個(gè)

WTF!!!這個(gè)菜單竟然是一個(gè)ImageView
,然后,我定義的id
呢?被Android吃了么????
然后再看這個(gè)菜單欄。。。。從uiautomatorviewer
上來看,是由一個(gè)一個(gè)的ListView
組成的,這些我不關(guān)心,作為測試,我關(guān)心的依然是它的id
。

從uiautomatorviewer
探測的結(jié)果是com.example.svenweng.uiwidgettest:id/title
??墒?,我特么開發(fā)的時(shí)候,定義的是這樣的啊啊啊?。。?!
<item
android:id="@+id/getTextView"
android:title="@string/main_menu_getTextView"/>
<item
android:id="@+id/changeTextView"
android:title="@string/main_menu_changeTextView"/>
<item
android:id="@+id/changeImageView"
android:title="@string/main_menu_changeImageView"/>
<item
android:id="@+id/changeProgressBar"
android:title="@string/main_menu_changeProgressBar"/>
<item
android:id="@+id/insertProgressBar"
android:title="@string/main_menu_insertProgressBar"/>
<item
android:id="@+id/delProgressBar"
android:title="@string/main_menu_delProgressBar"/>
<item
android:id="@+id/alertDialog"
android:title="@string/main_menu_alertDialog"/>
<item
android:id="@+id/alertProgressDialog"
android:title="@string/main_menu_progressDialog"/>
Android你全部給我改成com.example.svenweng.uiwidgettest:id/title
是幾個(gè)意思????
在開發(fā)調(diào)用對(duì)應(yīng)菜單監(jiān)控的時(shí)候,用的也是它的id
?。。?!
switch (item.getItemId()) {
case R.id.getTextView:
Toast.makeText(this, textView.getText().toString(), Toast.LENGTH_SHORT).show();
break;
case R.id.changeTextView:
if (textView.getText().toString() == "Change Text") {
textView.setText("This is a TextView");
} else {
textView.setText("Change Text");
}
break;
case R.id.changeImageView:
imageView.setImageResource(R.drawable.img_1);
break;
case R.id.changeProgressBar:
if (progressBar.getVisibility() == View.GONE) {
progressBar.setVisibility(View.VISIBLE);
} else {
progressBar.setVisibility(View.GONE);
}
break;
case R.id.insertProgressBar:
progress = progress + 10;
oriProgressBar.setProgress(progress);
break;
case R.id.delProgressBar:
progress = progress - 10;
oriProgressBar.setProgress(progress);
break;
case R.id.alertDialog:
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("This is a Dialog");
dialog.setMessage("This is a Message");
dialog.setCancelable(false);
dialog.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "OK", Toast.LENGTH_SHORT).show();
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(MainActivity.this, "Cancel", Toast.LENGTH_SHORT).show();
}
});
dialog.show();
break;
case R.id.alertProgressDialog:
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("This is a ProgressDialog");
progressDialog.setMessage("loading....");
progressDialog.setCancelable(true);
progressDialog.show();
break;
default:
break;
為毛線在測試使用的時(shí)候,唯一的id
都變成了一樣的東西?。。?!
最后
我一直堅(jiān)持的觀點(diǎn),不懂開發(fā)就很難測試,從這一次的學(xué)習(xí)來看,我之前有很多想法是有一些問題的,以前我總是覺得開發(fā)為什么這么懶,控件不用就不加id
,或者老喜歡用同樣的id
?,F(xiàn)在我終于知道了,很多時(shí)候我都在錯(cuò)怪開發(fā)。