初步了解Angular 2端到端的測試
有許多原因使我過去不愿對自己的應(yīng)用添加自動化測試。原因之一是不知道其中的效益成本比率,另一個(gè)原因是考慮到集成到現(xiàn)有生產(chǎn)環(huán)境的應(yīng)用可能比較難。測試應(yīng)用而不用從頭重構(gòu)代碼,僅僅只是引入測試要怎么做呢?
首先我們先從簡單區(qū)分測試的類型開始。應(yīng)用測試有很多類型,但最為常見的是單元測試及端對端測試(亦稱為集成測試)。單元測試是測試代碼自身行為的一種測試。在用戶看來什么也沒有做,但可以確保其方法能達(dá)到期望的目的。集成測試是模仿用戶行為的一種測試。比如說,登陸系統(tǒng),創(chuàng)建帖子,退出系統(tǒng)等這些操作都可以自動化,并且你可以用眼睛看到其過程是怎么發(fā)生的。
這兩種類型的測試,它們通常彼此結(jié)合使用。對于新的開發(fā)來說,這將是理想的。如果時(shí)間有限制,或繼承于現(xiàn)有項(xiàng)目的情況下,端對端測試或許比單元測試更加合適。因?yàn)槲覀儾⒉恍枰^多了解先前的代碼庫,同樣可以覆蓋更多場景,這將比單元測試更快,因?yàn)閱卧獪y試并不測試單個(gè)單元而是整個(gè)場景。
單元測試依然重要,但如果你必須要在開始的時(shí)候選擇一個(gè),我認(rèn)為端對端測試會是更好的選擇。在這篇文章中,我將測試一個(gè)現(xiàn)有的Angular 2的待辦事項(xiàng)的項(xiàng)目。我將使用集成測試,并覆蓋一系列的場景。
如果你需要熟悉Angular 2的入門,請看這篇文章 Angular 2 article from Jscrambler。
被測試的場景
- 當(dāng)應(yīng)用初始化加載時(shí),有3件待處理的事情
- 需要加載新待辦事項(xiàng)
- 點(diǎn)擊一個(gè)待辦事項(xiàng),然后跳到待辦事項(xiàng)的詳細(xì)頁
- 刪除一個(gè)待辦事項(xiàng)
- 編輯一個(gè)待辦事項(xiàng)標(biāo)題,然后保存后,可以在首頁的待辦事項(xiàng)列表中看到新標(biāo)題
- 不允許保存一個(gè)空的待辦事項(xiàng),在點(diǎn)了禁用的保存按鈕后,待辦事項(xiàng)的列表依然還是原來的總數(shù)
- 起初加載首頁時(shí),添加新待辦事項(xiàng)的按鈕應(yīng)處于禁用狀態(tài)
- 保存待辦事項(xiàng)的按鈕僅應(yīng)在輸入待辦事項(xiàng)標(biāo)題后處于編輯狀體
Todo 應(yīng)用程序概述
讓我們簡單的描述一下TODO應(yīng)用程序。應(yīng)用程序?qū)⑹紫仍谥黜撋狭谐龃k事項(xiàng)清單。有三個(gè)待辦事項(xiàng)會詳細(xì)列出。
這寫數(shù)據(jù)不會由服務(wù)端提供,而是從設(shè)備文件硬編碼中加載。
在首頁上,我們可以添加新的待辦事項(xiàng)。我們可以通過點(diǎn)擊代辦事項(xiàng)的標(biāo)題來訪問其詳細(xì)信息頁面。在此頁面上,我們可以編輯代辦事項(xiàng)標(biāo)題或刪除待辦事項(xiàng)。
克隆并設(shè)置Todo 應(yīng)用程序
1、首先克隆我已經(jīng)推送到存儲庫這里但未經(jīng)測試的應(yīng)用程序,確保你是在主分支克隆的。接下來,你需要安裝幾個(gè)工具以便能進(jìn)行下面的操作,在本教程中,使用Candidate已經(jīng)發(fā)布的Angular 2,版本為2。
2、確保您已安裝NodeJS的版本是Node 4.x.x或更高版本。
3、使用以下命令安裝節(jié)點(diǎn)依賴項(xiàng):
- npm install
當(dāng)然是在克隆的存儲庫中操作
4.使用Angular-CLI進(jìn)行開發(fā)。安裝Angular-CLI 全局使用下面命令:
- npm install -g angular-cli@latest
5.Angular 2 端到端測試使用被稱為 protractor的工具來運(yùn)行,安裝protractor 全局使用下面命令:
- npm install -g protractor
6.安裝所有依賴項(xiàng)后,使用以下命令啟動開發(fā)服務(wù)器:
- ng serve
然后導(dǎo)航到瀏覽器地址:http://localhost:4200,您將看到三個(gè)待辦事項(xiàng)列表。
如果您在啟動服務(wù)器時(shí)遇到問題,您可以參考stackoverflow issue來解決問題。
Angular 2測試相關(guān)的重要概念
端對端測試的文件夾在e2e。 其中有一個(gè)已預(yù)備好的案列文件,文件名為es2/app.e2e-spec.ts。
其中的測試文件是用 jasmine 框架開發(fā)的。有很多方式模塊化、組織Angular 2端對端測試,但這里為了方便,這篇文章都將在一個(gè)文件中進(jìn)行。
我們這個(gè)應(yīng)用僅有一個(gè)功能那就是待辦事項(xiàng)。為了滿足大家的好奇心或有人在想比上述更復(fù)雜的情況,試想一個(gè)需要測試訂單、用戶配置等功能更復(fù)雜的應(yīng)用的場景。對此測試的場景,我將在e2e文件夾中分別為每個(gè)待測試功能創(chuàng)建一個(gè)文件夾,并將各自測試文件放置其中。
這種情況下,我們將有兩個(gè)文件夾分別命名為e2e/orders 和 e2e/userProfile。每個(gè)文件夾中僅會有一個(gè)測試文件,或者為了滿足更多待測試功能的需要而創(chuàng)建多個(gè)測試文件。需要注意的一點(diǎn)是每個(gè)測試文件都以word e2e-spec.ts結(jié)尾,這樣Protractor測試工具才可以加載到。
Ok,還是回到我們簡單的單個(gè)測試文件。如果你略有查看此文件,你會發(fā)現(xiàn)文件的頭部有個(gè)導(dǎo)入聲明(import statement)。導(dǎo)入是聲明若干測試文件中所用的普通函數(shù)是源于哪里。然而這篇文章,我們不會使用這個(gè),而將這個(gè)視為函數(shù)庫。
在引入聲明(import statement)之后,我們有一個(gè)描述性的代碼塊,它的兩個(gè)函數(shù)調(diào)用即 beforeEach 和它里面的回調(diào)。
在描述代碼塊內(nèi),每個(gè)回調(diào)傳遞給 beforeEach 都需要測試。
每個(gè)測試再把里面的回調(diào)函數(shù)傳遞給它。
讓我們用命令運(yùn)行當(dāng)前的測試
- protractor
如果你在運(yùn)行 protractor 兩個(gè)命令中的一個(gè)時(shí)有問題,請參考這里。
- ./node_modules/protractor/bin/webdriver-manager update
或者
- webdriver-manager update
要是當(dāng)前的測試失敗,可能會在首頁看到“app works”這樣的文本提示。這并不是因?yàn)槲覀冃薷牧耸醉摰膬?nèi)容就出現(xiàn)這種情況。
在我們開始編寫我們的測試之前,讓我們理解一些重要的通用函數(shù),然后使用 Angular 2 的端到端測試。
導(dǎo)航到頁面
在測試文件中,有一個(gè)browser全局變量。它使用import語句引入
- import { browser, element, by } from 'protractor/globals';
你現(xiàn)在可以添加上。
例如,我們使用下面語句導(dǎo)航到你的應(yīng)用程序中可用的任何頁面
- browser.get('/');
到你的主頁,和
- browser.get('/users');
到users頁面。請注意,這些網(wǎng)址是相對的URL,我們也可以使用絕對URL,但是我建議使用相對URL,因?yàn)槿绻愕挠蛎淖?,這更易于維護(hù)。
選擇元素
通常的做法是在當(dāng)前頁面上選擇元素。你可以通過一個(gè)叫做element的全局變量選擇元素。它接受可以使用全局by創(chuàng)建的定位器。
使用選擇具有g(shù)reen類的p標(biāo)簽例子如下
- let greenParagraph = element(by.css('p.green'));
選擇多個(gè)元素,有些輕微的變化
- let greenParagraphs = element.all(by.css('p.green'));
這將給出一個(gè)數(shù)組,而不是一個(gè)單一的元素。
抓取元素文本
要得到一個(gè)元素的文本,你必須先選擇它,然后調(diào)用getText方法,想下面這樣。
- let greenParagraph = element(by.css('p.green'));
- let text = greenParagraph.getText();
點(diǎn)擊元素
點(diǎn)擊元素可以使用下面的語法完成
- let submitButton = element(by("form .submit-button"));
- submitButton.click();
統(tǒng)計(jì)元素
我們也可以使用下面的語法來統(tǒng)計(jì)元素個(gè)數(shù)。
- let blueParagraphsList = elements.all(by("p.blue"));
- let count = blueParagraphsList.count();
Test Scenarios測試方案
對于那些不太尋常的概念,我們列出了上面覆蓋的場景。
確認(rèn)要做的首要三件事
當(dāng)應(yīng)用程序初次被加載的時(shí)候,我們有將要做三件事。
在測試文件 e2e/app.e2e-spec.ts 的內(nèi)部, beforeEach 代碼塊的下面,刪除調(diào)用它的函數(shù),并在下面添加
- it("should show three todos when we first load the todo app", () => {
- browser.get("/");
- let todos = element.all(by.css(".todos .todo"));
- expect(todos.count()).toEqual(3);
- })
不要忘記在這個(gè)文件的頂部添加這個(gè)導(dǎo)入聲明
- import { browser, element, by } from 'protractor/globals';
現(xiàn)在,當(dāng)你運(yùn)行 protractor 命令,另一個(gè)瀏覽器將會被打開且迅速關(guān)閉,在你的控制臺上你能看到通過測試的時(shí)候顯示為綠色。
好的!我們剛剛已經(jīng)寫完了我們第一個(gè)通過 Angular 2 的端到端測試。
添加一個(gè)新的待辦事項(xiàng)
現(xiàn)在進(jìn)行下一個(gè),我們可以添加一個(gè)待辦事項(xiàng)。讓我們用下面的代碼添加一個(gè)測試塊
- it("should be able to add a new todo", () => {
- browser.get("/");
- let newTodoInput = element(by.css(".add-todo input[type=text]"));
- newTodoInput.sendKeys("Todo 4");
- let newTodoSubmitButton = element(by.css(".add-todo input[type=submit]"));
- newTodoSubmitButton.click();
- let todos = element.all(by.css(".todos .todo"));
- expect(todos.count()).toEqual(4);
- })
我們在這里要做的是在待辦事項(xiàng)輸入框中輸入文本并提交表單。然后我們檢查是否有四個(gè)待辦事項(xiàng)。 如果是的話,測試通過。
我們剛剛介紹了另一個(gè)函數(shù)sendKeys,它可以訪問一個(gè)選中的元素,常用于輸入文本到輸入框這類元素中。
查看待辦事項(xiàng)的詳情頁
我們應(yīng)該能單擊一個(gè)待辦事項(xiàng)轉(zhuǎn)到該待辦事項(xiàng)的詳情頁,讓我們用下面的測試實(shí)現(xiàn)它吧。
- it("should be able to click on a todo on the homepage and get to the details page", () => {
- browser.get("/");
- let firstTodo = element.all(by.css(".todos .todo")).first();
- let firstTodoText = firstTodo.getText();
- firstTodo.click();
- let inputFieldText = element(by.css("todo input[type=text]")).getAttribute("value");
- expect(inputFieldText).toEqual(firstTodoText);
- })
刪除一個(gè)待辦事項(xiàng)
我們應(yīng)該能刪除一個(gè)待辦事項(xiàng)?,F(xiàn)在讓我們試著刪除一個(gè)待辦事項(xiàng)看看能不能成功。
我們將轉(zhuǎn)到待辦事項(xiàng)頁并單擊刪除鏈接,當(dāng)我們返回主頁時(shí),我們能看到減少了一個(gè)待辦事項(xiàng)。
- it("should be able to delete a todo", () => {
- browser.get("/");
- let firstTodo = element.all(by.css(".todos .todo")).first();
- firstTodo.click();
- let deleteLink = element(by.cssContainingText("span", "Delete"));
- deleteLink.click();
- let todosList = element.all(by.css(".todos .todo"));
- expect(todosList.count()).toEqual(2);
- })
編輯一個(gè)待辦事項(xiàng)的標(biāo)題
我們能編輯待辦事項(xiàng)的標(biāo)題,保存后能在主頁的待辦事項(xiàng)列表中顯示新標(biāo)題。
- it("should be able to edit a todo title", () => {
- browser.get("/");
- let firstTodo = element.all(by.css(".todos .todo")).first();
- firstTodo.click();
- let todoInputField = element(by.css("todo input[type=text]"));
- todoInputField.clear();
- todoInputField.sendKeys("Editted Todo1 Title");
- let saveButton = element(by.css("todo button[type=submit]"));
- saveButton.click();
- firstTodo = element.all(by.css(".todos .todo")).first();
- let firstTodoText = firstTodo.getText();
- expect(firstTodoText).toEqual("Editted Todo1 Title");
- })
不能保存空的待辦事項(xiàng)
當(dāng)我們想保存一個(gè)空的待辦事項(xiàng)時(shí),我們無法進(jìn)行操作,并且單擊禁用按鈕時(shí),待辦事項(xiàng)列表依然保持同樣的長度。
- it("should not be able to save an empty todo", () => {
- browser.get("/");
- let newTodoInput = element(by.css(".add-todo input[type=text]"));
- let newTodoSubmitButton = element(by.css(".add-todo input[type=submit]"));
- newTodoSubmitButton.click();
- let todos = element.all(by.css(".todos .todo"));
- expect(todos.count()).toEqual(3);
- })
保存按鈕在初始化時(shí)禁用
初始化時(shí)添加待辦事項(xiàng)按鈕被禁用,所以我們添加下列代碼
- it("should have add todo button be disabled initially", () => {
- browser.get("/");
- let newTodoSubmitButton = element(by.css(".add-todo input[type=submit]"));
- expect(newTodoSubmitButton.isEnabled()).toEqual(false);
- })
當(dāng)我們開始輸入時(shí)啟用保存按鈕
只用當(dāng)我們開始輸入待辦事項(xiàng)標(biāo)題時(shí),待辦事項(xiàng)保存按鈕才被啟用。
- it("should only enable save todo button when we start typing a new todo title", () => {
- browser.get("/");
- let newTodoSubmitButton = element(by.css(".add-todo input[type=submit]"));
- let newTodoInputField = element(by.css(".add-todo input[type=text]"));
- newTodoInputField.sendKeys("New Todo 4");
- expect(newTodoSubmitButton.isEnabled()).toEqual(true);
- })
結(jié)論
現(xiàn)在,我們來總結(jié)一下 Angular2 的“端對端”測試。即使您沒有任何的編程基礎(chǔ),也可以快速上手編寫“端對端”測試。對于那些被引入代碼庫而又可能存在漏洞的部分,“端對端”測試是一個(gè)高效便捷的方法來捕獲問題所在。
我們在概念部分中介紹了一些其他方法。您可以點(diǎn)擊這里來瀏覽這些 Protractor API。并且可以在 GitHub repository 上找到這個(gè)應(yīng)用的完整版和測試版。
我希望您看完這個(gè)介紹之后,在您修改任何一行代碼之前都能興奮的開始您的前端應(yīng)用測試。如果您高興,和我們交流一些您在日常測試中的高見?;蛘呤悄P(guān)于 Javascript 框架及 Angular2 的想法。感謝您的閱讀。