1. 程式人生 > >Appium---如何高效組織自動化測試用例

Appium---如何高效組織自動化測試用例

如何高效組織自動化測試用例

 

自動化測試用例寫多了,不可避免會遇到這個問題,每次執行無需執行所有的用例,那麼如何把要執行的用例挑出來並高效組織它們呢?

一般說來,通用的做法都是把要執行的用例用特殊標記Mark出來,然後框架執行時,自動尋找這些帶標記的case,並把它們裝到一個新的test suite裡。

所以高效組織測試用例的關鍵就是兩部分:

1. 如何標記待測試用例.

2. 執行時如何收集這些帶標記的用例。

以下列出來我經歷過的方法:

1.給所有的測試用例編號,把測試用例按照 編號, summary, Path, ClassName, 測試執行與否的flag等等,寫到一個外部檔案裡,通常是Excel。

每次測試框架執行時,先去這個檔案裡,一行一行的讀檔案,如果發現“測試執行與否的flag”的值是True, 就把這條case拿出來放到一個臨時變數裡,讀完整個檔案,就拿到了

所有要執行的case, 這個時候,匯入這個case需要的所有依賴,執行這個case,並把執行結果寫回到一個results的檔案裡。 那麼,改動老的case,增加新的case,怎麼辦呢?

勤快一點的,每寫一個/更改一個自動化用例,手工更新這個外部表格。 懶一點的,寫一段程式碼,每寫/改一個case,手工執行這段程式碼,把更改寫進去。

2.利用裝飾器,經典的如TestNG,可以在測試用例前加@BeforeSuite, 今天要講的也是這個。如何在python裡實現。

先說下思想: 我期望自動化測試執行時候,可以根據我提供的不同的tag,執行不同的測試用例。 前提是我寫自動化用例的時需給每個測試用例,都加上tag並賦值。

在執行時候,框架通過我提供的不同的tag,去找這些被標記的case,然後組織起來執行。

要實現這個,我們需要接收使用者引數,可以用:

1.sys.argv[1:]來自己寫程式碼處理,也可以用標準庫argparse來,我們用後者。

argparse 的用法很直觀, 先看段程式碼::

1. 先建立一個parser:

parser = argparse.ArgumentParser(prog='para')

2. 加入一些引數:

parser.add_argument('-w', action='store', default='.', help='specify the work space')

parser.add_argument('-target', action='store', help='specify the run target file')

parser.add_argument('-tag', action='store', help='run by tag')

parser.add_argument('-n', action='store', help='concurrent user')

3. Parseing 這些引數

options = parser.parse_args()

print (options)

這個時候,如在command line裡輸入

python __init__.py -target test -tag smoke -n 3 

就會得到如下結果:

Namespace(n='3', tag='smoke', target='test', w='.')

使用argparse就可以方便的獲取使用者引數,並處理。關於argparse的用法,請參考官網。

2.使用者輸入的引數我們接收處理了,那麼如何處理這些tag呢,這就要用到裝飾器:

def TestCase(enabled=True, tags=None, **kwargs):

def tracer(cls):

cls.__type__ = 'Test'

cls.__enabled__ = enabled

if not tags:

cls.__tags__ = None

else:

# 想想看,如果tag有好幾個,這塊程式碼應該怎麼改

cls.__tags__ = tags

return cls

return tracer

上面的程式碼呢,我們定義了一個裝飾器,這個裝飾器唯一的作用,是接收傳入的類,然後給這些類增加一些屬性,這些屬性後續用來判斷是否要執行。 使用時在新建立測試類上加上@Testcase即可。

3.測試用例類,這些測試類或者測試類中的函式實現了一個個的功能。

class RealTest():

    @TestCase(tags='smoke')

def test1(self):

logging.info("this is test 1")

time.sleep(5)

    @TestCase(enabled=True, tags='smoke')

def test2(self):

logging.info("this is test2")

time.sleep(5)

    @TestCase(enabled=False, tags='smoke')

def test3(self):

logging.info("this is test3")

time.sleep(5)

我們可以利用裝飾器裡的tag引數, enable引數來實現測試用例的篩選。

4.我們再定義一個類,這個類作為一個test case的執行器,把要執行的測試用例裝進來,併發,或者順序執行。

class TestExecutor(threading.Thread):

def __init__(self, cls, worker):

threading.Thread.__init__(self)

self.cls = cls

self.workers = worker

self._lock = threading.RLock()

def run(self):

self.workers.acquire()

logging.info("Thread {threadname}".format(threadname=self.current_executor()))

self.cls(self)

self.workers.release()

def current_executor(self):

return threading.current_thread()

5.利用threading.Semaphore實現執行緒併發限制。

6.實現:

import argparse

import threading

import time

import logging

logging.basicConfig(level=logging.INFO , format='%(asctime)s - %(filename)s[line:%(lineno)d] - %(levelname)s: %(message)s')

def TestCase(enabled=True, tags=None, **kwargs):

def tracer(cls):

cls.__type__ = 'Test'

