JavaScript 程序測試

2021-09-15 16:16 更新

為什么要寫測試?

Web應(yīng)用程序越來越復(fù)雜,這意味著有更多的可能出錯。測試是幫助我們提高代碼質(zhì)量、降低錯誤的最好方法和工具之一。

  • 測試可以確保得到預(yù)期結(jié)果。
  • 加快開發(fā)速度。
  • 方便維護。
  • 提供用法的文檔。

測試的類型

單元測試

單元測試(unit testing)指的是以軟件的單元為單位,對軟件進行測試。單元(unit)可以是一個函數(shù),也可以是一個模塊或組件。它的基本特征就是,只要輸入不變,必定返回同樣的輸出。

單元測試這個詞,本身就暗示,軟件應(yīng)該以模塊化結(jié)構(gòu)存在。每個模塊的運作,是獨立于其他模塊的。一個軟件越容易寫單元測試,往往暗示著它的模塊化結(jié)構(gòu)越好,各模塊之間的耦合就越弱;越難寫單元測試,或者每次單元測試,不得不模擬大量的外部條件,很可能暗示軟件的模塊化結(jié)構(gòu)越差,模塊之間存在較強的耦合。

單元測試的要求是,每個模塊都必須有單元測試,而軟件由模塊組成。

單元測試通常采取斷言(assertion)的形式,也就是測試某個功能的返回結(jié)果,是否與預(yù)期結(jié)果一致。如果與預(yù)期不一致,就表示測試失敗。

單元測試是函數(shù)正常工作、不出錯的最基本、最有效的方法之一。 每一個單元測試發(fā)出一個特定的輸入到所要測試的函數(shù),看看函數(shù)是否返回預(yù)期的輸出,或者采取了預(yù)期的行動。單元測試證明了所測試的代碼行為符合預(yù)期。

單元測試有助于代碼的模塊化,因此有助于長期的重用。因為有了測試,你就知道代碼是可靠的,可以按照預(yù)期運行。從這個角度說,測試可以節(jié)省開發(fā)時間。單元測試的另一個好處是,有了測試,就等于就有了代碼功能的文檔,有助于其他開發(fā)者了解代碼的意圖。

單元測試應(yīng)該避免依賴性問題,比如不存取數(shù)據(jù)庫、不訪問網(wǎng)絡(luò)等等,而是使用工具虛擬出運行環(huán)境。這種虛擬使得測試成本最小化,不用花大力氣搭建各種測試環(huán)境。

單元測試的步驟

  • 準(zhǔn)備所有的測試條件
  • 調(diào)用(觸發(fā))所要測試的函數(shù)
  • 驗證運行結(jié)果是否正確
  • 還原被修改的記錄

集成測試

集成測試(Integration test)指的是多個部分在一起測試,比如在一個測試數(shù)據(jù)庫上,測試數(shù)據(jù)庫連接模塊。

功能測試

功能測試(Functional test)指的是,自動測試整個應(yīng)用程序的某個功能,比如使用Selenium工具自動打開瀏覽器運行程序。

開發(fā)模式

TDD

TDD是“測試驅(qū)動的開發(fā)”(Test-Driven Development)的簡稱,指的是先寫好測試,然后再根據(jù)測試完成開發(fā)。使用這種開發(fā)方式,會有很高的測試覆蓋率。

TDD的開發(fā)步驟如下。

  • 先寫一個測試。
  • 寫出最小數(shù)量的代碼,使其能夠通過測試。
  • 優(yōu)化代碼。
  • 重復(fù)前面三步。

TDD開發(fā)的測試覆蓋率通常在90%以上,這意味著維護代碼和新增特性會非常容易。因為測試保證了你可以信任這些代碼,修改它們不會破壞其他代碼的運行。

TDD接口提供以下四個方法。

  • suite()
  • test()
  • setup()
  • teardown()

下面代碼是測試計數(shù)器是否加1。

suite('Counter', function() {
  test('tick increases count to 1', function() {
    var counter = new Counter();
    counter.tick();
    assert.equal(counter.count, 1);
  });
});

