?
This document uses PHP Chinese website manual Release
為什么要寫測試?
測試的類型
單元測試
集成測試
功能測試
開發(fā)模式
TDD
BDD
斷言
Mocha.js
WebDriver
定制測試環(huán)境
操作瀏覽器的方法
網(wǎng)頁元素的定位
網(wǎng)頁元素的方法
頁面跳轉(zhuǎn)的方法
cookie的方法
瀏覽器窗口的方法
彈出窗口
鼠標(biāo)和鍵盤的方法
Web應(yīng)用程序越來越復(fù)雜,這意味著有更多的可能出錯(cuò)。測試是幫助我們提高代碼質(zhì)量、降低錯(cuò)誤的最好方法和工具之一。
測試可以確保得到預(yù)期結(jié)果。
加快開發(fā)速度。
方便維護(hù)。
提供用法的文檔。
單元測試(unit testing)指的是以軟件的單元為單位,對(duì)軟件進(jìn)行測試。單元(unit)可以是一個(gè)函數(shù),也可以是一個(gè)模塊或組件。它的基本特征就是,只要輸入不變,必定返回同樣的輸出。
單元測試這個(gè)詞,本身就暗示,軟件應(yīng)該以模塊化結(jié)構(gòu)存在。每個(gè)模塊的運(yùn)作,是獨(dú)立于其他模塊的。一個(gè)軟件越容易寫單元測試,往往暗示著它的模塊化結(jié)構(gòu)越好,各模塊之間的耦合就越弱;越難寫單元測試,或者每次單元測試,不得不模擬大量的外部條件,很可能暗示軟件的模塊化結(jié)構(gòu)越差,模塊之間存在較強(qiáng)的耦合。
單元測試的要求是,每個(gè)模塊都必須有單元測試,而軟件由模塊組成。
單元測試通常采取斷言(assertion)的形式,也就是測試某個(gè)功能的返回結(jié)果,是否與預(yù)期結(jié)果一致。如果與預(yù)期不一致,就表示測試失敗。
單元測試是函數(shù)正常工作、不出錯(cuò)的最基本、最有效的方法之一。 每一個(gè)單元測試發(fā)出一個(gè)特定的輸入到所要測試的函數(shù),看看函數(shù)是否返回預(yù)期的輸出,或者采取了預(yù)期的行動(dòng)。單元測試證明了所測試的代碼行為符合預(yù)期。
單元測試有助于代碼的模塊化,因此有助于長期的重用。因?yàn)橛辛藴y試,你就知道代碼是可靠的,可以按照預(yù)期運(yùn)行。從這個(gè)角度說,測試可以節(jié)省開發(fā)時(shí)間。單元測試的另一個(gè)好處是,有了測試,就等于就有了代碼功能的文檔,有助于其他開發(fā)者了解代碼的意圖。
單元測試應(yīng)該避免依賴性問題,比如不存取數(shù)據(jù)庫、不訪問網(wǎng)絡(luò)等等,而是使用工具虛擬出運(yùn)行環(huán)境。這種虛擬使得測試成本最小化,不用花大力氣搭建各種測試環(huán)境。
單元測試的步驟
準(zhǔn)備所有的測試條件
調(diào)用(觸發(fā))所要測試的函數(shù)
驗(yàn)證運(yùn)行結(jié)果是否正確
還原被修改的記錄
集成測試(Integration test)指的是多個(gè)部分在一起測試,比如在一個(gè)測試數(shù)據(jù)庫上,測試數(shù)據(jù)庫連接模塊。
功能測試(Functional test)指的是,自動(dòng)測試整個(gè)應(yīng)用程序的某個(gè)功能,比如使用Selenium工具自動(dòng)打開瀏覽器運(yùn)行程序。
TDD是“測試驅(qū)動(dòng)的開發(fā)”(Test-Driven Development)的簡稱,指的是先寫好測試,然后再根據(jù)測試完成開發(fā)。使用這種開發(fā)方式,會(huì)有很高的測試覆蓋率。
TDD的開發(fā)步驟如下。
先寫一個(gè)測試。
寫出最小數(shù)量的代碼,使其能夠通過測試。
優(yōu)化代碼。
重復(fù)前面三步。
TDD開發(fā)的測試覆蓋率通常在90%以上,這意味著維護(hù)代碼和新增特性會(huì)非常容易。因?yàn)闇y試保證了你可以信任這些代碼,修改它們不會(huì)破壞其他代碼的運(yùn)行。
TDD接口提供以下四個(gè)方法。
suite()
test()
setup()
teardown()
下面代碼是測試計(jì)數(shù)器是否加1。
suite('Counter', function() { test('tick increases count to 1', function() { var counter = new Counter(); counter.tick(); assert.equal(counter.count, 1); }); });
BDD是“行為驅(qū)動(dòng)的開發(fā)”(Behavior-Driven Development)的簡稱,指的是寫出優(yōu)秀測試的最佳實(shí)踐的總稱。
BDD認(rèn)為,不應(yīng)該針對(duì)代碼的實(shí)現(xiàn)細(xì)節(jié)寫測試,而是要針對(duì)行為寫測試。BDD測試的是行為,即軟件應(yīng)該怎樣運(yùn)行。
BDD接口提供以下四個(gè)方法。
describe()
it()
before()
after()
beforeEach()
afterEach()
下面是測試計(jì)數(shù)器是否加1的BDD寫法。
describe('Counter', function() { describe('Counter', function() { it('should increase count by 1 after calling tick', function() { var counter = new Counter(); var expectedCount = counter.count + 1; counter.tick(); assert.equal(counter.count, expectedCount); }); });
斷言是判斷實(shí)際值與預(yù)期值是否相等的工具。
斷言有assert、expext、should三種風(fēng)格,或者稱為三種寫法。
// assert風(fēng)格 assert.equal(event.detail.item, '(item).); // expect風(fēng)格 expect(event.detail.item).to.equal('(item)'); // should風(fēng)格 event.detail.item.should.equal('(item)');
Chai.js是一個(gè)很流行的斷言庫,同時(shí)支持上面三種風(fēng)格。
(1) assert風(fēng)格
var assert = require('chai').assert; var foo = 'bar'; var beverages = { tea: [ 'chai', 'matcha', 'oolong' ] }; assert.typeOf(foo, 'string', 'foo is a string'); assert.equal(foo, 'bar', 'foo equal `bar`'); assert.lengthOf(foo, 3, 'foo`s value has a length of 3'); assert.lengthOf(beverages.tea, 3, 'beverages has 3 types of tea');
上面代碼中,assert方法的最后一個(gè)參數(shù)是錯(cuò)誤提示信息,只有測試沒有通過時(shí),才會(huì)顯示。
(2)expect風(fēng)格
var expect = require('chai').expect; var foo = 'bar'; var beverages = { tea: [ 'chai', 'matcha', 'oolong' ] }; expect(foo).to.be.a('string'); expect(foo).to.equal('bar'); expect(foo).to.have.length(3); expect(beverages).to.have.property('tea').with.length(3);
(3)should風(fēng)格
var should = require('chai').should(); var foo = 'bar'; var beverages = { tea: [ 'chai', 'matcha', 'oolong' ] }; foo.should.be.a('string'); foo.should.equal('bar'); foo.should.have.length(3); beverages.should.have.property('tea').with.length(3);
Mocha是一個(gè)測試框架,也就是運(yùn)行測試的工具。它使用下面的命令安裝。
$ npm install -g mocha
Mocha自身不帶斷言庫,所以還需要安裝一個(gè)斷言庫,這里選用Chai.js。
$ npm install -g chai
Mocha默認(rèn)執(zhí)行test目錄的腳本文件,所以我們將所有測試腳本放在test子目錄。
Mocha允許指定測試腳本文件,可以使用通配符,同時(shí)指定多個(gè)文件。
$ mocha --reporter spec spec/{my,awesome}.js $ mocha --ui tdd test/unit/*.js etc
如果希望測試非存放于test子目錄的測試用例,可以在test子目錄中新建Mocha的配置文件mocha.opts。在該文件中寫入以下內(nèi)容。
server-tests
上面代碼指定Mocha默認(rèn)測試server-tests子目錄的測試腳本。
server-tests --recursive
上面代碼中,--recursive參數(shù)指定同時(shí)運(yùn)行子目錄中的測試用例腳本。
report參數(shù)用于指定Mocha的報(bào)告格式。
$ mocha --reporter spec server-test/*.js
上面代碼指定報(bào)告格式是spec。
grep參數(shù)用于搜索測試用例的名稱(即it方法的第一個(gè)參數(shù)),然后只執(zhí)行匹配的測試用例。
$ mocha --reporter spec --grep "Fnord:" server-test/*.js
上面代碼只測試名稱中包含“Fnord:”的測試用例。
invert參數(shù)表示只運(yùn)行不符合條件的測試腳本。
$ mocha --grep auth --invert
測試腳本中,describe方法和it方法都允許調(diào)用only方法,表示只運(yùn)行某個(gè)測試套件或測試用例。
describe("using only", function() { it.only("this is the only test to be run", function() { }); it("this is not run", function() { }); });
上面代碼中,只有第一個(gè)測試用例會(huì)運(yùn)行。
describe方法和it方法還可以調(diào)用skip方法,表示跳過指定的測試套件或測試用例。
describe("using only", function() { it.skip("this is the only test to be run", function() { }); it("this is not run", function() { }); });
上面代碼中,只有第二個(gè)測試用例會(huì)執(zhí)行。
如果測試用例包含異步操作,可以done方法顯式指定測試用例的運(yùn)行結(jié)束時(shí)間。
it('logs a', function(done) { var f = function(){ console.log('logs a'); done(); }; setTimeout(f, 500); });
上面代碼中,正常情況下,函數(shù)f還沒有執(zhí)行,Mocha就已經(jīng)結(jié)束運(yùn)行了。為了保證Mocha等到測試用例跑完再結(jié)束運(yùn)行,可以手動(dòng)調(diào)用done方法
WebDriver是一個(gè)瀏覽器的自動(dòng)化框架。它在各種瀏覽器的基礎(chǔ)上,提供一個(gè)統(tǒng)一接口,將接收到的指令轉(zhuǎn)為瀏覽器的原生指令,驅(qū)動(dòng)瀏覽器。
WebDriver由Selenium項(xiàng)目演變而來。Selenium是一個(gè)測試自動(dòng)化框架,它的1.0版叫做Selenium RC,通過一個(gè)代理服務(wù)器,將測試腳本轉(zhuǎn)為JavaScript腳本,注入不同的瀏覽器,再由瀏覽器執(zhí)行這些腳本后返回結(jié)果。WebDriver就是Selenium 2.0,它對(duì)每個(gè)瀏覽器提供一個(gè)驅(qū)動(dòng),測試腳本通過驅(qū)動(dòng)轉(zhuǎn)換為瀏覽器原生命令,在瀏覽器中執(zhí)行。
DesiredCapabilities對(duì)象用于定制測試環(huán)境。
定制DesiredCapabilities對(duì)象的各個(gè)屬性
創(chuàng)建DesiredCapabilities實(shí)例
將DesiredCapabilities實(shí)例作為參數(shù),新建一個(gè)WebDriver實(shí)例
WebDriver提供以下方法操作瀏覽器。
close():退出或關(guān)閉當(dāng)前瀏覽器窗口。
driver.close();
quit():關(guān)閉所有瀏覽器窗口,中止當(dāng)前瀏覽器driver和session。
driver.quit();
getTitle():返回當(dāng)前網(wǎng)頁的標(biāo)題。
driver.getTitle();
getCurrentUrl():返回當(dāng)前網(wǎng)頁的網(wǎng)址。
driver.getCurrentUrl();
getPageSource():返回當(dāng)前網(wǎng)頁的源碼。
// 斷言是否含有指定文本 assert(driver.getPageSource().contains("Hello World"), "預(yù)期含有文本Hello World");
click():模擬鼠標(biāo)點(diǎn)擊。
// 例一 driver.findElement(By.locatorType("path")) .click(); // 例二 driver.get("https://www.google.com"); driver.findElement(By.name("q")) .sendKeys("webDriver"); driver.findElement(By.id("sblsbb")) .click();
clear():清空文本輸入框。
// 例一 driver.findElement(By.locatorType("path")).clear(); // 例二 driver.get("https://www.google.com"); driver.findElement(By.name("q")) .sendKeys("webDriver"); driver.findElement(By.name("q")) .clear(); driver.findElement(By.name("q")) .sendKeys("testing");
sendKeys():在文本輸入框輸入文本。
driver.findElement(By.locatorType("path")) .sendKeys("your text");
submit():提交表單,或者用來模擬按下回車鍵。
// 例一 driver.findElement(By.locatorType("path")) .submit(); // 例二 driver.get("https://www.google.com"); driver.findElement(By.name("q")) .sendKeys("webdriver"); element.submit();
findElement():返回選中的第一個(gè)元素。
driver.get("https://www.google.com"); driver.findElement(By.id("lst-ib"));
findElements():返回選中的所有元素(0個(gè)或多個(gè))。
// 例一 driver.findElement(By.id("searchbox")) .sendKeys("webdriver"); driver.findElements(By.xpath("//div[3]/ul/li")) .get(0) .click(); // 例二 driver.findElements(By.tagName("select")) .get(0) .findElements(By.tagName("option")) .get(3) .click() .get(4) .click() .get(5) .click(); // 例三:獲取頁面所有鏈接 var links = driver .get("https://www.google.com") .findElements(By.tagName("a")); var linkSize = links.size(); var linksSrc = []; console.log(linkSize); for(var i=0;i<linkSize;i++) { linksSrc[i] = links.get(i).getAttribute("href"); } for(int i=0;i<linkSize;i++){ driver.navigate().to(linksSrc[i]); Thread.sleep(3000); }
可以使用size()
,查看到底選中了多少個(gè)元素。
WebDriver提供8種定位器,用于定位網(wǎng)頁元素。
:HTML元素的id屬性
:HTML元素的name屬性
By.xpath:使用XPath語法選中HTML元素
By.cssSelector:使用CSS選擇器語法
By.className:HTML元素的class屬性
By.linkText:鏈接文本(只用于選中鏈接)
By.tagName:HTML元素的標(biāo)簽名
By.partialLinkText:部分鏈接文本(只用于選中鏈接)
下面是一個(gè)使用id定位器,選中網(wǎng)頁元素的例子。
driver.findElement(By.id("sblsbb")).click();
以下方法屬于網(wǎng)頁元素的方法,而不是webDriver實(shí)例的方法。需要注意的是,有些方法是某些元素特有的,比如只有文本框才能輸入文字。如果在網(wǎng)頁元素上調(diào)用不支持的方法,WebDriver不會(huì)報(bào)錯(cuò),也不會(huì)給出給出任何提示,只會(huì)靜靜地忽略。
getAttribute():返回網(wǎng)頁元素指定屬性的值。
driver.get(" driver.findElement(By.xpath("//div[@id='lst-ib']")) .getAttribute("class");
getText():返回網(wǎng)頁元素的內(nèi)部文本。
driver.findElement(By.locatorType("path")).getText();
getTagName():返回指定元素的標(biāo)簽名。
driver.get(" driver.findElement(By.xpath("//div[@class='sbib_b']")) .getTagName();
isDisplayed():返回一個(gè)布爾值,表示元素是否可見。
driver.get("https://www.google.com");assert(driver.findElement(By.name("q")) .isDisplayed(), '搜索框應(yīng)該可選擇');
isEnabled():返回一個(gè)布爾值,表示文本框是否可編輯。
driver.get("https://www.google.com"); var Element = driver.findElement(By.name("q")); if (Element.isEnabled()) { driver.findElement(By.name("q")) .sendKeys("Selenium Essentials"); } else { throw new Error(); }
isSelected():返回一個(gè)布爾值,表示一個(gè)元素是否可選擇。
driver.findElement(By.xpath("//select[@name='jump']/option[1]")) .isSelected()
getSize():返回一個(gè)網(wǎng)頁元素的寬度和高度。
var dimensions=driver.findElement(By.locatorType("path")) .getSize(); dimensions.width; dimensions.height;
getLocation():返回網(wǎng)頁元素左上角的x坐標(biāo)和y坐標(biāo)。
var point = driver.findElement(By.locatorType("path")).getLocation(); point.x; // 等同于 point.getX(); point.y; // 等同于 point.getY();
getCssValue():返回網(wǎng)頁元素指定的CSS屬性的值。
driver.get("https://www.google.com");var element = driver.findElement(By.xpath("//div[@id='hplogo']")); console.log(element.getCssValue("font-size")); console.log(element.getCssValue("font-weight")); console.log(element.getCssValue("color")); console.log(element.getCssValue("background-size"));
以下方法用來跳轉(zhuǎn)到某一個(gè)頁面。
get():要求瀏覽器跳到某個(gè)網(wǎng)址。
driver.get("URL");
navigate().back():瀏覽器回退。
driver.navigate().back();
navigate().forward():瀏覽器前進(jìn)。
driver.navigate().forward();
navigate().to():跳轉(zhuǎn)到瀏覽器歷史中的某個(gè)頁面。
driver.navigate().to("URL");
navigate().refresh():刷新當(dāng)前頁面。
driver.navigate().refresh(); // 等同于 driver.navigate() .to(driver.getCurrentUrl()); // 等同于 driver.findElement(By.locatorType("path")) .sendKeys(Keys.F5);
getCookies():獲取cookie
driver.get("https://www.google.com"); driver.manage().getCookies();
getCookieNamed() :返回指定名稱的cookie。
driver.get("https://www.google.com"); console.log(driver.manage().getCookieNamed("NID"));
addCookie():將cookie加入當(dāng)前頁面。
driver.get("https://www.google.com"); driver.manage().addCookie(cookie0);
deleteCookie():刪除指定的cookie。
driver.get("https://www.google.co.in"); driver.manage().deleteCookieNamed("NID");
maximize():最大化瀏覽器窗口。
var driver = new FirefoxDriver(); driver.manage().window().maximize();
getSize():返回瀏覽器窗口、圖像、網(wǎng)頁元素的寬和高。
driver.manage().window().getSize();
getPosition():返回瀏覽器窗口左上角的x坐標(biāo)和y坐標(biāo)。
console.log("Position X: " + driver.manage().window().getPosition().x); console.log("Position Y: " + driver.manage().window().getPosition().y); console.log("Position X: " + driver.manage().window().getPosition().getX()); console.log("Position Y: " + driver.manage().window().getPosition().getY());
setSize():定制瀏覽器窗口的大小。
var d = new Dimension(320, 480); driver.manage().window().setSize(d); driver.manage().window().setSize(new Dimension(320, 480));
setPosition():移動(dòng)瀏覽器左上角到指定位置。
var p = new Point(200, 200); driver.manage().window().setPosition(p); driver.manage().window().setPosition(new Point(300, 150));
getWindowHandle():返回當(dāng)前瀏覽器窗口。
var parentwindow = driver.getWindowHandle(); driver.switchTo().window(parentwindow);
getWindowHandles():返回所有瀏覽器窗口。
var childwindows = driver.getWindowHandles(); driver.switchTo().window(childwindow);
switchTo.window():在瀏覽器窗口之間切換。
driver.SwitchTo().Window(childwindow); driver.close(); driver.SwitchTo().Window(parentWindow);
以下方法處理瀏覽器的彈出窗口。
dismiss() :關(guān)閉彈出窗口。
var alert = driver.switchTo().alert(); alert.dismiss();
accept():接受彈出窗口,相當(dāng)于按下接受OK按鈕。
var alert = driver.switchTo().alert(); alert.accept();
getText():返回彈出窗口的文本值。
var alert = driver.switchTo().alert(); alert.getText();
sendKeys():向彈出窗口發(fā)送文本字符串。
var alert = driver.switchTo().alert(); alert.sendKeys("Text to be passed");
authenticateUsing():處理HTTP認(rèn)證。
var user = new UserAndPassword("USERNAME", "PASSWORD"); alert.authenticateUsing(user);
以下方法模擬鼠標(biāo)和鍵盤的動(dòng)作。
click():鼠標(biāo)在當(dāng)前位置點(diǎn)擊。
clickAndHold():按下鼠標(biāo)不放
contextClick():右擊鼠標(biāo)
doubleClick():雙擊鼠標(biāo)
dragAndDrop():鼠標(biāo)拖放到目標(biāo)元素
dragAndDropBy():鼠標(biāo)拖放到目標(biāo)坐標(biāo)
keyDown():按下某個(gè)鍵
keyUp():從按下狀態(tài)釋放某個(gè)鍵
moveByOffset():移動(dòng)鼠標(biāo)到另一個(gè)坐標(biāo)位置
moveToElement():移動(dòng)鼠標(biāo)到另一個(gè)網(wǎng)頁元素
release():釋放拖拉的元素
sendKeys():控制鍵盤輸出