Python數據驅動實踐(四)–動態挑選測試用例

iTesting2019-04-14 03:23:45

iTesting,愛測試,愛分享


前面幾天, 我從數據驅動的一個第3方庫ddt出發,連續分享了3篇文章:
Python數據驅動實踐(一)–ddt實現數據驅動
Python數據驅動實踐(二)–教你用Python實現數據驅動
Python數據驅動實踐(三)–動態添加測試用例

後面兩篇文章實際上是任何一個測試框架都必須要有的部分。 今天我再分享一篇如何動態挑選測試用例, 大家知道,自動化腳本越寫越多,但不是每次都需要full regression, 這個時候需要把開發修改涉及到的測試用例跑一下,而那些無關的用例可以不跑。

那麼,思路是什麼? 如果對每一個用例,我定義的時候給一個標籤比如説Test,再給它一個值,True或False,這樣我框架尋找測試用例的時候就找標籤編輯為Test且值是True的就好了。
老規矩,先上代碼:

 1# test_decorator.py
2def TestClass(enabled=True):
3    def wraps(cls):
4        setattr(cls, "__test_type__""TestClass")
5        setattr(cls, "__enabled__", enabled)
6        return cls
7    return wraps
8def Test(enabled= True):
9    def wraps(func):
10        setattr(func, "__test_type__""Test")
11        setattr(func, "__enabled__", enabled)
12        return func
13    return wraps
14# 利用裝飾器,對於每一個進來的類或func,
15#我們給它加上一個屬性一個值,然後再你需要運行的類或者func上裝飾就好了。

結合我們上次講過的動態添加測試用例, 和數據驅動,我們把這部分整合起來看看,一個簡單完整的測試框架如下。
1.從指定的文件夾/文件下查找待運行測試類/方法
2.找到待運行測試類/方法,並根據數據不同重新生成測試用例
3.運行測試用例集並保存運行結果

我的整個項目層次結構是這樣的:

其中:
Common:

放通用的功能,比如,查找待測試用例的test_case_finder, 和我們上文的定義是否測試類/測試函數的裝飾器test_decorator

pages:

放我們所有的頁面功能,如果是UI測試,這個就對應於一個個的UI Page,我們將以Page Object組織頁面元素,並定義頁面類和方法,每一個頁面當作一個page看待並定義一個類

tests:

這個下面放我們的測試用例,結構跟pages完全對應,每一個測試類對應於一個page 類。

run:

這個文件定義了用例如何運行,是並行還是順序。

還有其它的很多功能,我們暫且不講,先來看看這幾個如何協作的。

1# pages/page_add.py
2#我們就定義個多數相加的方法。
3class SumData:
4    def __init__(self):
5        pass
6    def sum_data(self, *ags):
7        return sum(ags)

再看它對應的測試類

 1# tests/test_page1/test_page_add.py
2#這裏面我定義了兩個測試方法,一個要參數,一個不要。
3#並且用了兩個裝飾類,一個用來提供測試數據,一個用來標記是否需要測試
4from common.test_case_finder import data_provider
5from common.test_decorator import Test
6from pages.page_add import SumData
7class TestSumData:
8    def __init__(self):
9        pass
10    @data_provider([(123), (459)])
11    @Test()
12    def test_sum_data(self, x, y, z):
13        print("the value are {0}, {1}, {2}".format(x, y, z))
14        assert SumData().sum_data(x, y) == z
15    @Test(enabled=False)
16    def test_sum_data2(self):
17        print(2)
18        assert SumData().sum_data(45) == 7

這兩個裝飾類的定義Test()我們前面已經介紹過了,test provider定義如下:

1def data_provider(test_data):
2    def wrapper(func):
3        setattr(func, "__data_Provider__", test_data)
4        global index_len
5        index_len = len(str(len(test_data)))
6        return func
7    return wrapper

好了,有了page類,有個test類,那麼如何讓我們的程序查找到我們需要運行的測試用例呢?

 1#test_case_finder.py
