發錢了!寧夏這些小鎮每個將補助2000萬元,有你的家鄉嗎?

寧夏微生活2017-05-07 22:24:39

iTesting,愛測試,愛分享

在自動化測試的過程中,測試框架是我們繞不過去的一個工具,無論你是不需要寫代碼直接改動數據生成腳本,還是你需要檢查測試結果甚至持續集成,測試框架都在發揮它的作用。

不同編程語言的實現出來的框架也不盡相同,但是思想總是相通的,比如儘量使框架使用者只關注自己的業務,框架幫助處理錯誤截圖,保存錯誤log,出錯重試甚至跟jenkins持續集成等。

可以説,一個還算合格的測試框架,可以大大提升測試效率;一個優秀的測試框架,説它能把測試人員從繁縟複雜的跟業務無關但又不得不做的工作中解脱出來也不為過。既然框架的作用這麼明顯,那麼有哪些優秀的測試框架可以給我們用呢?

大家都知道, java裏有TestNG, python裏有unittest,pytest等優秀的“官方”框架, 我對python比較熟悉,之前也介紹過很多這方面的文章,相信大家也多少看過一兩篇。
可以説,能把這些經過百萬人檢驗的“官方”測試框架工具用好用順,拿下本職工作根本不在話下,更不用説你還能用好圍繞着這些工具的生態庫了,那簡直是如入無人之地。

那麼,為什麼還要自己寫一個框架呢?

相信常做自動化的都有自己的感悟, 比如自己業務沒那麼複雜,官方框架顯得太重了;比如官方框架依賴很多的第三方庫,每個要單獨安裝,更新,而且每個都有自己的用法,學習成本高;
再比如説,鞋子合腳不合腳,有的人要穿7年才能知道(對,那誰)。

痛點:

對於常用python寫自動化的來説,unittest本身很優秀,但是不支持併發,而且測試報告支持的也不好,也不支持數據驅動。
pytest那裏都好,就是吃了酒香不怕巷子深的虧,那個docouments真要心非常靜才能讀下去, 另外什麼功能都需要額外安裝一個庫,你想併發?好,裝一個pytest-parallel,你想報告html展示?好,裝個pytest-html, 你想要錯誤的測試用例自動重跑一遍?好,裝個pytest-rerunfailures。 強大是強大,感覺有點過於繁瑣了。

解決方案:

基於上述痛點,大部分同學選擇在原有開源框架上二次開發,比如unittest集合多線程threading.Thread實現併發,加入HTMLTestRunner實現報告展示,引入ddt實現數據驅動,然後美其名曰,我寫了個框架,我每次聽到都是先很崇拜,然後晚上暗搓搓拉下代碼一看,MD,這不是就是搭了個積木取名要你命3000嗎?你還不能説人家説自己寫了一個框架不對,何況這個框架通常也很好用。
用pytest就不更用説了,連併發都有兩個庫,一個xdist,一個pytest-parallel,每次都患選擇恐懼症。

再有,開源的框架,畢竟普適多於貼合,跟自己的業務有時候就不那麼緊密,為了使用某個具體功能還得引入很大一個包,也不是非常方便,另外最關鍵的一點是,我總覺得自己還行,想站起來試試 :)

框架是什麼?我是怎麼考慮框架的?

之前分享過幾篇文章,
《測試框架之我見》

《web自動化框架實踐指南》

《接口測試框架實踐》

 《pytest框架從入門到精通》

 這些都是我工作的一些感悟,和對框架的一些思考,可以看到思想也是循序漸進的。你也可以看到不是“官方”框架,就是二次開發的“官方”框架。 當然也可以説我是常懷覬覦之心,不停研究的目的就是我個人非常想有一套完全自己實現的框架。

什麼是ktest?

一句話:

ktest is a common test framework support for Both UI and API test with run in parallel ability。

跟其它的框架有什麼不同?
除標準庫之外,原則上不引入任何第3方庫,所有的一切都自己實現。比如併發就老老實實自己設計規則多線程併發,不用xdist或者pytst-parallel了,比如錯誤重跑,HTML報告都是自己實現了。

參考了誰?

我當然不是閉門造車,參考了unittest,pytest,ddt,還有自己公司的官方框架, 讀了部分源碼,研究了下部分功能實現原理。

實現的功能:

