目次
序文:
uiauTomator2 は Android プラットフォームに基づく UI 自動テスト フレームワーク、pytest は機能豊富な Python テスト フレームワーク、allure は美しいテスト レポートを生成するツールです。これら 3 つのツールを組み合わせて使用すると、Android UI 自動テストを実施し、直感的で視覚的なテスト レポートを生成できます。
この記事では主に、Android UI 自動テストのための uiauTomator2+pytest+allure の使用について説明します。実際、主な目的は、pytest フレームワークについてさらに学ぶためにいくつかの実用的なスクリプトを作成することです。
また、ちなみに、これも非常にスムーズに使える自動化フレームワーク uiauTomator2 を紹介します。
以前、appium+testng を使って一連の自動スクリプトを作成し、実際に社内で使用したことがありますが、今回は自社のアプリをテストする必要はなく、前会社 58 Tongcheng のアプリを自動テストに使用します。
導入
UI の自動化を行うには、Java の testng や Python の Unittest など、適切なテスト フレームワークを選択する必要があります。主な目的は、コード レベルを明確、簡潔、再利用可能にすることです。今回は、Python の pytest フレームワークを紹介します。
pytest
pytest 官方: pytest: より良いプログラムを書くのに役立ちます — pytest ドキュメント
pytest フレームワークを使用すると、小規模なテストを簡単に作成できますが、アプリケーションやライブラリの複雑な機能テストをサポートできるように拡張できます。
公式の紹介文は、多くの制約なしでテスト コードを簡単に作成できるようにすることを目的としています。もちろん、この記事では、pytest がなぜ優れているのか、どのように優れているのかには焦点を当てていません。pytest がテスト フレームワークであることを覚えておいてください。
uiautomator2
uiauTomator2 は、デバイスを自動化するためのテスト スクリプトを作成する Python をサポートする Android UI 自動化フレームワークです。最下層は、最近人気のある openatx プロジェクトに属する Google uiauTomator に基づいています。
次の図は動作の概略図です。
atx-agent は、受信したリクエストを解析して uiauTomator2 のコードに変換するために、uiauTomator2 のサーバーとしてデバイスにインストールする必要があります。一般に、対話プロセスはそれほど面倒ではなく、確かに appium よりもはるかに高速です。実際の使用感。
魅力
allure は、クールなページとさまざまな統計を備えたテスト レポートで、HTMLTestRunner レポートよりも 100 倍優れています。もちろん、複数の言語もサポートしています。
公式アドレス: http://allure.qatools.ru
環境構築
Macコンピュータを使用して環境を構築する
pytest
最新バージョンは 4.0 ですが、実際に 4.0 を使用すると allure との互換性が若干悪いため
、pytest バージョン 3.7 を使用することをお勧めします。
pip install pytest==3.7
uiautomator2
uiauTomator2 も Python のクラス ライブラリであり、pip でインストールできます。
pip install uiautomator2
魅力
brew install allure
pip install pytest-allure-adaptor
テスト フレームワーク、自動化フレームワーク、テスト レポートを使用すると、基本的にコーディングが可能になります。
pytestプラグイン
pytest プラグインは、失敗の再試行、進行状況の表示、順序の指定を実装できます。
pip install pytest-sugar # 打印进度
pip install pytest-rerunfailures # 失败重试
pip install pytest-ordering # 执行顺序
もちろん、他にもたくさんのプラグインがあるので、ここでは一つずつ紹介しません。
例
ドライバーを初期化する
UI オートメーションを行うには、ドライバー オブジェクトを初期化する必要があります。このドライバー オブジェクトは、クリック イベント、スライド、ダブルクリックなどを操作できます。uiauTomator2 の初期化ドライバー メソッドは appium 設定より少なく、グローバルな暗黙的な待機時間を設定
でき
ます要素の場合
import uiautomator2 as ut2
def init_driver(self,device_name):
'''
初始化driver
:return:driver
'''
try:
logger.info(device_name)
d = ut2.connect(device_name)
#logger.info("设备信息:{}".format(d.info))
# 设置全局寻找元素超时时间
d.wait_timeout = wait_timeout # default 20.0
# 设置点击元素延迟时间
d.click_post_delay = click_post_delay
#d.service("uiautomator").stop()
# 停止uiautomator 可能和atx agent冲突
logger.info("连接设备:{}".format(device_name))
return d
except Exception as e:
logger.info("初始化driver异常!{}".format(e))
固定機構
Unittest フレームワークには、テスト操作の初期化と終了に使用されるセットアップ メソッドとティアダウン メソッドがあります。pytest は@pytest.fixtureメソッドを使用してセットアップとティアダウンを実装します。
次のコードは、初期化と終了を行う driver_setup メソッドを定義します。
# 当设置autouse为True时,
# 在一个session内的所有的test都会自动调用这个fixture
@pytest.fixture()
def driver_setup(request):
logger.info("自动化测试开始!")
request.instance.driver = Driver().init_driver(device_name)
logger.info("driver初始化")
request.instance.driver.app_start(pck_name, lanuch_activity, stop=True)
time.sleep(lanuch_time)
allow(request.instance.driver)
def driver_teardown():
logger.info("自动化测试结束!")
request.instance.driver.app_stop(pck_name)
request.addfinalizer(driver_teardown)
さらに、それを達成する別の方法もあります。これは、1 つの方法でセットアップと破棄を実行し、yield キーワードで一時停止すると理解できます。
@pytest.fixture()
def init(self,scope="class"):
self.home = Home(self.driver)
self.home.news_tab()
self.news = News(self.driver)
logger.info("初始化消息模块")
yield self.news
logger.info("结束消息模块")
yield キーワードは、Python 構文ジェネレーターとイテレーターでメモリを節約するために使用されます。
たとえば、大きなリストをループする場合、一度にループアウトするのはパフォーマンスの無駄になります。
そのため、yield キーワードはループを制御するために使用されます。
以下は収量を示しています。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
def yt():
print "第一次打印"
yield 0
print("第二次打印")
if __name__ == '__main__':
a = yt()
print next(a)
print next(a)
yt 関数を直接呼び出した場合は、何も出力されないことがわかります。これは、現時点では yt 関数は宣言されているだけで実際には使用されていないためです。
初回呼び出しには next メソッドを使用します。入力結果は次のようになります。
この時点では、yield は return 0 に相当し、この時点では「2 回目の出力」は出力されず、このブロックで停止します。
第一次打印
0
next メソッドを使用して 2 回目に呼び出すと、入力結果は次のようになります。
第二次打印
上記の例をもう一度見てみましょう。
セットアップ操作は yield の前に完了し、self.news オブジェクトが返されます。
破棄操作は yield の後に完了します。
@pytest.fixture()
def init(self,scope="class"):
self.home = Home(self.driver)
self.home.news_tab()
self.news = News(self.driver)
logger.info("初始化消息模块")
yield self.news
logger.info("结束消息模块")
データ共有
pytest では、conftest.py クラスを記述するだけでデータ共有を実現でき、インポートせずに一部の構成を自動的に検索できます。
先ほど述べた初期化 driver_setup 関数は、conftest.py クラスで設定できます。現時点では、この関数はグローバル関数です。次のようにテスト クラスで使用できます。@pytest.mark.usefixtures デコレータを使用して
、 driver_setup 関数
@allure.feature("测试发布")
@pytest.mark.usefixtures('driver_setup')
class TestNews:
@pytest.fixture(params=item)
def item(self, request):
return request.param
テストクラス
pytest は、それがテストの開始時またはテスト終了時のクラスである場合、それが実行可能なテスト クラスであるとみなされることを検出します。
テストクラスのテストの開始時にテストメソッドを記述します。
@allure.story('测试首页搜索')
def test_home_search(self,init):
init.home_search()
パラメータ化する
ホームページ上で複数の単語を検索するシーンがあるとします。これはパラメーター化で完了する必要があります。
@pytest.mark.parametrize を使用します
@pytest.mark.parametrize(('kewords'), [(u"司机"), (u"老师"), (u"公寓")])
def test_home_moresearch(self, init,kewords):
init.home_more_search(kewords)
指定された順序
ユースケースを公開するには最初にログインする必要があると仮定し、最初にログインしてからユースケースを並べ替えて公開することができます
@pytest.mark.run を使用して、odrer が小さいものから大きいものへと最初に実行されます。
@pytest.mark.usefixtures('driver_setup')
@pytest.mark.run(order=1)
# 指定login先执行
class TestLogin:
指定したレベルを実行する
多くのユース ケースが作成されており、一部のユース ケースは指定されたレベルで実行できるスモーク ユース ケースであると仮定します
。
@allure.story('测试首页更多')
@pytest.mark.P0
def test_home_more(self, init):
init.home_more()
コマンドライン実行: pytest -v -m "P0"、すべての P0 レベルのユースケースを実行します。
リトライ
現時点では、pytest-rerunfailures プラグインを使用する必要があります。これは次のように使用されます。
@pytest.mark.flaky(reruns=5, reruns_delay=2)
@allure.story('测试精选活动')
def test_news_good(self,init):
init.news_good()
もちろん、失敗する場合を指定してリトライする方法です
次のようにユーザーをグローバルに設定することもできます。
pytest --reruns 2 --reruns_delay 2
reruns: 再試行の数
reruns_lay: 再試行の間隔
フック関数
conftest.py ファイルに @pytest.hookimpl 関数を定義します。この関数は pytest の実行ステータスをフックできます。
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
'''
hook pytest失败
:param item:
:param call:
:return:
'''
# execute all other hooks to obtain the report object
outcome = yield
rep = outcome.get_result()
# we only look at actual failing test calls, not setup/teardown
if rep.when == "call" and rep.failed:
mode = "a" if os.path.exists("failures") else "w"
with open("failures", mode) as f:
# let's also access a fixture for the fun of it
if "tmpdir" in item.fixturenames:
extra = " (%s)" % item.funcargs["tmpdir"]
else:
extra = ""
f.write(rep.nodeid + extra + "\n")
コードを見ると、失敗に関する関連情報が取得できることがわかりますが、その際、失敗情報を利用して、ユースケースが失敗したときにスクリーンショットを撮ったり、データ統計用に失敗回数を記録したりすることができます。
断言
ユースケースを実行するとき、最後のステップでは要素が存在するかどうかのアサートなどをアサートします。
def assert_exited(self, element):
'''
断言当前页面存在要查找的元素,存在则判断成功
:param driver:
:return:
'''
if self.find_elements(element):
logger.info("断言{}元素存在,成功!".format(element))
assert True
else:
logger.info("断言{}元素存在,失败!".format(element))
assert False
次のようにコードを最適化することもできます。
def assert_exited(self, element):
'''
断言当前页面存在要查找的元素,存在则判断成功
:param driver:
:return:
'''
assert self.find_elements(element) == True,"断言{}元素存在,失败!".format(element)
logger.info("断言{}元素存在,成功!".format(element))
アサーションが失敗すると、AssertionError と定義されたテキストが表示されます
AssertionError: 断言xxxxx元素存在,失败!
走る
いくつかの一般的なコマンドライン操作を紹介します
特定のフォルダーの下でユースケースを実行する
ファイルの下ですべてのユースケースを実行する
pytest android/testcase
メソッドを実行する
クラスファイルアドレス::メソッド名
pytest test_home.py::test_home_more
または、 -k パラメータ + メソッド名を使用します
pytest -k test_home_more
クラスを運営する
クラス ファイルのアドレスを維持するために、テスト クラス内のすべてのテスト メソッドを直接デバッグする必要がある場合があります。
pytest test_home.py
実行 P0 レベル
pytest -v -m "P0"
非 P0 レベルを実行する
pytest -v -m "not P0"
メインモード
run.py に次のコードを記述します。これは、コマンド ライン パラメーターをスクリプトにカプセル化するのと同じです。
pytest.main(["-s","--reruns=2", "android/testcase","--alluredir=data"])
報告
テスト コードを作成した後も、非常に見栄えの良いレポートが作成されます。以前は、HTMLTestRunner を使用してレポートを作成するのが一般的でしたが、HTMLTestRunner のレポート機能は比較的単純で、失敗時のスクリーンショットはサポートされていません。
たまたまコミュニティでallureさんの投稿を拝見し、プレゼンテーションレポートを読んでとても面白かったので、まずはユースケースのスクリーンショットを添付します。
さらに、次のようにコードでレポート レベルを設定できます。
@allure.feature("测试首页")
@pytest.mark.usefixtures('driver_setup')
class TestHome:
@pytest.fixture()
def init(self,scope="class"):
self.home = Home(self.driver)
logger.info("初始化首页模块")
yield self.home
logger.info("结束首页模块")
@allure.story('测试首页搜索')
def test_home_search(self,init):
init.home_search()
allure.feature と allure.story を設定することは、上司と部下の関係に相当します。
失敗の詳細
失敗したユースケースをクリックすると、失敗に関する情報が表示されます
失敗のスクリーンショット
自動化の実行中に障害が発生したため、その時点の状況を説明するスクリーンショットが必要です。
上記の @pytest.hookimpl 関数では、最後にスクリーンショット メソッドが呼び出され、
allure.attach を使用してスクリーンショットが追加されます。
添付の 2 番目のパラメータは画像のバイナリ情報であることに注意してください。
@pytest.hookimpl(tryfirst=True, hookwrapper=True)
def pytest_runtest_makereport(item, call):
'''
hook pytest失败
:param item:
:param call:
:return:
'''
# execute all other hooks to obtain the report object
outcome = yield
rep = outcome.get_result()
# we only look at actual failing test calls, not setup/teardown
if rep.when == "call" and rep.failed:
mode = "a" if os.path.exists("failures") else "w"
with open("failures", mode) as f:
# let's also access a fixture for the fun of it
if "tmpdir" in item.fixturenames:
extra = " (%s)" % item.funcargs["tmpdir"]
else:
extra = ""
f.write(rep.nodeid + extra + "\n")
pic_info = adb_screen_shot()
with allure.step('添加失败截图...'):
allure.attach("失败截图", pic_info, allure.attach_type.JPG)
uiautomator2の基本操作
サービスを開始する
次のコマンドを実行します。
python -m uiautomator2 init
atx-agent.apk を電話機にインストールし、電話機でサービスを開始します
2018-12-14 18:03:50,691 - __main__.py:327 - INFO - Detect pluged devices: [u'a3f8ca3a']
2018-12-14 18:03:50,693 - __main__.py:343 - INFO - Device(a3f8ca3a) initialing ...
2018-12-14 18:03:51,154 - __main__.py:133 - INFO - install minicap
2018-12-14 18:03:51,314 - __main__.py:140 - INFO - install minitouch
2018-12-14 18:03:51,743 - __main__.py:168 - INFO - apk(1.1.7) already installed, skip
2018-12-14 18:03:51,744 - __main__.py:350 - INFO - atx-agent is already running, force stop
2018-12-14 18:03:52,308 - __main__.py:213 - INFO - atx-agent(0.5.0) already installed, skip
2018-12-14 18:03:52,490 - __main__.py:254 - INFO - launch atx-agent daemon
2018-12-14 18:03:54,568 - __main__.py:273 - INFO - atx-agent version: 0.5.0
atx-agent output: 2018/12/14 18:03:52 [INFO][github.com/openatx/atx-agent] main.go:508: atx-agent listening on 192.168.129.93:7912
モニターは携帯電話の IP + デフォルトは 7921 です。
イベント
クリックやスライドなどのイベントの種類には、よく使用されるものがいくつか紹介されています。
クリック
id、xpath、text に基づいて要素を見つけることは、appium とあまり変わりません。
self.d(resourceId=element).click()
self.d.xpath(element).click()
self.d(text=element).click()
滑り台
最初の 4 つのパラメータは座標、時間はコントロールのスライド時間です
self.d.drag(self.width / 2, self.height * 3 / 4, self.width / 2, self.height / 4, time)
モニター
これは、メソッドが if 判定に相当する場合に、初めてアプリを起動して権限をクリックするか、画面広告を開く
ために使用され、クリックは条件が満たされた場合にのみクリックされるため、大量のロジック コードが生成される可能性があります。 。
driver.watcher("允许").when(text="允许").click(text="允许")
driver.watcher("跳过 >").when(text="跳过 >").click(text="跳过 >")
driver.watcher("不要啦").when(text="不要啦").click(text="不要啦")
ビュー要素
インストール
weditorライブラリをインストールする必要があります
pip install weditor
スタートツール
python -m weditor
ブラウザが自動的に開いて要素が表示されます。これは、Web バージョンの uiautomatorviewer と同等であり、より使いやすくなっています。
無線操作
上記の携帯電話の IP があれば、接続の
メソッドを携帯電話の IP に置き換えるだけで、スクリプトをワイヤレスで実行できます。
# d = ut2.connect(device_name)
d = ut2.connect("192.168.129.93")
ここに来た者として、皆さんも寄り道は避けていただきたいと思います。
ここでは、自動テストを進める上で必要なことをいくつか共有し、お役に立てれば幸いです。
(ソフトウェアテスト関連資料、自動テスト関連資料、技術的なQ&Aなど)
そうすることでより良い進歩が見込めると信じています!
下の小さなカードをクリックしてください