2# 簡化下,只給出如何鑑別待運行的用例
3#mod_ref就是我們動態加載進來的所有測試module。
4def find_classes_in_module(self, mod_ref):
5    test_classes = []
6    for module in mod_ref:
7    #微信關注公眾號iTesting,加入萬人測試團
8        cls_members = inspect.getmembers(moduleinspect.isclass)
9        for cls in map(lambda x: x[1], cls_members):
10            for name, func in list(cls.__dict__.items()):
11                if hasattr(func, "__test_type__"and hasattr(func, "__enabled__"):
12                    if getattr(func, "__test_type__") == "Test" and getattr(func, "__enabled__") == True:
13                        test_classes.append(func)
14    return test_classes

那麼,用例找出來了,如何數據驅動呢?

 1#test_case_finder.py
2def mk_test_name(name, value, index=0):
3    index = "{0:0{1}}".format(index+1, index_len)
4    test_name = "{0}_{1}_{2}".format(name, index, value)
5    return re.sub(r'\W|^(?=\d)''_', test_name)
6def unpack_test_cases_from_functions(func_list):
7    return_cases = []
8    for func in func_list:
9        if hasattr(func,  '__data_Provider__'):
10            for i, v in enumerate(getattr(func, "__data_Provider__")):
11                test_name = mk_test_name(func.__name__, getattr(v, "__name__", v), i)
12                return_cases.append((test_name, func, v))
13        else:
14            return_cases.append((func.__name__, func, None))
15    return return_cases
16#我們通過反射的方式找到有Test()裝飾的測試方法,並且只把enabled=True的測試方法放入我們的測試用例裏。

測試用例也找到了,該運行了:

 1#run.py
2#簡化版, 實現了一個簡單的順序執行,我們後面將一步步優化至併發,順序都可以
3import traceback
4from common.test_case_finder import DiscoverTestCases, unpack_test_cases_from_functions
5if __name__ == "__main__":
6    #下面一句給定了查找測試用例的根目錄,如果不給定,我們默認從tests文件夾找
7    mypath = r"D:\ktest\tests\test_page1" 
8    cases_to_run = []
9    cases_run_success = []
10    cases_run_fail = []
11    discover_cases = DiscoverTestCases(mypath)
12    mds = discover_cases.get_modules_spec()
13    cases_to_run = unpack_test_cases_from_functions(discover_cases.find_classes_in_module(mds))
14    while cases_to_run:
15        try:
16            name, func, value = cases_to_run.pop(0)
17            if value:
18                func.__call__(name, *value)
19            else:
20                func.__call__(name)
21        except:
22            # traceback.print_exc()
23            cases_run_fail.append(name)
24        else:
25            cases_run_success.append(name)
26    print('Below cases are passed:\n %s' %cases_run_success)
27    print('Below cases are failed:\n %s' % cases_run_fail)
28
29#以上給定了一個簡單的方法,找到所有的呆測試用例,
30#然後一個個執行,並且將測試成功和失敗用例集分別存放,方便後續測試報告的生成。

運行結果如下:

1Below cases are passed:
2 ['test_sum_data_1__1__2__3_''test_sum_data_2__4__5__9_']
3Below cases are failed:
4 []

基本上實現了我們的需求,但是一個測試框架,要考慮的還很多,比如:
1.測試fixture,包括運行前準備和運行後清理。
2.併發執行
3.測試報告
4.郵件發送
5.錯誤截圖
6.log記錄

我後續將帶你逐個擊破,最終寫出自己的測試框架,敬請期待。


 -  End  -

作者:

Kevin Cai, 江湖人稱蔡老師。

兩性情感專家,非著名測試開發。

技術路線的堅定支持者,始終相信Nobody can be somebody。      

                     

· 猜你喜歡的文章 ·

功能測試進階系列直播説明

pytest框架從入門到精通

測試往何處去 -- 新時期測試如何面對挑戰

先點贊、再轉發、麼麼噠↓↓

https://hk.wxwenku.com/d/200085696