1.多線程併發。(整套框架代碼沒出現任何哪怕一句threading,實現了併發,神奇不,嘿嘿)
2.分佈式併發。(藉助selenium-grid)
3.數據驅動。(一條用例兩條數據會被當成兩個用例,並展示在最終報告裏)
4.同個測試類數據共享,每個測試用例數據獨立。setUpClass, tearDownClass, setUp, tearDown(一看就是unittest的概念,只不過我自己實現了)。
4.動態生成,挑選,運行測試用例。(大量藉助裝飾器)
5.Web UI自動化測試每條用例錯誤自動截屏,記錄log信息,自動重跑機制。
6.HTML報告。
7.XML格式for Jenkins。(馬上完成,姑且算完成吧)
現在還是第一版,我個人想把它寫成一個通用的測試框架,即可服務於web UI自動化測試,又可服務於API測試。當然還有很長的路要走。

詳細介紹

先不介紹技術細節, 先把自己放在一個業務測試,或者剛接觸自動化腳本的測試角色上,我拿到了一個測試框架,我最先想到的是什麼? 如何用對吧? 用這個框架,我原有的測試用例需要做哪些改變?這個框架有哪些方便?你對框架的期待有哪些?
1.使用簡單,介紹詳細。
2.能讓我方便的查找, 生成, 運行, 清理測試用例及測試數據。
3.對業務的侵襲小,我遷移成本低。
4.功能強大,報告美觀。
5.穩定,bug少。

下面就詳細介紹:

安裝:
1#目前部署在自己公司的pypi庫上,後期會上傳到pypi。
2pip install ktest --index-url http://jenkin.xxxx.com:8081/pypi --trusted-host jenkins.xxxx.com
3#安裝好後,你可以在 Python安裝目錄下的如下文件找到安裝的包\Lib\site-packages
你的項目應該包括哪些:

在講用法前,我們先來直觀看下,你的項目目應該是什麼樣子的

你的項目中應該包含:


1.pages package, 這裏面放你所有待測試頁面,每個頁面作為一個page object來保存。
2.tests package, 這個文件夾下面放所有的測試用例,包括你的數據驅動,斷言都在這裏。
3.settings package, 這個裏面放了一些框架裏會用到的參數,當然也可以放你自己的配置。 如果你不知道框架用到哪些變量,你可以暫時不建立它,運行時框架會幫你自動生成。

可以看到,你只需要把精力放在你本身的業務上就好了。

ktest框架組成

package建立好了,我的測試用例,及我的待測頁面要如何組織才能接入框架呢? 別急,我們先來看看框架本身長什麼樣子。

功能列表

看用紅框標記起來的部分:

Common – 框架精華

1.歡迎關注公眾號iTesting,跟萬人測試團一起成長。
2.abstract_base_page.py 這個文件有是為了你自己項目的pages準備的,你的每一個待測頁面或者功能都應該繼承自這個文件中的類,並重寫一些特定的函數。
3.api_base_case.py 創建了為api測試而用的基類。
4.enumeration.py 一些枚舉變量,比如測試類的setup 和tearDown等,主要為了防止代碼裏寫錯及方便修改。
5.get_platform_info.py 判斷運行的系統環境是windows,還是linux, 框架開始前會清理執行環境。
6.get_project_info.py 用於拿到你的項目文件的根目錄。
7.html_reporter.py 用於生成HTML報告。
8.klogger.py 用於全局logger或者每個case獨立logger。
9.selenium_helper.py 基於selenium封裝的一些toolkit, 比如打開瀏覽器,使用chrome headless模式,關閉瀏覽器等。方便你快速開展工作。
10.test_case_finder.py 用於測試用例的查找和組織。 根據你的用户輸入來進行,默認從tests 這個package下找所有被標記為@TestClass, @Test並且enable的用例。
11.test_decorator.py  用於標記測試類,測試用例,並且賦予每個測試類和測試用例獨特的屬性,方便test_case_finder查找。
12.test_filter.py 所有測試用例查找到後,根據用户的輸入進行filter,最終保留出當次運行需要的測試類,測試函數及測試數據的組合。
13.ui_base_page.py UI的公用頁,所有的UI Case 都繼承自此。
14.user_options.py 接受用户參數並分析,最終為框架所用,包括要哪些用例運行,這些用例比如屬於哪個組,那個測試類, 多線程還是單線程。 都在這裏設置。

utiilites -- 拿來即用測試套件

這裏面放一些半成品,比如連接數據庫腳本,比如用excel做數據驅動,對excel進行讀寫的腳本。 用户不需要操心連接的建立,銷燬等。

