取得領先
VMware 提供培訓和認證,以加速您的進展。
了解更多在我的第二篇文章中,我描述了如何使用 Spring MVC Test 搭配 HtmlUnit。在這篇文章中,我們將利用 WebDriver 中的其他抽象概念,使事情變得更加容易。
我們已經可以使用 HtmlUnit 和 MockMvc,那麼為什麼還要使用 WebDriver 呢? WebDriver 提供了一個非常優雅的 API,並且可以讓我們輕鬆地組織程式碼。為了更好地理解,讓我們探索一個範例。
注意 即使 WebDriver 是 Selenium 的一部分,它也不需要 Selenium Server 才能執行您的測試。
假設我們需要確保正確地建立訊息。測試包括找到 HTML 輸入欄位,填寫它們,並進行各種斷言。
有很多測試,因為我們也想測試錯誤情況。例如,我們想確保如果我們只填寫部分表單,我們會收到錯誤訊息。如果我們填寫了整個表單,則隨後會顯示新建立的訊息。
如果其中一個欄位名為 "summary",那麼我們可能在測試中到處重複以下內容
HtmlTextInput summaryInput = createMsgFormPage.getHtmlElementById("summary");
summaryInput.setValueAttribute("Spring Rocks");
如果我們將 ID 更改為 "smmry" 會發生什麼事? 這表示我們必須更新我們所有的測試! 相反地,我們希望我們編寫了更優雅的程式碼,將填寫表單的動作放在自己的方法中
public HtmlPage createMessage(HtmlPage currentPage, String summary, String text) {
...
setSummary(currentPage, summary);
...
}
public void setSummary(HtmlPage currentPage, String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
這確保如果我們更改 UI,我們不必更新我們所有的測試。
我們可以更進一步,將此邏輯放在一個代表我們目前所在的 HtmlPage
的物件中。
public class CreateMessagePage {
private final HtmlPage currentPage;
...
public T createMessage(Class<T> resultPage, String summary, String text) {
...
setSummary(currentPage, summary);
...
HtmlPage result = submit.click();
...
return (T) error ? new CreateMessagePage(result) : new ViewMessagePage(result);
}
public void setSummary(String summary) {
HtmlTextInput summaryInput = currentPage.getHtmlElementById("summary");
summaryInput.setValueAttribute(summary);
}
}
先前,此模式被稱為 Page Object Pattern。雖然我們當然可以用 HtmlUnit 做到這一點,但 WebDriver 提供了一些工具,我們將在以下各節中探討,使這種模式更容易。
在使用專案之前,您必須確保更新您的相依性。有關 Maven 和 Gradle 的說明可以在網站文件中找到。
現在我們有了正確的相依性,我們可以在單元測試中使用 WebDriver。 我們的範例假設您已經將 JUnit 作為相依性。 如果您尚未新增它,請相應地更新您的類別路徑。 使用 WebDriver 和 Spring MVC Test 的完整程式碼範例可以在 MockMvcHtmlUnitDriverCreateMessageTests 中找到。
為了使用 WebDriver 和 Spring MVC Test,我們首先必須建立一個 MockMvc
實例。 關於如何建立 MockMvc
實例有很多文檔,但我們將在本節中快速回顧如何建立 MockMvc
實例。
第一步是建立一個新的 JUnit 類別,並使用如下所示的註解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebMvcConfig.class, MockDataConfig.class})
@WebAppConfiguration
public class MockMvcHtmlUnitDriverCreateMessageTests {
@Autowired
private WebApplicationContext context;
...
}
@RunWith(SpringJUnit4ClassRunner.class)
允許 Spring 在我們的 MockMvcHtmlUnitDriverCreateMessageTests
上執行依賴注入。 這就是為什麼我們的 @Autowired
註解將會被採用。@ContextConfiguration
告訴 Spring 要載入什麼配置。 您會注意到我們正在載入資料層的模擬實例,以提高測試的效能。 如果我們願意,我們可以選擇針對真實的資料庫執行測試。 但是,這有我們之前提到的缺點。@WebAppConfiguration
指示 SpringJUnit4ClassRunner
應該建立一個 WebApplicationContext
,而不是 ApplicationContext
。接下來,我們需要從 context
建立我們的 MockMvc
實例。 下面提供了一個如何執行此操作的範例
@Before
public void setup() {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
...
}
當然,這只是建立 MockMvc
實例的一種方法。 我們可以決定新增一個 Servlet Filter,使用 Standalone 設定等等。 重要的事情是我們需要一個 MockMvc
的實例。 關於建立 MockMvc
實例的更多資訊,請參閱 Spring MVC Test 文件。
現在我們已經建立了 MockMvc
實例,我們需要建立一個 MockMvcHtmlUnitDriver
,以確保我們使用在上一步中建立的 MockMvc
實例。
private WebDriver driver;
@Before
public void setup() {
MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
driver = new MockMvcHtmlUnitDriver(mockMvc, true);
}
現在我們可以像平常一樣使用 WebDriver,而無需部署我們的應用程式。 例如,我們可以使用以下方式請求檢視以建立訊息
CreateMessagePage messagePage = CreateMessagePage.to(driver);
然後我們可以填寫表單並提交以建立訊息。
ViewMessagePage viewMessagePage =
messagePage.createMessage(ViewMessagePage.class, expectedSummary, expectedText);
這透過利用 Page Object Pattern 改進了我們的 HtmlUnit 測試的設計。 正如我們在為何選擇 WebDriver? 中提到的那樣,我們可以使用 Page Object Pattern 搭配 HtmlUnit,但現在更容易了。 讓我們看看我們的 CreateMessagePage
。
public class CreateMessagePage extends AbstractPage {
private WebElement summary;
private WebElement text;
@FindBy(css = "input[type=submit]")
private WebElement submit;
public CreateMessagePage(WebDriver driver) {
super(driver);
}
public <T> T createMessage(Class<T> resultPage, String summary, String details) {
this.summary.sendKeys(summary);
this.text.sendKeys(details);
this.submit.click();
return PageFactory.initElements(driver, resultPage);
}
public static CreateMessagePage to(WebDriver driver) {
driver.get("https://127.0.0.1:9990/mail/messages/form");
return PageFactory.initElements(driver, CreateMessagePage.class);
}
}
您首先會注意到我們的 CreateMessagePage
擴展了 AbstractPage
。 我們不會討論 AbstractPage
的細節,但總而言之,它包含我們所有頁面的所有通用功能。 例如,如果您的應用程式有一個導覽列、全域錯誤訊息等等。 此邏輯可以放在共享位置。
接下來您會發現,我們對 HTML 的每個部分都有一個成員變數 WebElement
,我們對其感興趣。 WebDriver
的 PageFactory 允許我們透過自動解析每個 WebElement
,從 HtmlUnit 版本的 CreateMessagePage
中移除大量程式碼。
PageFactory#initElements
方法將自動解析每個 WebElement
,方法是使用欄位名稱並嘗試依 HTML 頁面上元素的 id 或名稱來查詢它。 我們也可以使用 @FindBy 註解 來覆寫預設值。 我們的範例示範了如何使用 @FindBy
註解,使用 input[type=submit] 的 CSS 選擇器來查詢我們的提交按鈕。
最後,我們可以驗證是否已成功建立新訊息
assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");
我們可以發現,我們的 ViewMessagePage
除了個別的 Message
屬性之外,還可以回傳一個 Message
物件。這讓我們可以輕鬆地與豐富的領域物件互動,而不僅僅是一個 String
。然後,我們可以在斷言中利用這些豐富的領域物件。我們透過建立一個 自定義 fest 斷言 來驗證實際的 Message
的所有屬性是否等於預期的 Message
。您可以在 Assertions 和 MessageAssert 中查看自定義斷言的詳細資訊。
最後,別忘了在我們完成後關閉 WebDriver
實例。
@After
public void destroy() {
if(driver != null) {
driver.close();
}
}
有關使用 WebDriver 的更多資訊,請參閱 WebDriver 文件。
WebDriver
具有與使用 HtmlUnit 相同的優點,並且更容易支援使用 Page Object 模式。但是,有相當多的樣板程式碼可以改進。在我們的下一篇文章中,我們將看到如何使用 Geb 來使我們的測試更加 Groovy。
請提供回饋意見!
如果您對本部落格系列或 Spring Test MVC HtmlUnit 有任何回饋意見,我鼓勵您透過 github issues 聯絡,或在 Twitter 上 ping 我 @rob_winch。當然,最好的回饋是 貢獻。