文件

§測試網路服務客戶端

撰寫網路服務客戶端可能會使用許多程式碼,例如準備要求、序列化和反序列化主體、設定正確的標頭。由於許多程式碼會與字串和弱型別映射搭配使用,因此測試非常重要。然而,測試也帶來了一些挑戰。一些常見的方法包括

§針對實際網路服務進行測試

這當然能讓客戶端程式碼達到最高的信心水準,但通常不切實際。如果是第三方網路服務,可能會實施速率限制,以防止測試執行(針對第三方服務執行自動化測試並非被視為良好的網路公民)。可能無法設定或確保測試在該服務上所需的必要資料,而且測試可能會對服務造成不良的副作用。

§針對網路服務的測試執行個體進行測試

這比前一個方法好一點,但仍然有許多問題。許多第三方網路服務不提供測試執行個體。這也表示測試依賴於測試執行個體的執行,這表示測試服務可能會導致建置失敗。如果測試執行個體位於防火牆之後,也會限制測試可以從何處執行。

§模擬 http 客戶端

此方法對測試程式碼的信心水準最低,這種測試通常只會測試程式碼執行其功能,這沒有任何價值。針對模擬網路服務客戶端進行的測試顯示程式碼會執行並執行特定動作,但無法確定程式碼執行的任何動作是否實際對應於發出的有效 HTTP 要求。

§模擬網路服務

此方法在針對實際網路服務進行測試和模擬 http 伺服器之間取得良好的折衷。測試會顯示它所發出的所有要求都是有效的 HTTP 要求,主體的序列化/反序列化運作正常等,但它們會完全獨立,不依賴任何第三方服務。

Play 提供一些輔助工具程式,用於在測試中模擬網路服務,使這種測試方法成為非常可行且有吸引力的選項。

§測試 GitHub 伺服器

舉例來說,假設您撰寫了一個 GitHub 伺服器,而且您想要測試它。伺服器非常簡單,它只允許您查詢公開儲存庫的名稱

import com.fasterxml.jackson.databind.JsonNode;
import java.util.*;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
import javax.inject.Inject;
import play.libs.ws.WSClient;

class GitHubClient {
  private WSClient ws;

  @Inject
  public GitHubClient(WSClient ws) {
    this.ws = ws;
  }

  String baseUrl = "https://api.github.com";

  public CompletionStage<List<String>> getRepositories() {
    return ws.url(baseUrl + "/repositories")
        .get()
        .thenApply(
            response ->
                response.asJson().findValues("full_name").stream()
                    .map(JsonNode::asText)
                    .collect(Collectors.toList()));
  }
}

請注意,它將 GitHub API 基本 URL 作為參數 - 我們會在測試中覆寫它,以便讓它指向我們的模擬伺服器。

若要測試它,我們需要一個嵌入式 Play 伺服器,它會實作這個端點。我們可以透過使用 路由 DSL建立一個嵌入式伺服器 來執行此操作

Server server =
    Server.forRouter(
        (components) ->
            RoutingDsl.fromComponents(components)
                .GET("/repositories")
                .routingTo(
                    request -> {
                      ArrayNode repos = Json.newArray();
                      ObjectNode repo = Json.newObject();
                      repo.put("full_name", "octocat/Hello-World");
                      repos.add(repo);
                      return ok(repos);
                    })
                .build());

我們的伺服器現在執行於隨機埠,我們可透過 httpPort 方法存取。我們可以使用此方法建立要傳遞至 GitHubClient 的基本 URL,不過 Play 有更簡單的機制。WSTestClient 類別提供一個 newClient 方法,其中包含埠號。當使用客戶端對應於相對 URL(例如 /repositories)發出要求時,此客戶端會將該要求傳送至傳入埠的本機主機。這表示我們可以將 GitHubClient 上的基本 URL 設為 ""。這也表示如果客戶端傳回含有其他資源的 URL 連結的資源,而客戶端接著使用這些連結發出更多要求,我們可以確保這些是相對 URL,並照原樣使用它們。