cls.__enabled__ = enabled

if not tags:

cls.__tags__ = None

else:

# 想想看,如果tag有好幾個,這塊程式碼應該怎麼改

cls.__tags__ = tags

return cls

return tracer

class TestExecutor(threading.Thread):

def __init__(self, cls, worker):

threading.Thread.__init__(self)

self.cls = cls

self.workers = worker

self._lock = threading.RLock()

def run(self):

self.workers.acquire()

logging.info("Thread {threadname}".format(threadname=self.current_executor()))

self.cls(self)

self.workers.release()

def current_executor(self):

return threading.current_thread()

class RealTest():

    @TestCase(tags='smoke')

def test1(self):

logging.info("this is test 1")

time.sleep(5)

    @TestCase(enabled=True, tags='smoke')

def test2(self):

logging.info("this is test2")

time.sleep(5)

    @TestCase(enabled=True, tags='smoke')

def test3(self):

logging.info("this is test3")

time.sleep(5)

if __name__ == "__main__":

s = threading.Semaphore(2)

parser = argparse.ArgumentParser(prog='para')

parser.add_argument('-w', action='store', default='.', help='specify the work space')

parser.add_argument('-target', action='store', help='specify the run target file')

parser.add_argument('-tag', action='store', help='run by tag')

parser.add_argument('-n', action='store', help='concurrent user')

options = parser.parse_args()

real_cases = []

for module_element in  dir(RealTest):

pre_test_class = getattr(RealTest,module_element) # @Mark

if options.tag:

if hasattr(pre_test_class, "__type__") and \

hasattr(pre_test_class, "__enabled__") \

and pre_test_class.__enabled__\

and hasattr(pre_test_class, "__tags__")\

and pre_test_class.__tags__ \

and pre_test_class.__tags__.lower() == options.tag.lower():

real_cases.append(pre_test_class)

threads = []

for item in real_cases:

threads.append(TestExecutor(item, s))

for t in threads:

t.start()

for t in threads:

t.join()

這裡重點講下標記為

#@Mark

的語句,實際上這個就是如何把標記的case挑選出來的核心方法。

我們使用了反射機制

反射: 有時候我們會碰到這樣的需求,需要執行物件的某個方法,或是需要對物件的某個欄位賦值,而方法名或是欄位名在編碼程式碼時並不能確定,需要通過引數傳遞字串的形式輸入。舉個具體的例子:當我們需要實現一個通用的DBM框架時,可能需要對資料物件的欄位賦值,但我們無法預知用到這個框架的資料物件都有些什麼欄位,換言之,我們在寫框架的時候需要通過某種機制訪問未知的屬性。這個機制被稱為反射(反過來讓物件告訴我們他是什麼),或是自省

dir([obj]):

呼叫這個方法將返回包含obj大多數屬性名的列表(會有一些特殊的屬性不包含在內)。obj的預設值是當前的模組物件。

hasattr(obj, attr):

這個方法用於檢查obj是否有一個名為attr的值的屬性,返回一個布林值。

getattr(obj, attr):

呼叫這個方法將返回obj中名為attr值的屬性的值,例如如果attr為’bar’,則返回obj.bar。

對於所有的測試類,我們會逐個把他們匯入(示例裡無這段程式碼,關於case class的匯入可以根據 -target這個引數的值來程式碼實現,此處未展示。)

對於每一個類,我們通過dir(), getattr()拿到測試類所有的可用方法,然後這些方法因為有裝飾器TestCase的加持,我們可以順利的根據引數-tags拿到要執行的測試用例。

然後我們根據拿到的每一個測試用例,判斷它們是否有需要的屬性( enabled,type), 然後對於每個有這個屬性的用例,我們在放到真正執行的測試用例list裡。

最後對測試用例list的用例,每一個都放入TestExecutor裡執行,TestExecutor 實現了多執行緒及多執行緒限制。

我們來看下執行效果:

輸入:

python Android/init.py -target test -tag smoke -n 3

輸出:

因為我們限制了併發執行緒數為2, 所以我們先看到以下輸出。

Namespace(n='3', tag='smoke', target='test', w='.')

2017-07-07 20:01:25,009 - __init__.py[line:31] - INFO: Thread <TestExecutor(Thread-1, started 11284)>

2017-07-07 20:01:25,009 - __init__.py[line:42] - INFO: this is test 1

2017-07-07 20:01:25,009 - __init__.py[line:31] - INFO: Thread <TestExecutor(Thread-2, started 10196)>

2017-07-07 20:01:25,010 - __init__.py[line:47] - INFO: this is test2

5秒後,又看到如下:

2017-07-07 20:01:30,010 - __init__.py[line:31] - INFO: Thread <TestExecutor(Thread-3, started 14156)>

2017-07-07 20:01:30,011 - __init__.py[line:52] - INFO: this is test3

如此一來,我們僅需要寫測試用例時,新增合適的tag,然後執行時用引數區分,就可以不修改程式碼的的情況下執行不同的case了。