main.py   用來運行框架,併發運行控制也是在這裏。併發我“捨棄”了threading.Thread, 代碼量下降一半以上,且不用操心鎖。
setup.py  用來把框架打包。

集成你的項目
框架也看了,我的項目也建了, 我們的測試類和測試方法應該怎麼寫?
還拿我一直用的baidu來舉例,你的項目pages package下

 1#bai_du.py
2#coding=utf-8
3__author__ = 'iTesting'
4import time
5from common.abstract_base_page import AbstractBasePage
6from page_objects import page_element
7class BAI_DU(AbstractBasePage):
8    BAI_DU_HOME = "http://www.baidu.com"
9    SEARCH_DIALOG_ID = "kw"
10    SEARCH_ICON_ID = "su"
11    MEMBER_COUNT_XPATH = "//option[@value='10']"
12    SAVE_XPATH = ".//*[@id='save']"
13    search_input_dialog = page_element(id_=SEARCH_DIALOG_ID)
14    search_icon = page_element(id_=SEARCH_ICON_ID)
15    member_count = page_element(xpath=MEMBER_COUNT_XPATH)
16    save_button = page_element(xpath=SAVE_XPATH)
17    def __init__(self, browser):
18        self.browser = browser
19        AbstractBasePage.__init__(self, browser)
20    def is_target_page(self):
21        self.browser.get(self.BAI_DU_HOME)
22        print(self.is_element_displayed_on_page(self.search_icon))
23        return self.is_element_displayed_on_page(self.search_icon)
24    def search(self, search_string):
25        self.search_input_dialog.send_keys(search_string)
26        self.search_icon.click()
27        time.sleep(5)
28    def preferences(self):
29        browser = self.browser
30        browser.get(self.BAI_DU_HOME + "/gaoji/preferences.html")
31        self.member_count.click()
32        time.sleep(1)
33        self.save_button.click()
34        time.sleep(1)
35        browser.switch_to_alert().accept()

每一個你的測試類(待測頁面)你需要:
1.測試類繼承AbstractBasePage, 然後page_object模式就可以使用了。
2.測試類要實現is_target_page函數,返回值是True或者False, 運行中如果返回值False,會Raise Error並停止本測試類後續運行。
3.如果你不用page objectmm模式,你無需遵守上述規定。

你的tests package下測試用例定義

 1# test_baidu.py
2#coding=utf-8
3from common.selenium_helper import SeleniumHelper
4from common.test_case_finder import data_provider
5from common.test_decorator import TestClass, SetUpClass, Test, TearDownClass
6from pages.baidu import BAI_DU
7@TestClass(group='smoke', enabled=True)
8class TestBaidu:
9    test_case_id = '3'
10    @SetUpClass()
11    def before(self):
12        pass
13    def setUp(self):
14        self.browser = SeleniumHelper.open_browser('firefox')
15        self.baidu = BAI_DU(self.browser)
16    @data_provider([('baidu',), ('google',)])
17    @Test()
18    def test_baidu_search(self, x):
19        self.tag= 'tt'
20        """Test search"""
21        self.baidu.search(x)
22        assert 1== 1
23    @Test()
24    def test_baidu_set(self):
25        """Test set preference"""
26        print('NONONO')
27        self.baidu.preferences()
28        assert 1==0
29    def tearDown(self):
30        self.browser.delete_all_cookies()
31        self.browser.close()
32    @TearDownClass()
33    def after(self):
34        pass

注意事項
我來仔細解釋下:
1.test_case_id: 是測試報告裏要顯示的test_case_id, 一般跟你testcenter裏的test_id相同,如果你一個測試類共用一個id, 你定義為類屬性就可以。 如果你每個測試用例不同id,你在每個測試函數裏,重寫它就可以。 框架會為你替換。

2.tags: 是每個用例的tag,定義在類屬性裏,test_finder查找時會解析這個tags,可以是str或者list或者tuple,或者是3者的嵌套。 框架最終會解析成一個list。
比如你定義 tags=[‘smoke’,’bvt’] 和你定義 ‘smoke, bvt’是一樣的效果。 當用户指定了要跑的tag屬性時候,test_finder會根據它的值來做filter。

[email protected](), @TearDownClass() 測試類裝飾器,無輸入參數。 每個測試類,不管它有多少個測試用例,這兩個裝飾器裝飾的函數只會被執行一次。 一般用作測試類公用的數據的初始化,比如説,連接db查找某些值。 請注意, 併發運行,不要在這個函數裏初始化你的browser,會有共享問題。