因此,我們現在可以在附註有 @Before 的方法中建立伺服器、WS 客戶端和 GitHubClient,並在附註有 @After 的方法中關閉它們,然後我們可以在測試中測試客戶端

import static org.hamcrest.core.IsCollectionContaining.*;
import static org.junit.Assert.*;
import static play.mvc.Results.*;

import com.fasterxml.jackson.databind.node.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
import org.junit.*;
import play.libs.Json;
import play.libs.ws.*;
import play.routing.RoutingDsl;
import play.server.Server;

public class GitHubClientTest {
  private GitHubClient client;
  private WSClient ws;
  private Server server;

  @Before
  public void setup() {
    server =
        Server.forRouter(
            (components) ->
                RoutingDsl.fromComponents(components)
                    .GET("/repositories")
                    .routingTo(
                        request -> {
                          ArrayNode repos = Json.newArray();
                          ObjectNode repo = Json.newObject();
                          repo.put("full_name", "octocat/Hello-World");
                          repos.add(repo);
                          return ok(repos);
                        })
                    .build());
    ws = play.test.WSTestClient.newClient(server.httpPort());
    client = new GitHubClient(ws);
    client.baseUrl = "";
  }

  @After
  public void tearDown() throws IOException {
    try {
      ws.close();
    } finally {
      server.stop();
    }
  }

  @Test
  public void repositories() throws Exception {
    List<String> repos = client.getRepositories().toCompletableFuture().get(10, TimeUnit.SECONDS);
    assertThat(repos, hasItem("octocat/Hello-World"));
  }
}

§傳回檔案

在先前的範例中,我們手動為模擬服務建立 json。通常,從您測試的服務擷取實際回應並傳回會更好。為了協助處理這項工作,Play 提供一個 sendResource 方法,可輕鬆建立來自類別路徑上檔案的結果。

因此,在對實際 GitHub API 提出請求後,建立一個檔案將其儲存在測試資源目錄中。測試資源目錄可能是 test/resources(如果您使用 Play 目錄配置)或 src/test/resources(如果您使用標準 sbt 目錄配置)。在此情況下,我們將其命名為 github/repositories.json,其中將包含下列內容

[
  {
    "id": 1296269,
    "owner": {
      "login": "octocat",
      "id": 1,
      "avatar_url": "https://github.com/images/error/octocat_happy.gif",
      "gravatar_id": "",
      "url": "https://api.github.com/users/octocat",
      "html_url": "https://github.com/octocat",
      "followers_url": "https://api.github.com/users/octocat/followers",
      "following_url": "https://api.github.com/users/octocat/following{/other_user}",
      "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
      "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
      "subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
      "organizations_url": "https://api.github.com/users/octocat/orgs",
      "repos_url": "https://api.github.com/users/octocat/repos",
      "events_url": "https://api.github.com/users/octocat/events{/privacy}",
      "received_events_url": "https://api.github.com/users/octocat/received_events",
      "type": "User",
      "site_admin": false
    },
    "name": "Hello-World",
    "full_name": "octocat/Hello-World",
    "description": "This your first repo!",
    "private": false,
    "fork": false,
    "url": "https://api.github.com/repos/octocat/Hello-World",
    "html_url": "https://github.com/octocat/Hello-World"
  }
]

您可以決定修改它以符合您的測試需求,例如,如果您的 GitHub 應用程式使用上述回應中的 URL 對其他端點提出請求,您可以移除 https://api.github.com 前置詞,讓它們也成為相對的,並由測試應用程式在正確的埠上自動路由到 localhost。

現在,修改路由器以提供此資源

Server server =
    Server.forRouter(
        (components) ->
            RoutingDsl.fromComponents(components)
                .GET("/repositories")
                .routingTo(request -> ok().sendResource("github/repositories.json"))
                .build());

請注意,Play 會自動設定內容類型為 application/json,因為檔案名稱的副檔名為 .json

下一步:記錄


在此文件檔中發現錯誤?此頁面的原始程式碼可以在 這裡 找到。閱讀 文件檔指南 後,請隨時貢獻一個 pull 請求。有問題或建議要分享嗎?請前往 我們的社群論壇 與社群展開對話。