验收测试 4
驗收測試 - 入門
介紹
單元測試主要針對函數/方法做測試,而驗收測試則是主要針對頁面做測試
-
驗收測試在Codeception里的名詞是Acceptance
-
測試用例的存放目錄是/tests/acceptance
-
配置文件是/tests/acceptance.suite.yml,但配置內容跟單元測試并不完全相同
-
運行驗收測試用例之前會運行/tests/acceptance/_bootstrap.php
-
像單元測試一樣,默認添加了一個空模塊,你猜到了,就是/tests/_support/AcceptanceHelper.php!
-
創建驗收測試的命令是php E:\codecept.phar generate:cept acceptance IndexPage大概這樣,下面會介紹
來來來聰明的小孩,咱們開始配置和編輯
開一下cmd謝謝
E: cd project1-tests php E:\codecept.phar generate:cept acceptance IndexPage好了,然后提示E:\project1-tests\tests\acceptance\IndexPageCept.php已經創建好了,咱們去打開它準備編輯一下
不過另外要知道的是,接下來我們要針對頁面進行測試,所以要先確定針對哪個頁面做測試呢?這樣吧,針對我這個博客網站的頁面進行測試吧
先打開acceptance.suite.yml這個配置文件,修改modules - config - PhpBrowser - url這個配置的值,它默認值應該是http://localhost/myapp/的,我們修改成http://www.kkh86.com,這樣就意味著接下來的測試主要是針對這個網站了,當然要跨網站的時候也是有辦法的,再說吧
開始編寫IndexPageCept.php,剛才的命令生成這個測試用例之后,它里面應該是自帶了大概這樣的代碼的:
use project1_tests\AcceptanceTester; $I = new AcceptanceTester($scenario); $I->wantTo('perform actions and see result');我們可以不修改這些,在下面第4行繼續增加代碼:
$I->amOnPage('/'); //切換到配置站點 http://www.kkh86.com 的 / 頁面,拼起來就是 http://www.kkh86.com/ $I->see('我叫KK'); //斷言可以在這個頁面里看到指定的文字運行和結果
這是很簡單的測試代碼,使用php E:\codecept.phar run acceptance IndexPageCept來運行它,輸出結果應該是
OK (1 test, 1 assertion)就是運行了1個測試用例,1次斷言,測試通過,那你將$I->see('我叫KK')改成$I->see('我叫XX')再運行看看結果,失敗了吧!?
我就不把運行結果貼出來了,因為經過之前的單元測試知識學習,你已經會簡單地查看測試結果了,不然的話就是在低估你的智商了對吧哈哈-_-
驗收測試 - 測試報告
緊接著上一章節測試失敗的報告出來之后哦,你要注意,由于測試代碼聲明了要查看的特定字符串我是XX,但是實際上在頁面上根本找不到,在失敗報告出來后,為了讓你自己進一步確認,測試框架會保存一份測試失敗的時候的頁面HTML代碼給你復核,確認是不是真的這樣,那這份HTML代碼放在哪里呢?
且看tests/_output,這個目錄本來是空目錄來的,由于這次運行失敗,測試用例的名稱叫IndexPage,于是它就生成了一個叫IndexPageCept.fail.html的文件,打開里面就是一些HTML代碼,你肯定很熟悉,沒錯,就是你要測試的頁面的HTML代碼,然后你可以手動搜索一下是不是真的沒有我是XX這幾個字————沒吧?
結構化的報告
(新手可以先跳過,未來需要高級應用時再看這個)
運行的時候加入--xml參數的話就是要求生成xml格式的結構化測試報告,比如
php E:\codecept.phar run acceptance IndexPageCept --xml這時候無論測試結果成功與否,都會在_output目錄下產生一個叫report.xml的測試結果報告文件,具體這個文件有什么意義呢,在其它地方教大家部署自動化構建環境(持續集成)時再說吧,現在你只要知道有這東西就好
還有--html和--json兩個報告格式生成選項,你換著試一下就知道了,report.html,report.json,你懂的
驗收測試 - 試著寫幾個行為
回顧之前入門的幾個代碼,我們大概翻譯一下它(如果我翻譯不準也不許投雞蛋!)
use project1_tests\AcceptanceTester; //引用這個“驗收測試者“類 $I = new AcceptanceTester($scenario); //實例化一個測試者,將全局變量$scenario傳進去作為構造參數 $I->wantTo('perform actions and see result'); //我想執行一些動作并且看看結果 $I->amOnPage('/'); //我在 / 這個頁面 $I->see('我叫KK'); //我看到“我叫KK"這串文字發現沒有,那些代碼好像在講英語一樣耶,我想你可能是第一次接觸這樣有趣代碼對嗎!?至少我就是第一次在這個框架里接觸這樣的代碼!太有創意了哈哈
Codeception在驗收測試方面,定義了一個叫測試者的概念,就是上面new的那個類,一般測試者的實例變量名都叫$I,然后要測試的事情抽象出一個動詞出來作為方法名稱,比如see方法,就形成了$I->see($字符串)這樣的代碼,看著好像讀英語一樣。在英文文檔中這個測試者應該叫Actor(演員/行動者?)
開始編碼
好了概念說到這里,將以下代碼貼進之前的測試用例里,覆蓋掉原有的代碼吧親!
use project1_tests\AcceptanceTester; //引用這個“驗收測試者“類 $I = new AcceptanceTester($scenario); //實例化一個測試者,將全局變量$scenario傳進去作為構造參數 $I->wantTo('perform actions and see result'); //我想執行一些動作并且看看結果 $I->amOnPage('/'); //我在 / 這個頁面 $I->see('我叫KK'); //我看得到“我叫KK"這串文字 $I->click('文章'); //我擊帶有“文章“這兩個字的鏈接 $I->seeCurrentUrlEquals('/article/list.html'); //我看到當前網址是'/article/list.html' $I->dontSee('我叫KK'); //我不想看到“我叫KK"這串文字 $I->seeElement('.pArcList'); //我看到class="pArcList"的一個元素 $menuText = $I->grabTextFrom('nav li:nth-child(3) a'); //我通過 nav li:nth-child(3) a 這個CSS選擇器定位到一個元素并捕捉它里面的文本 \Codeception\Module\Asserts::assertEquals('心情', $menuText); //調用斷言模塊斷言變量 $I->click('nav li:nth-child(55) a'); $I->dontSeeCurrentUrlEquals('/article/list.html'); //我不想看到當前的網址是'/article/list.html' $I->seeInTitle('音樂廳'); //我能在title里看到'音樂廳'三個字 $I->amOnPage('/about.html'); //我在'/about.html'這個頁面 $I->seeNumberOfElements('.wrapTabContent', 3); //我看到3個class="wrapTabContent"的元素 $I->amOnPage('/xxxxx.html'); //我在 '/xxxxx.html' 這個頁面 $I->seePageNotFound(); //我看到頁面不存在的錯誤(根據返回狀態碼是否404判斷)好了就寫這么多代碼先吧,你大概都能看懂,然后運行一趟,它提示
Trying to perform actions and see result (IndexPageCept)...等了一會,報告OK (1 test, 9 assertions),結果全部測試通過了
看看失敗的后果
然后你將中間那個$I->seeElement('.pArcList');修改成$I->seeElement('.xxxxx');,當然你也知道會運行失敗,只是咱們看看它測試失敗的報告是啥樣子。..運行后報告內容大概是這樣的:
1) Failed to perform actions and see result in IndexPageCept (E:\project1-tests\tests\acceptance\IndexPageCept.php) Couldn't see element ".xxxxx": Element located either by name, CSS or XPath '.xxxxx' was not found on page.Scenario Steps: 6. I see element ".xxxxx" 5. I don't see "我叫KK" 4. I see current url equals "/article/list.html" 3. I click "文章“ 2. I see "我叫KK" 1. I am on page "/"可以說整份失敗報告的信息都很有用
第1行告訴你哪個測試用例失敗了(因為有時會同時運行多個測試用例,你需要知道在哪個失敗了)
第2行告訴你失敗原因
第3行進一步提示原因的細節
接下來就是Scenario Steps:欄目,它下面有6個運行步驟倒序排列,表示它從第1個測試動作運行到第6個動作,然后在第6個動作失敗了
調試和查看測試進度
關于調試,其實跟單元測試里的調試一模一樣,都要靠codecept_debug函數來輸出調試數據,并且在運行時增加--debug參數
查看測試進度這個嘛,就是因為做這個驗收測試一般會比單元測試慢很多,畢竟它要通過HTTP請求獲取目標頁面的內容,再從內容中搜索各種要see的東西,于是一個完善的驗收測試跑下來你都要等老久,有時候你想知道它現在到底運行到哪了,則可以加上--steps參數,實例:
#可以多個運行參數一起上的! php E:\codecept.phar run acceptance IndexPageCept --debug --steps --json這樣運行的時候它會每跑一個動作都在控制臺有一個輸出,告訴你它現在就在測試這個東西
?
驗收測試 - 元素定位
前面的代碼你看到了,像$I->click('nav li:nth-child(55) a');這里是明顯是通過CSS選擇器定位到了一個a標簽再執行了click
驗收測試開發中最經常用到的就是元素定位了,不然的話怎么找到那個元素去確認它的各種情況呢對吧
那么定位方面Codeception就是提供了CSS選擇器和XPath的支持,但請你不要搞錯成jQuery選擇器了喔!我以前有過幾位同事不小心寫了jQuery的選擇器類似nav li:eq(55) a這樣,其實CSS選擇器根本沒有eq這個偽類嘛,所以他就定位失敗,搞半天來問我,我一看就懂了...哈哈,所以寫慣了jQuery的同學們必須注意這個問題!而且這里的CSS選擇器是CSS3選擇器,以后你定位不到元素上網搜索CSS3選擇器就是了
后面一些斷言方法的參數中會有一些叫做$selector的參數,指的就是CSS選擇器和XPath
......
.......
........
.........
..........
呃,好像這節內容就這么點?專門開一頁?不如這樣吧,有空你可以看看這下面,不然就跳過.
接下來做一些元素定位的練習吧^-^ XPath我不熟悉,下面演示的基本都用CSS選擇器,我相信大家也是比較熟悉CSS的
(下面的定位器并不是針對我的網站寫的,你們自己練習時將配置改成自己的網站,然后自己捏造元素吧!)
$I->see('登陸'); //無選擇器,將直接在整個頁面查找文本 $I->see('登陸', '#mainDiv form'); //在選擇器所指的區域里開始查找文本 $I->see('登陸', '#mainDiv form button'); //更加精確了 $I->dontSee('登陸', '#mainDiv form'); //指定區域里排除文本,就是斷言這個區域里不會有這個文本咯 $I->click('忘記密碼'); //點擊帶有這"登陸"兩個字的a標簽 $I->click('忘記密碼', '#mainDiv .loginForm'); //在指定區域開始查找這個文字的鏈接 $I->click('//form/*[@type=submit]'); //通過XPath定位到一個submit按鈕并點擊它,實現表單提交驗收測試 - 斷言大全
好了其實各種see,dontSee,click,grabTextFrom這些方法都是斷言方法,當它們期望的東西執行不成功時就會停止運行,跟單元測試里的assert系列斷言方法的效果是一樣的
其實有經驗的人士應該想像得出來,就是通過curl把目標網站的源代碼下載來,再往里面搜索字符串這樣實現斷言的
那么我們要針對頁面測試時都能執行哪些斷言呢?這實在是太多啦,抱歉我還沒有精力把全部翻譯并列出來,其實你可以查看tests/acceptance/AcceptanceTester.php這個文件的類里的方法,默認情況下它的所有方法就是可以使用的斷言方法,比如開頭你應該能看到一個叫setHeader的方法,則在編程時可以使用
$I->setHeader('content-type', 'application/json')而不用像單元測試那樣要調用$this->tester
接下來我主要介紹幾個可能經常用得到的方法,大家可以參考這幾個方法去理解其它類似的方法,畢竟不是全部人都能看懂官方那個類里面自帶的英文注解的~
-
amOnPage($url)?將當前頁面切換到指定的URL,這個url可以是完整的URL也可以是一個相對URL
-
amOnUrl($url)?切換當前基礎網址,本身我們 $I->amOnPage('/index.html') 的話會默認相對于yml配置里的URL的嘛,現在如果通過這個方法切換成別的URL,則后面所有 amOnPage 方法的相對URL都是相對于這個新的URL,可以說是動態修改那個URL配置了,僅限該測試用例的當前會話生效,運行結束后下一個測試用例無效
-
see($text, $selector = null)?斷言頁面上會存在$text這個參數的字符,如果指定$selector的話則會在$selector里面查找這個字符
-
canSee($text, $selector = null)?跟see方法一樣是查找文本的,但是如果找不到文本卻不會停止運行,還有其它很多can開頭的方法名稱,都是斷言失敗不停止的方法,但失敗會產生在報告里
-
click($link, $context = null)?點擊一個鏈接,這樣會導致當前頁面變更哦
而$link參數可以是a標簽里的文字,也可以是選擇器
但是其實如果button的name或value的值符合$link的話都會被定位到哦
對于img標簽則會把alt屬性也加入匹配定位的內容中
最后呢如果匹配到的是一個type=submit的button的話則會同時觸發表單的提交
-
fillField($field, $value)?向name="$field"這個表單項填充$value這個值
比如你有一個注冊頁,用select控件來選擇性別,value=2就是女性
$I->fillField('sex', 2); //選擇女性 -
seeInField($field, $value)?斷言name="$field"的表單項的value值與$value是匹配的
接上面fillField的例子,默認性別是未知,value是0,做一個表單修改的測試
$I->seeInField('sex', 3); //斷言默認是3 $I->fillField('sex', 2); //填充2 $I->seeInField('sex', 2); //斷言填充后就是2,但實際是填充后再斷言,在基礎的驗收測試里意義不大,用在后面的WebDriver驗收測試中才最能彰顯測試效果 $I->amOnPage('/user-center.html'); //假如切換到注冊頁面 $I->seeInField('username', '請填寫用戶名'); //斷言用戶名的默認值 -
seeCheckboxIsChecked($checkbox)?斷言$checkbox所指的勾選項是已經勾選了的($checkbox也是一個選擇器!個人覺得該參數應該命名為$selector)
然后如果要斷言是未勾選的就是用dontSeeCheckboxIsChecked($checkbox)
-
submitForm($selector, $params, $button = null)?將$selector選中的表單發起提交,$params是key => value表達的表單參數值,這樣你就不需要慢慢用 $I->fillField 這些方法來填充表單而是在這里直接傳遞參數了, $button 是提交按鈕的選擇器,可以不填,但如果存在 修改/刪除 等多個提交按鈕時就需要用 $button 了
-
seeCookie($name)?斷言存在指定$name的Cookie
-
resetCookie($name)?刪除cookie
斷言沒有指定Cookie的話當然也是用dontSeeCookie($name)了,其實好多see方法都有一個對應的dontSee方法和canSee方法
-
canSeePageNotFound?斷言當前是404頁面,之前的例子里你有見過,我這里不舉例了
-
grabValueFrom($field)?獲取HTML中name="$field"這個表單項的值,但這里要注意,這個表單項必須要被form標簽包起來哦,我試過有一次對一個select標簽的值死活取不出來,后來發現form里的能拿出來,于是才注意到有這個坑...
而關于這個方法的使用嘛,我要舉一下例子,為什么呢?因為驗收測試中默認是沒有assert系列的斷言方法的!你想想喔,如果你想測試的頁面是一個注冊頁,用select控件來選擇性別,默認是未知,value是3,然后你想測試時確認這個值默認是3怎么辦?我認為只能這樣:先通過grabValueFrom方法將表單值獲取出來,再用斷言方法斷言這個值,代碼如下:
//頂上要 use \Codeception\Module\Asserts;$gender = $I->grabValueFrom('sex'); Asserts::assertEquals(3, $gender);這樣來斷言,其實之前的例子有刻意添加過這個演示代碼,具體嘛,將會在?驗收測試 - 擴展?章節中解釋,反正如果你要斷言的話就要引用Asserts,然后再通過靜態方法來調用斷言方法,它的斷言方法和PHPUnit差不多
-
amOnSubdomain($subdomain)?切換到子域名,比如配置時URL是 qq.com,或者 www.qq.com, 又或者是 shop.qq.com ,執行
$I->amOnSubdomain('pay');則是意味著
$I->amOnPage('/xxx.html');會切換到?http://pay.qq.com/xxx.html
驗收測試 - 斷言變量
驗收測試針對頁面進行測試,一般情況下更多的是$I->see('文本')斷言頁面上有特定的文本,或者$I->seeElement('#xxx')這樣有特定的dom元素,但有時候我們要斷言一些變量的值,你想使用$I->assertEqual(3, $var)默認情況下那可不行,因為驗收測試是使用PhpBrowser模塊實現的,而這個模塊并沒有assert系列的方法,所以AcceptanceTester測試器自然就沒有assert方法,$I變量也就調用不了這些方法咯
要讓測試器擁有類似單元測試的斷言方法,需要修改acceptance.yml添加Asserts模塊,但這里添加模塊的時候要注意一下配置文件
回顧一下單元測試的模塊配置,是這樣的吧:
enabled: [Asserts, UnitHelper]這樣表示單元測試引入了兩個模塊,但是驗收測試就不同了,它的enabled配置名稱后面沒有用方括號包住兩個模塊,而是分成兩行,這樣:
enabled:- WebDriver- AcceptanceHelper這樣和方括號是一樣效果的,大家完全可以修改成
enabled: [WebDriver, AcceptanceHelper]所以如果不修改的話,要添加Asserts模塊則是在下面增加一行- Asserts即可
接下來配置好之后請務必記得重構測試器,然后就可以執行$I->assertEquals(3, 3)這樣了
然并卵
雖說引入這個Asserts模塊后使得驗收測試既能斷言dom又能斷言變量,但是實際上你會發現$I->assertInternalType('string', $str)的時候就會報錯說你調用了一個不存在的方法,這難道不是已經引入了Asserts模塊了嗎?
是的,你是引入了,但是很抱歉地告訴你,這個Asserts其實只有二十個斷言方法左右.它并不擁有PHPUnit框架的所有斷言方法.
而單元測試里之所以能用那么多斷言方法是因為它是繼承了PHPunit的斷言類,但這個Asserts模塊并不繼承于它,有興趣的朋友可以看看Codeception的源代碼.
那又不提供所有PHPUnit的方法,要用到的時候沒得用,這算啥呢?
我只能希望框架開發團隊未來能完善這個斷言模塊吧,其實這個Assert模塊直接繼承PHPUnit的類多好,這樣就全部擁有了~淺見而已,他們不這樣做是不是有別的原因咧?
來點實際的
上面我只是告訴大家一些真相,然而大家無論知不知道,反正不用那個Assert模塊就不會有啥問題,但是在驗收測試的時候,我們還是有必要去斷言變量的,既然Asserts模塊不能給到我們全部,我們就找PHPUnit就行了
Codeception已經將PHPUnit集成在里面了,因此我們隨時可以調用PHPUnit的斷言功能.為了方便,我們在使用之前先在頂上聲明use PHPUnit_Framework_Assert;
然后就可以
PHPUnit_Framework_Assert::assertTrue(1 == 2); PHPUnit_Framework_Assert::assertEquals(2, 2); PHPUnit_Framework_Assert::assertInternalType('string', 'abc');而后面的驗收測試代碼中我們偶爾還真的需要使用它去幫我們斷言一些變量
驗收測試 - 不能執行JS
當要測試的頁面上存在JS標簽或者JS代碼時,它們不會被運行起來,比如有這樣的JS代碼:
window.onload = function(){var div = document.getElementById('status'); div.innerText = '數據獲取中...'; };假如#status的text本來是空的,這段JS就是在頁面加載完畢之后將這個#status的內容設置成一個提示文本
我們用各種安防軟件設置本機網速限制為幾KB一秒,再用瀏覽器人工訪問這個頁面可以看到,由于網頁慢,頁面代碼未加載完畢,#status是空的,加載完成后就由于JS代碼被運行所以出現了提示語句
但是在Codeception的基礎頁面測試中并不是這樣,以下斷言是不會成功的
$I->see('數據獲取中...', '#status');原因就是 #status 始終都是空的,因為JS沒有被運行起來,這是因為基礎的驗收測試是靠CURL獲取頁面源代碼,再從源代碼中構造DOM樹,從DOM樹中實現了測試代碼的斷言行為
但并沒有提取源代碼中的JS部分運將JS代碼運行起來,這也需要一個JS引擎才可以實現,但可以告訴你這個codecept.phar包中并不包含JS引擎,呵呵,就不多啰嗦了,我想你都是做了1年左右的PHP了,我說這么點你已經知道具體原因了
但是要實現運行JS也并非不可能,后面的?WebDriver驗收測試?是可以幫到你的,且淡定地先把基礎的驗收測試學好吧
驗收測試 - 測試目標
一句話總結:最好就是測試代碼放在本地服務器,測試目標也是公司本地服務器的測試網站,盡量不要針對線上服務器做增刪改測試
詳解
像這樣的測試注冊功能的代碼
$I->amOnPage('/register.html'); $I->fillField('email', 'xxx@yy.com'); $I->fillField('password', '121212'); $I->submitForm('#registerForm');寫好后,為了確保注冊功能是正常的,理想狀態下應該讓它每天都運行至少1次
但是要測試的網站是哪個呢?你有本地開發的測試網站,也有外網正式部署的網站呀
這里我沒有絕對的答案,問過前輩們也得不到最終答案。
其實只要確認好本地測試站沒事,線上站一般都不會有事的,除非你的代碼夠亂
所以只要確保本地測試網站正常就基本可以了(中大型項目另談)
我想針對線上進行測試好嗎?
如果這個注冊代碼針對線上做測試,你需要解決一個問題就是“刪除測試數據"
每天都測試注冊,肯定會產生一個注冊用戶的數據嘛,然后你要刪除他,可能有N個關聯表的初始化信息都要刪除是不是
如果注冊時還有地區這種表單,填了北京作為測試地區,然后確認注冊,剛好這一時刻有個用戶查找可能認識的人或查找北京的人,對你這個用戶加好友,然后好友申請表中有關于這個用戶的ID了,又或者可能認識的人的ID集中冗余了這個ID,呵呵,這時候你除了要刪除基礎的用戶數據表,還要找到可能關聯的位置把ID都滅掉
所以刪除數據絕對麻煩死你,雖然并非做不到那么狠
我還是想針對線上進行測試好嗎?
"我總是覺得,測試線上才是最實際的!有時候偏偏就是線下沒問題,線上出問題呀!"
我不否認出問題的情況會有差異,但只能斷定為你對項目的規劃和程序設計方面并不到位才造成了問題,穩定的程序基本不會出現這個問題的
但我依然不贊成你對線上做測試!
注意你測試的其實不只是注冊功能吧,登陸好說,填東西點擊登陸成功就是了,但你還可能有評論測試,贊的測試,領取禮物的測試啥的
我問你,評論測試的話,你測試的評論被發表出去,要是被別人引用回復了怎么辦,你銷毀測試評論的時候是不是要把別人的回復也滅了才行,不然別人引用的回復找不到數據可麻煩了。...而且這是程序自動化測試,不是人工測試,所以不能智能地寫評論,只能把你填好的字符串提交上去,或者隨機字符,如果你說不刪除測試數據,是不是就縱容這些測試評論一天一天地堆積?一天測試一次量肯定不大,其實你還不知道有的項目是一小時測試一次的,狠不狠?人家就是那么重視質量
我問你,刪除數據怎么刪除,你的測試代碼一般是不能跟產品服務器放在一起的,就像之前針對我的博客網站測試一樣,你的代碼不必放在我網站服務器上嘛,你也可以運行起來對我網站各頁面做各種斷言。于是,如果我的網站是屬于你自己的,你測試完注冊用戶后,要刪除一個注冊用戶,怎么實現刪除?
-
遠程連接網站數據庫做數據刪除嗎----實際上大部分網站的數據庫是不對外開放連接的,所以你頂多是對自己的小站點實施這個方案,我服了你
-
在網站下添加一個叫delete-test-data.php的文件,測試完后再來一句$I->amOnPage('/delete-test-data.php')來觸發刪除腳本----后果就是如果這個入口被外人知道了的話他也能通過瀏覽器輸入來觸發刪除,一旦你代碼寫不好可能就被這個觸發誤刪了些東西
-
將本地測試服務器的ssh公鑰放到線上服務器中,然后發送通知到線上服務器進行數據刪除----這個還算可以,但實際上允許了通過ssh做更多事情。..公司內其他同事一旦控制了這臺測試服務器,你不能保準他們會干嘛
更多更多。..
最終,刪除測試數據真是麻煩的事,而且通常是很可能刪不干凈的,導致數據庫有壞數據,不必要的冗余等。
聽我的,別對線上服務器做增加數據的操作,包括修改的,因為修改后,你又要將數據恢復是不是?比如修改年齡啊,本來是18歲,你改成20歲,如果你不恢復,明天測試時又修改成20歲,網站應該會提示“您的信息沒有變更“啥的吧
刪除數據的測試更加不行啦哈哈,比如你測試刪除一個好友后,又要實現恢復好友數據的代碼,或者刪除評論,又要恢復評論,而且你刪除后沒恢復期間有別的數據間插入來擾亂了原來的順序就可能會導致麻煩。
所以呢,聽我的,增刪改的操作最好別對線上服務器做測試,因為數據變更好,要恢復到原來的狀態是非常非常麻煩的事情。畢竟數據經常有多方面的關聯性
只做查詢的測試倒是可以,起碼能確認頁面是正常的,或者個別數據的增刪改測試后,不恢復的話都完全不會造成影響就是,這畢竟也只是少數
一般本地測試網站怎么增刪改數據都無所謂,不搞恢復工作也無所謂,因為可以直接從線上鏡像一份數據下來,或者先備份后測試,再恢復備份數據庫
驗收測試 - 驗證碼的問題
重復一下之前這段針對注冊表單做測試的代碼,我下面加一些內容請你注意:
$I->amOnPage('/register.html'); $I->fillField('email', 'xxx@yy.com'); //填充郵箱 $I->fillField('password', '121212'); //填充密碼 $I->fillField('verify', '填什么好呢'); //填充驗證碼 $I->submitForm('#registerForm');其實一般都會有個驗證碼,那就是驗證碼方面我們填什么好呢?你的程序不容易自動識別驗證碼圖片的文字呀,如果你寫死一個驗證碼或者隨機生成一個填寫,那99.999999%會提示你驗證碼錯誤咯,導致注冊能否正常都難以測試.
所以本地測試又有這樣一個好處,我們可以在網站程序里大概這樣根據環境判斷結果來生成不同的驗證碼:
if(PROJECT_ENV == 'test'){$verifyImage->drawText('121212'); //固定測試服務器的所有驗證碼為121212 }else{ $verifyImage->drawText(mt_rand(138772, 923839)); //隨機生成 }這樣測試代碼就可以總是填寫121212這個驗證碼了
另外其實這樣不止是對自動化測試有好處,對人工測試也有好處,你就不必耗費腦力去分析驗證碼是什么文字啦,很快速地填寫121212這個驗證碼就行了,關于為什么使用121212方面你可以轉到我這篇經驗分享中了解一下?經驗分享 - 測試 - 統一密碼和驗證碼
驗收測試 - 最好別寫死URL
比如切換到一個5號分類商品頁面$I->amOnPage('/product/5')
但我并不建議大家實際編程時真的這樣寫死URL,因為以后如果你們的URL規則變了呢,比如變成了/shop/5.html這樣子,測試用例又要修改相關的URL啦,好麻煩的
多數框架都提供了生成URL的方法,我們在_bootstrap.php里引入項目的框架,做好初始化工作,然后就大概這樣實際應用(我拿Yii2框架打比方):
//上面 use yii\helpers\Url;$I->amOnPage(Url::to(['product/category', 'id' => 5]));這樣的話,只要控制器和方法名稱不變就好,實際生成后的偽靜態URL就看項目配置了嘛
驗收測試 - Ajax
之前跟大家演示的都只是些頁面URL、元素和文本的斷言,但是如果要異步獲取一個數據列表的數據時,我們通常就要往一個地址獲取json數據回來再用js把數據渲染到列表里,好了我們如何測試這個異步獲取數據列表的接口輸出的數據是否正常呢?
由于它輸出的數據是json,假設它的地址是datalist.php,那么我們也不能這樣實現斷言
$I->amOnPage('/datalist.php'); $I->see('你一定很頭疼這里寫什么'); $I->seeElement('就一串JSON字符串,哪有什么Html Element?');來,正題,問題就在上面,解決辦法在此
$param = ['page' => 2, //假設我們要第2頁的數據'type' => 3, //假設數據有類型~ ]; //這個$param是要異步請求時提交上去的參數 $I->sendAjaxRequest('get', '/datalist.php', $param); //這樣其實就相當于 /datalist.php?page=2&type=3 $I->seeResponseCodeIs(200); //斷言請求后,服務端響應回來的報文狀態碼應該是200 $browser = \Codeception\SuiteManager::$modules['PhpBrowser']; //這樣來取出一個模塊 $jsonString = $browser->client->getInternalResponse()->getContent()->__toString(); //通過模塊獲取響應正文,就是那串json,但必須轉成string(注意我代碼后面有toString的調用),否則你會得到一個對象,這框架抽象得挺厲害,連個響應報文內容都是對象 $jsonArray = json_decode($jsonString, true); \PHPUnit_Framework_Assert::assertInternalType('array', $jsonArray); //斷言解碼后的類型 \PHPUnit_Framework_Assert::assertEquals(20, count($jsonArray)); //斷言數據個數這是一個斷言ajax請求的例子,其實這里用到的幾個方法也沒寫到前面的斷言大全里,因為我想你慢慢深入,不要一下子迎來太多東西.但其實經驗豐富的人士就算不看英文文檔,光看AcceptanceTester這個類的方法就已經知道有這些東西了
好了我下一節繼續補充一下斷言
驗收測試 - 斷言大全 - 補充
-
sendAjaxRequest($method, $uri, $params = null):發送一個ajax請求,$method是請求的方式,比如get,post,delete,put,也可以自定義請求方式,具體看服務端程序是否擴展了這個請求方式并作響應了
-
sendAjaxGetRequest($uri, $params = null)用get方法發送一個ajax請求,跟sendAjaxRequest('get', ...)是一樣的
-
sendAjaxPostRequest($uri, $params = null)用get方法發送一個ajax請求,跟sendAjaxRequest('post', ...)是一樣的
-
seeResponseCodeIs($code)?斷言上次發生HTTP請求后的響應狀態碼
-
setHeader($header, $value)?設置下一次請求的header
-
attachFile($field, $filename)?附加一個文件,以$field這個字符串變量來命名,比如$field叫image的話,就是PHP角度訪問的那個$_FILES['image']了; 而$filename就是附加的文件是哪個文件,是你當前測試用例所在磁盤上的物理路徑哦
-
amHttpAuthenticated($username, $password)?用指定的用戶名和密碼提交到當前的Http認證中,認證不通過將導致斷言失敗
就寫到這了,我還真寫不完整個AcceptanceTester的斷言,什么時候閑得蛋疼了,或哪位朋友有空義務幫忙寫寫再說吧~~幫忙寫的話用markdown哦^-^
驗收測試 - Db模塊協助
好了,希望你也認同驗收測試代碼應該是部署在本地服務器,然后最好也只針對本地測試網站做測試這個理念,這樣才好學本節的內容。
其實不管怎樣,本節內容的前提就是:測試代碼和被測試的網站都處于同一個內網,或者有辦法共同連接同一個數據庫
這樣就好玩多了,因為這里還能夠去測試數據庫的變更和站點UI是否相對,中間緩存又是否已經生效。
添加模塊
首先我們要為驗收測試添加一個叫Db的模塊,它是自帶模塊,只要在配置文件上追加一下名稱并重構測試器即可
但是追加模塊名稱后還需要配置數據庫的用戶和密碼信息以便這個模塊能連接到數據庫上,在codeception.yml全局配置的modules - config - Db里為Db模塊配置各項信息,它默認提供了幾個配置選項(即使以前沒有使用該模塊,但有這個信息配置存在也不會影響),而dsn就是PDO的dsn了,因為它是靠PDO來靠作數據庫的
實踐
然后比如要測試"贊"的功能正確
$testBookId = 99; //要測試的書籍ID $bookData = $I->grabFromDatabase('book', '*', ['id' => $testBookId]); //從數據庫取出書籍記錄 $I->sendAjaxPostRequest('/book/support.do', ['id' => $testBookId]); //假設這是對 ID為99的一本書進行"贊/喜歡"操作 $I->seeInDatabase('book', [ 'id' => $testBookId, 'support' => $bookData['support'] + 1, ]); //斷言support字段被加1了 $I->dontSeeInDatabase('book', [ 'id' => $testBookId, 'support' => $bookData['support'], ]); //或者斷言不會是原來的值由于測試目標網站和測試代碼連的是同一個數據庫,所以理論上從數據庫讀出的數據跟網站上呈現的數據應該是能對得上的,這樣你就能確保一些讀取數據庫數據顯示的代碼是設計正確的了
個人經常用的是往數據庫里隨機抽一條數據記錄,然后拿這個數據去站點的某個表單中填一填看看反應是不是正確的
還有個haveInDatabase方法,我覺得用途不是很大,有興趣的話自己看看測試器的備注試用一下吧
驗收測試 - 基礎 - 自動恢復測試數據
測試過程中可能會產生一些數據,比如
-
測試添加一個用戶,這樣數據庫里就會多一個用戶了,平均每天運行約5次添加用戶的測試的話,則100天后數據庫就會多出500個用戶了。。。其它數據也是這樣滾雪球一樣累加
-
測試抽獎,于是大獎可能被抽到了,下次再測試就沒有了大獎可抽
-
測試禁用功能,數據status被標記為禁用后,下次再測試禁用就會update影響行數為0了
-
測試刪除指定數據,下次再測試又找不到這條數據刪除了
所以大家遲早都會慢慢地遇到這些需要重復測試的數據支持功能
我以前不會弄的時候真的好笨,添加的用戶在after方法里執行刪除,抽過的獎也重新標記為未抽獎。。。
這樣就寫了好多數據恢復代碼,真是累覺不愛!想想感覺自己也有點強大,整個項目下來N多測試,就這些數據撤回的代碼居然都全給寫下了測試代碼,把一個個測試場景的數據恢復了
發現有捷徑
怪我以前沒不夠認真看Db模塊的教程啊,原來它可以支持快速恢復數據!
還記得在phpmyadmin導出的.sql文件,以及mysqldump命令導出的文件嗎?里面其實就是一句句SQL語句是吧,導入時,就是執行了這些語句,然后就往數據庫里重新建立了整個數據庫
其實Codeception的原理也是一樣,實現步驟是這樣的:
我們先導出一個.sql文件,然后放在tests/_data目錄下命名為dump.sql,
在測試項目的根目錄的codeception.y2ml配置Db模塊
里面除了配置dsn、username、password這些選項滿足連接數據庫以外,還有一個叫dump的配置選項,它要求提供一個測試項目的相對文件路徑,這個文件就是我上面講的數據庫導出文件,一般后綴我們都命名為.sql
新建空的數據庫,比如dsn里配置的數據庫名稱叫shop的話,那你就要在數據庫服務器上先準備一個空的shop數據庫
在需要測試的測試類型配置文件中開啟Db模塊,比如要在單元測試中使用自動恢復數據功能,就要在unit.suite.y2ml里開啟Db模塊了,記得build一下
然后每次運行測試的時(不論是單元測試還是驗收測試),Codeception都會將這個dump.sql作為數據庫鏡像導入到dsn所指的數據庫中,于是測試代碼就可以基于全新的數據進行測試了
運行完測試后你可以發覺shop數據庫已經不是一個空數據庫,而是有表有數據的,正好就是dump.sql里的東西
下次再測試時,它又重新導入一次,就算shop已經有了數據,Codeception也會先清空這些數據,再將dump.sql導入
注意點:dump.sql里的建表語句必須有IF NOT EXISTS判斷,就以phpmyadmin導出的數據庫文件為準,這是直接可以用來做dump.sql的,如果沒有的話會導致第二次運行時無法覆蓋已有數據,具體細節感興趣的話請看Codeception源碼
驗收測試 - 擴展(模塊)
和單元測試一樣,test/acceptance.suite.yml里的modules - enabled里面是驗收測試的模塊配置,
于是你也知道了,驗收測試用了PhpBrowser和AcceptanceHelper這兩個模塊,而AcceptanceHelper就是_support目錄下的AcceptanceHelper,默認是個空類
增加我們的方法
那當然就是像當然測試那樣,對自帶的tests/_support/AcceptanceHelper.php這里的類做編輯,添加你想要的方法咯,比如我添加這樣一個方法:
public function login($username, $password){ $browser = $this->getModule('PhpBrowser'); //要先取出瀏覽器模塊,這個模塊必須是yml里已經配置了的,否則get出個null $browser->amOnPage('/login.html'); //其實之前測試用例的$I調用的就是PhpBrowser模塊的方法,這里直接用browser來調用效果是一樣的 $browser->see('登陸', '#loginForm'); $browser->fillField('username', 'test1'); $browser->fillField('password', '121212'); $browser->fillField('verify', '121212'); $browser->submitForm('#loginForm'); $browser->see('會員中心', '#position'); }然后重構一下測試器,這里它會同時將單元測試的Tester也一起重新構造,盡管你沒對單元測試的模塊做改變
然后可以看到tests/acceptance/AccepanceTester.php里多了一個login($username, $password)方法
那么你的測試用例可以這樣寫了:
use project1_tests\AcceptanceTester; $I = new AcceptanceTester($scenario); $I->login(); $I->amOnPage('/friends'); //假設去好友查看頁面 $I->see('可能認識的人'); //假設去商店 $I->see('下一頁'); //斷言有下一頁按鈕 $I->seeNumberOfElements('#friends li', 20); //斷言好友列表里有20個 //....更多登陸后才能做的斷言于是,你也能封裝更多更多的方法到Helper里了
?
總結
- 上一篇: 运维学习第三弹
- 下一篇: ARouter 源码历险记 (一)