[email protected](), 測試類的裝飾類, 函數接受兩個個參數一個是group,就是測試類所屬的group,一個是enabled,默認值True。 值為False時, test_finder會把這個測試類略過。

[email protected](), 測試裝飾類, 函數接受一個參數enabled,默認值True。 值為False時, test_finder會把這個測試函數略過。

[email protected]_provider(), 數據驅動裝飾器。 接受一個參數,且此參數必須要iterable. 因為是數據驅動,不太可能只有一個數據,所以這個iterable,我通常我會定義成一個tuple,如果 有多個就是多個tuple, 例如[(1,2,3),(4,5,6)]這種,(1,2,3)會被解析成一條測試數據, (4,5,6)會被解析成另外一條。 這個概念來自ddt,我之前也介紹過相關框架。

[email protected](), @tearDown().兩個函數,每個測試類必須定義,否則運行時框架會報錯。 用作每個測試類的測試函數即每一條測試用例的運行前初始化和運行後的清理。
定義一次, 由它裝飾的函數會在每個測試用例運行前後調用。 一般在裏面初始化web browser和 API的 session。

測試函數,就是以@Test()裝飾的函數,一般是你的業務代碼,你需要自己實現業務流程的操作和斷言。如果用到setUpClass或者setUp裏的方法屬性,你只需要在這些屬性前加self.
它不像pytest或者unittest,此函數名不必以test開頭或結尾。

以上基本參照了pytest和unittest的用法,主要初衷也是為了減少遷移成本。

好,我測試類,測試函數都寫好了,如何跑呢?

可用參數
1#最簡單在命令行裏輸入ktest 即可, 框架會自動查詢所有你項目文件下tests文件夾的測試用例。
2#ktest還支持如下參數:
3usage: ktest [-h]
4             [-t test     targets, should be either folder or .py file, this should be the root folder of your test cases or .py file of your test classes.]
5             [-i user provided tags,     string only, separate by comma without an spacing among all tags. if any user provided     tags are defined in test class, the test class will be considered to run. | -ai user provided tags,    string only, separate by comma without an spacing among all tags.    only all of user provided tags are defined in test class, the test class will be considered to run.]
6             [-e user provided tags,     string only, separate by comma without an spacing among all tags,if any user provided     tags are defined in test class, the test class will be Excluded. | -ae user provided tags,    string only, separate by comma without an spacing among all tags.    all the provided tags must defined in test class as well.]
7             [-I include groups,     string only, separated by comma for each tag.    if any user provided groups are defined in decorator on top of test class, the test class will be considered to run. | -AI exclude groups,     string only, separated by comma for each tag.    all user provided groups are defined in decorator on top of test class, the test class which match will be excluded to run.]
8             [-E exclude groups,    defined in decorator on top of test classstring only, separated by comma for each tag.     If any user provided groups are defined in decorator on top of test class, the test class will be Excluded to run | -AE exclude groups,    defined in decorator on top of test classstring only, separated by comma for each tag.     All user provided groups must defined in decorator on top of test class. the test class which matched will be Excluded to run]
9             [-n int number] [-r dir]

其中:

1-t 是你要運行的測試目標的根目錄,默認是項目下的tests文件夾。
2-i 是測試類裏定義的tags。 tags會被解析成list,用户指定的任何tag只要包含在這個lists裏,並且這個測試函數所屬的TestClass()是enabled和這個測試函數的enabled是True,就表示這個測試類的這個測試函數會被test_filder找到。
3-I 是裝飾測試類的@TestClass()定義的group,包含兩個參數, 符合用户指定的group並enabled, 那麼它裝飾的類會被當作一個測試類被test_finder找到。
4-e 是測試類裏定義的tags。 tags會被解析成list。 用户指定的任何tags包含在list裏,這個測試函數就會被test_finder忽略。
5-E 是測試類裏定義的tags。 tags會被解析成list。 用户指定的任何tags包含在list裏,這個測試函數就會被test_finder忽略。
6-n 併發執行的個數,默認是cpu_count。
7-r 錯誤重跑, 默認是True。重跑再錯誤,所有跟case相關的log和screenshot會被記錄。

Note:
1.這種默認下,測試用例(測試類)的tags解析出來的的任意子集,如果用户指定的group或者tag是包含它,那麼他會被test_finder找到。 比如-test就跑包括了test這個tag的用例。 比如你有兩個測試類,一個測試類的tag是test,另外一個測試類的tags是regression,  用户給了-i test,regression. 那麼這兩個測試類所屬的測試用例都會被掃描到並且添加進待測list裏。
2.所有的用户輸入只支持str。 tag本身不必要加引號,除非它在測試類裏也加了引號。 且多個tag直接用逗號隔開即可,不必加空格。