BDD

BDD是“行為驅(qū)動的開發(fā)”(Behavior-Driven Development)的簡稱,指的是寫出優(yōu)秀測試的最佳實踐的總稱。

BDD認(rèn)為,不應(yīng)該針對代碼的實現(xiàn)細(xì)節(jié)寫測試,而是要針對行為寫測試。BDD測試的是行為,即軟件應(yīng)該怎樣運行。

BDD接口提供以下四個方法。

  • describe()
  • it()
  • before()
  • after()
  • beforeEach()
  • afterEach()

下面是測試計數(shù)器是否加1的BDD寫法。

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);
  });
});

斷言

斷言是判斷實際值與預(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是一個很流行的斷言庫,同時支持上面三種風(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方法的最后一個參數(shù)是錯誤提示信息,只有測試沒有通過時,才會顯示。

(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.js

Mocha是一個測試框架,也就是運行測試的工具。它使用下面的命令安裝。

$ npm install -g mocha

Mocha自身不帶斷言庫,所以還需要安裝一個斷言庫,這里選用Chai.js。

$ npm install -g chai

Mocha默認(rèn)執(zhí)行test目錄的腳本文件,所以我們將所有測試腳本放在test子目錄。

Mocha允許指定測試腳本文件,可以使用通配符,同時指定多個文件。

$ 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ù)指定同時運行子目錄中的測試用例腳本。

report參數(shù)用于指定Mocha的報告格式。

$ mocha --reporter spec server-test/*.js

上面代碼指定報告格式是spec。

grep參數(shù)用于搜索測試用例的名稱(即it方法的第一個參數(shù)),然后只執(zhí)行匹配的測試用例。

$ mocha --reporter spec --grep "Fnord:" server-test/*.js

上面代碼只測試名稱中包含“Fnord:”的測試用例。

invert參數(shù)表示只運行不符合條件的測試腳本。

$ mocha --grep auth --invert

測試腳本中,describe方法和it方法都允許調(diào)用only方法,表示只運行某個測試套件或測試用例。

describe("using only", function() {
  it.only("this is the only test to be run", function() {

  });

  it("this is not run", function() {

  });
});

上面代碼中,只有第一個測試用例會運行。

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() {

  });
});

上面代碼中,只有第二個測試用例會執(zhí)行。

如果測試用例包含異步操作,可以done方法顯式指定測試用例的運行結(jié)束時間。

it('logs a', function(done) {
  var f = function(){
    console.log('logs a');
    done();
  };
  setTimeout(f, 500);
});

上面代碼中,正常情況下,函數(shù)f還沒有執(zhí)行,Mocha就已經(jīng)結(jié)束運行了。為了保證Mocha等到測試用例跑完再結(jié)束運行,可以手動調(diào)用done方法

WebDriver

WebDriver是一個瀏覽器的自動化框架。它在各種瀏覽器的基礎(chǔ)上,提供一個統(tǒng)一接口,將接收到的指令轉(zhuǎn)為瀏覽器的原生指令,驅(qū)動瀏覽器。

WebDriver由Selenium項目演變而來。Selenium是一個測試自動化框架,它的1.0版叫做Selenium RC,通過一個代理服務(wù)器,將測試腳本轉(zhuǎn)為JavaScript腳本,注入不同的瀏覽器,再由瀏覽器執(zhí)行這些腳本后返回結(jié)果。WebDriver就是Selenium 2.0,它對每個瀏覽器提供一個驅(qū)動,測試腳本通過驅(qū)動轉(zhuǎn)換為瀏覽器原生命令,在瀏覽器中執(zhí)行。

定制測試環(huán)境

DesiredCapabilities對象用于定制測試環(huán)境。

  • 定制DesiredCapabilities對象的各個屬性
  • 創(chuàng)建DesiredCapabilities實例
  • 將DesiredCapabilities實例作為參數(shù),新建一個WebDriver實例

操作瀏覽器的方法

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)點擊。

// 例一
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():返回選中的第一個元素。

driver.get("https://www.google.com");
driver.findElement(By.id("lst-ib"));

findElements():返回選中的所有元素(0個或多個)。

// 例一
driver.findElement(By.id("searchbox"))
  .sendKeys("webdriver");
driver.findElements(By.xpath("http://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(),查看到底選中了多少個元素。

網(wǎng)頁元素的定位

WebDriver提供8種定位器,用于定位網(wǎng)頁元素。

  • By.id:HTML元素的id屬性
  • By.name:HTML元素的name屬性
  • By.xpath:使用XPath語法選中HTML元素
  • By.cssSelector:使用CSS選擇器語法
  • By.className:HTML元素的class屬性
  • By.linkText:鏈接文本(只用于選中鏈接)
  • By.tagName:HTML元素的標(biāo)簽名
  • By.partialLinkText:部分鏈接文本(只用于選中鏈接)

下面是一個使用id定位器,選中網(wǎng)頁元素的例子。

driver.findElement(By.id("sblsbb")).click();

網(wǎng)頁元素的方法

以下方法屬于網(wǎng)頁元素的方法,而不是webDriver實例的方法。需要注意的是,有些方法是某些元素特有的,比如只有文本框才能輸入文字。如果在網(wǎng)頁元素上調(diào)用不支持的方法,WebDriver不會報錯,也不會給出給出任何提示,只會靜靜地忽略。

getAttribute():返回網(wǎng)頁元素指定屬性的值。

driver.get("https://www.google.com");
driver.findElement(By.xpath("http://div[@id='lst-ib']"))
  .getAttribute("class");

getText():返回網(wǎng)頁元素的內(nèi)部文本。

driver.findElement(By.locatorType("path")).getText();

getTagName():返回指定元素的標(biāo)簽名。

driver.get("https://www.google.com");
driver.findElement(By.xpath("http://div[@class='sbib_b']"))
  .getTagName();

isDisplayed():返回一個布爾值,表示元素是否可見。

driver.get("https://www.google.com");
assert(driver.findElement(By.name("q"))
  .isDisplayed(),
  '搜索框應(yīng)該可選擇');

isEnabled():返回一個布爾值,表示文本框是否可編輯。

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():返回一個布爾值,表示一個元素是否可選擇。

driver.findElement(By.xpath("http://select[@name='jump']/option[1]"))
  .isSelected()

getSize():返回一個網(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("http://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)的方法

以下方法用來跳轉(zhuǎn)到某一個頁面。

get():要求瀏覽器跳到某個網(wǎng)址。

driver.get("URL");

navigate().back():瀏覽器回退。

driver.navigate().back();

navigate().forward():瀏覽器前進。

driver.navigate().forward();

navigate().to():跳轉(zhuǎn)到瀏覽器歷史中的某個頁面。

driver.navigate().to("URL");

navigate().refresh():刷新當(dāng)前頁面。

driver.navigate().refresh();
// 等同于
driver.navigate()
  .to(driver.getCurrentUrl());
// 等同于
driver.findElement(By.locatorType("path"))
  .sendKeys(Keys.F5);

cookie的方法

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():移動瀏覽器左上角到指定位置。

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)和鍵盤的方法

以下方法模擬鼠標(biāo)和鍵盤的動作。

  • click():鼠標(biāo)在當(dāng)前位置點擊。
  • clickAndHold():按下鼠標(biāo)不放
  • contextClick():右擊鼠標(biāo)
  • doubleClick():雙擊鼠標(biāo)
  • dragAndDrop():鼠標(biāo)拖放到目標(biāo)元素
  • dragAndDropBy():鼠標(biāo)拖放到目標(biāo)坐標(biāo)
  • keyDown():按下某個鍵
  • keyUp():從按下狀態(tài)釋放某個鍵
  • moveByOffset():移動鼠標(biāo)到另一個坐標(biāo)位置
  • moveToElement():移動鼠標(biāo)到另一個網(wǎng)頁元素
  • release():釋放拖拉的元素
  • sendKeys():控制鍵盤輸出
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號