使用 WebDriver 進行 Spring MVC 測試

工程 | Rob Winch | 2014 年 3 月 26 日 | ...

在我的第二篇文章中,我描述瞭如何使用 HtmlUnit 進行 Spring MVC 測試。在本文中,我們將利用 WebDriver 中的額外抽象來使事情變得更加容易。

為何選擇 WebDriver?

我們已經可以使用 HtmlUnit 和 MockMvc,那麼為何還要使用 WebDriver 呢?WebDriver 提供了非常優雅的 API,並允許我們輕鬆組織程式碼。為了更好地理解,我們來探索一個例子。


注意 儘管是 Selenium 的一部分,WebDriver 執行測試時不需要 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 模式。雖然我們當然可以使用 HtmlUnit 實現此模式,但 WebDriver 提供了一些工具,我們將在後續章節中探索這些工具,使此模式變得更加容易。

更新依賴

在使用專案之前,您必須確保更新您的依賴。關於 MavenGradle 的說明可以在網站文件中找到。

使用 WebDriver

現在我們有了正確的依賴,我們可以在單元測試中使用 WebDriver。我們的示例假設您已經將 JUnit 作為依賴。如果尚未新增,請相應地更新您的 classpath。使用 WebDriver 和 Spring MVC Test 的完整程式碼示例可以在 MockMvcHtmlUnitDriverCreateMessageTests 中找到。

建立 MockMvc

為了使用 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 要載入哪些配置。您會注意到我們載入了一個模擬的資料層例項,以提高測試效能。如果願意,我們可以選擇針對真實資料庫執行測試。但是,這會帶來我們之前提到的缺點
  • @WebAppConfigurationSpringJUnit4ClassRunner 表明它應該建立一個 WebApplicationContext 而不是 ApplicationContext

接下來,我們需要從 context 建立我們的 MockMvc 例項。下面提供瞭如何執行此操作的示例

@Before
public void setup() {
  MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
  ...  
}

當然,這只是建立 MockMvc 例項的一種方法。我們可以選擇新增 Servlet Filter,使用獨立設定等。重要的是我們需要一個 MockMvc 例項。有關建立 MockMvc 例項的更多資訊,請參閱 Spring MVC Test 文件

初始化 WebDriver

現在我們已經建立了 MockMvc 例項,我們需要建立一個 MockMvcHtmlUnitDriver,它確保我們使用上一步建立的 MockMvc 例項。

private WebDriver driver;

@Before
public void setup() {
	MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
	driver = new MockMvcHtmlUnitDriver(mockMvc, true);
}

使用 WebDriver

現在我們可以像往常一樣使用 WebDriver,而無需部署我們的應用程式。例如,我們可以使用以下方法請求建立訊息的檢視

CreateMessagePage messagePage = CreateMessagePage.to(driver);

然後我們可以填寫表單並提交以建立訊息。

ViewMessagePage viewMessagePage = 
    messagePage.createMessage(ViewMessagePage.class, expectedSummary, expectedText);

透過利用 Page Object 模式,這改進了我們的 HtmlUnit 測試的設計。正如我們在為何選擇 WebDriver?中提到的,我們可以使用 HtmlUnit 來使用 Page Object 模式,但現在使用 WebDriver 會容易得多。讓我們看看我們的 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://:9990/mail/messages/form");
        return PageFactory.initElements(driver, CreateMessagePage.class);
    }
}

您會注意到的第一件事是我們的 CreateMessagePage 擴充套件了 AbstractPage。我們不會詳細介紹 AbstractPage 的細節,但總而言之,它包含我們所有頁面的所有通用功能。例如,如果您的應用程式有導航欄、全域性錯誤訊息等,可以將此邏輯放在共享位置。

接下來您會發現我們對我們感興趣的 HTML 各部分,即 WebElement,都有一個成員變數。WebDriverPageFactory 允許我們透過自動解析每個 WebElement 來減少 HtmlUnit 版本 CreateMessagePage 中的大量程式碼。

PageFactory#initElements 方法將使用欄位名稱自動解析每個 WebElement,並嘗試透過 HTML 頁面上元素的 id 或 name 來查詢它。我們還可以使用 @FindBy 註解來覆蓋預設設定。我們的示例演示瞭如何使用 @FindBy 註解透過 css 選擇器 input[type=submit] 來查詢提交按鈕。

最後,我們可以驗證新訊息是否成功建立

assertThat(viewMessagePage.getMessage()).isEqualTo(expectedMessage);
assertThat(viewMessagePage.getSuccess()).isEqualTo("Successfully created a new message");

我們可以看到,我們的 ViewMessagePage 除了單獨的 Message 屬性外,還可以返回一個 Message 物件。這使我們可以輕鬆地與富領域物件互動,而不僅僅是 String。然後,我們可以在斷言中利用富領域物件。我們透過建立一個 自定義 fest 斷言來實現這一點,該斷言允許我們驗證實際 Message 的所有屬性是否等於預期的 Message。您可以在 AssertionsMessageAssert 中檢視自定義斷言的詳細資訊。

最後,完成後別忘了關閉 WebDriver 例項。

@After
public void destroy() {
	if(driver != null) {
		driver.close();
	}
}

有關使用 WebDriver 的更多資訊,請參閱 WebDriver 文件

一切都變得 Groovy...

WebDriver 擁有使用 HtmlUnit 的所有優點,並且額外增加了對 Page Object 模式的輕鬆支援。然而,仍然存在相當多的樣板程式碼可以改進。在我們的下一篇文章中,我們將看到如何使用 Geb 讓我們的測試更加 Groovy。


請提供反饋!

如果您對本部落格系列或 Spring Test MVC HtmlUnit 有任何反饋,歡迎透過 GitHub Issues 聯絡我,或在 Twitter 上 @我 @rob_winch。當然,最好的反饋是貢獻

訂閱 Spring 新聞通訊

訂閱 Spring 新聞通訊,保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,僅需一份簡單訂閱。

瞭解更多

近期活動

檢視 Spring 社群的所有近期活動。

檢視全部