Spring MVC 測試搭配 WebDriver

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

在我的第二篇文章中,我描述了如何使用 Spring MVC Test 搭配 HtmlUnit。在這篇文章中,我們將利用 WebDriver 中的其他抽象概念,使事情變得更加容易。

為何選擇 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 提供了一些工具,我們將在以下各節中探討,使這種模式更容易。

更新相依性

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

使用 WebDriver

現在我們有了正確的相依性,我們可以在單元測試中使用 WebDriver。 我們的範例假設您已經將 JUnit 作為相依性。 如果您尚未新增它,請相應地更新您的類別路徑。 使用 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 要載入什麼配置。 您會注意到我們正在載入資料層的模擬實例,以提高測試的效能。 如果我們願意,我們可以選擇針對真實的資料庫執行測試。 但是,這有我們之前提到的缺點。
  • @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 文件

初始化 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 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,我們對其感興趣。 WebDriverPageFactory 允許我們透過自動解析每個 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。您可以在 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 上 ping 我 @rob_winch。當然,最好的回饋是 貢獻

獲取 Spring 電子報

與 Spring 電子報保持聯繫

訂閱

取得領先

VMware 提供培訓和認證,以加速您的進展。

了解更多

取得支援

Tanzu Spring 在一個簡單的訂閱中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位檔。

了解更多

即將舉行的活動

查看 Spring 社群中所有即將舉行的活動。

查看全部