有的同學會問了,我希望跑同時包括test和regression在內這兩個tags的用例呢? 誰提出的這個需求?我真想指着你的鼻子説:
沒有問題,統統實現!

 1# 定義了tag和group的更加嚴格版。只有用户輸入的參數全部包含在測試用例定義的tags裏,這個測試用例才會被test_finder掃描到。
2  -ai user provided tags,    string only, separate by comma without an spacing among all tags.    only all of user provided tags are defined in test class, the test class will be considered to run.
3                        Select test cases to run by tags, separated by comma,
4                        no blank space among tag values. all user provided
5                        tags must defined in test class as well. tags are
6                        defined in test class with name . eg: tags =
7                        ['smoke''regression']. tags value in test class can
8                        be string, tuple or list.
9
10  -ae user provided tags,    string only, separate by comma without an spacing among all tags.    all the provided tags must defined in test class as well.
11                        Exclude test cases by tags, separated by comma, no
12                        blank space among tag values. all of the tags user
13                        provided must in test class as well. tags are defined
14                        in test class with name . eg: tags = ['smoke',
15                        'regression']. tags value in test class can be string,
16                        tuple or list.
17
18  -AI exclude groups,     string only, separated by comma for each tag.    all user provided groups are defined in decorator on top of test class, the test class which match will be excluded to run.
19                        Select test cases belong to groups, separated by
20                        comma, no blank space among group values. All user
21                        provided group must defined in decorator on top of
22                        test class, the test class which match will be
23                        collected. groups are defined in decorator on top of
24                        test class. eg: group'UI'.
25
26  -AE exclude groups,    defined in decorator on top of test classstring only, separated by comma for each tag.     All user provided groups must defined in decorator on top of test class. the test class which matched will be Excluded to run
27                        Exclude test cases belong to groups, separated by
28                        comma, no blank space among group values. All of
29                        groups user provided must defined in decorator on top
30                        of test class as well. groups are defined in decorator
31                        on top of test class. eg: group'UI'.

事實上, 為避免用法繁瑣及方便用户, -i 和-ai, -e和-ae, -I和-AI, -E和-AE 是兩兩互斥的, 你只能指定其中的一個。

測試報告

下面我們看下一個運行實例

1ktest -I group -n 10 -r True
2執行中console的輸出:
執行中console的輸出:

執行成功後報告的展示:

report會自動生成在你項目根目錄下,以運行時時間戳為文件夾,每個測試用例一個子文件夾。


測試報告加入了run pass, run fail, run error的圖表。 run fail代表真正的fail, run error代表代碼有問題或者環境問題導致的錯誤。同樣報告直接按照測試類filter。

後記:

到此為止,ktest基本成型,也能根據需求完成web UI自動化和API自動化的工作了,不同無非是你在setUP初始化你的driver時候初始化的是你的browser還是request.session. 如果你想實現分佈式併發,也可以在setUP initial selenium Grid, 前提是配置好selenium-server。

還是有一些感悟:

1.框架真不是一蹴而就的,是逐漸演化的。 最後成型的這一版跟我初始的規劃還是有很大差距,有些代碼甚至是不得已的妥協,比如我要出html報告,就要很多測試函數無關的數據收集,那麼這些數據勢必會侵入我的代碼,結果就是我返回的測試函數數據結構很不簡潔。
2.我最得意的是沒有用大家都推崇的多線程threading.Thread,整個框架沒有一行threading.Threadd 代碼而實現了多線程併發,可閲讀性增加了,而且我的代碼量也因此少了三分之一, 雖然踩了不少坑,但也證明條條大路通羅馬,只要理論通了,怎麼實現,完全看個人喜歡。實際上threading.Thread實現太過繁瑣,我幾乎是一開始就否定了它。
3.你完全不需要搭積木, 一磚一瓦也能創建出漂亮的房子。

彩蛋:

放部分代碼段:

test_finder部分代碼

測試類裝飾器代碼

關於更多技術實現細節,我會重新寫一篇文章介紹。更多測試框架技術分享,請往下拉。


 -  End  -

作者:

Kevin Cai, 江湖人稱蔡老師。

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

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

                     

· 猜你喜歡的文章 ·

Python數據驅動深入實踐(一)

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

測試框架實踐--TestFixture



https://www.wxwenku.com/d/200085807