玩游戏
在本节中,我们将测试驱动用于玩游戏的代码。这涉及向端点提交多次猜测尝试,直到收到游戏结束的响应。
我们首先为端点中的新 /guess
路由创建一个集成测试:
-
第一步是编写 Arrange 步骤。我们的领域模型提供了
Wordz
类中的assess()
方法,用于评估猜测的分数,并报告游戏是否结束。为了进行测试驱动开发,我们设置了mockWordz
存根,以便在调用assess()
方法时返回一个有效的GuessResult
对象。@Test void partiallyCorrectGuess() { var score = new Score("-U---"); score.assess("GUESS"); var result = new GuessResult(score, false, false); when(mockWordz.assess(eq(PLAYER), eq("GUESS"))) .thenReturn(result); }
-
Act 步骤将通过 web 请求提交猜测来调用我们的端点。我们的设计决策是向
/guess
路由发送一个 HTTP POST 请求,请求体将包含猜测的单词的 JSON 表示。为了创建这个请求,我们将使用GuessRequest
记录,并使用 Gson 将其转换为 JSON。@Test void partiallyCorrectGuess() { var score = new Score("-U---"); score.assess("GUESS"); var result = new GuessResult(score, false, false); when(mockWordz.assess(eq(PLAYER), eq("GUESS"))) .thenReturn(result); var guessRequest = new GuessRequest(PLAYER, "-U---"); var body = new Gson().toJson(guessRequest); var req = requestBuilder("guess") .POST(ofString(body)) .build(); }
-
接下来,我们定义记录:
package com.wordz.adapters.api; import com.wordz.domain.Player; public record GuessRequest(Player player, String guess) { }
-
然后,我们通过 HTTP 将请求发送到我们的端点,等待响应:
@Test void partiallyCorrectGuess() throws Exception { var score = new Score("-U---"); score.assess("GUESS"); var result = new GuessResult(score, false, false); when(mockWordz.assess(eq(PLAYER), eq("GUESS"))) .thenReturn(result); var guessRequest = new GuessRequest(PLAYER, "-U---"); var body = new Gson().toJson(guessRequest); var req = requestBuilder("guess") .POST(ofString(body)) .build(); var res = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); }
-
然后,我们提取返回的主体数据并将其与我们的预期进行断言:
@Test void partiallyCorrectGuess() throws Exception { var score = new Score("-U--G"); score.assess("GUESS"); var result = new GuessResult(score, false, false); when(mockWordz.assess(eq(PLAYER), eq("GUESS"))) .thenReturn(result); var guessRequest = new GuessRequest(PLAYER, "-U--G"); var body = new Gson().toJson(guessRequest); var req = requestBuilder("guess") .POST(ofString(body)) .build(); var res = httpClient.send(req, HttpResponse.BodyHandlers.ofString()); var response = new Gson().fromJson(res.body(), GuessHttpResponse.class); // Key to letters in scores(): // C correct, P part correct, X incorrect Assertions.assertThat(response.scores()) .isEqualTo("PCXXX"); Assertions.assertThat(response.isGameOver()) .isFalse(); }
这里的一个 API 设计决策是将每个字母的得分作为一个五字符的
String
对象返回。单个字母X
、C
和P
用于表示不正确、正确和部分正确的字母。我们在断言中捕获了这一决策。 -
我们定义一个记录来表示我们将从端点返回的 JSON 数据结构:
package com.wordz.adapters.api; public record GuessHttpResponse(String scores, boolean isGameOver) { }
-
由于我们决定向新的
/guess
路由发送 POST 请求,因此我们需要将此路由添加到路由表中。我们还需要将其绑定到一个将执行操作的方法,即我们将其命名为guessWord()
。public WordzEndpoint(Wordz wordz, String host, int port) { this.wordz = wordz; server = WebServer.create(host, port); try { server.route(new Routes() {{ post("/start") .to(request -> startGame(request)); post("/guess") .to(request -> guessWord(request)); }}); } catch (IOException e) { throw new IllegalStateException(e); } }
我们添加
IllegalStateException
以重新抛出启动 HTTP 服务器时发生的任何问题。对于此应用程序,此异常可能会向上传播并导致应用程序停止运行。如果没有正常运行的 Web 服务器,任何 Web 代码都没有意义。 -
实现`guessWord()`方法 我们实现`guessWord()`方法,从POST请求体中提取请求数据:
```java private Response guessWord(Request request) { try { GuessRequest gr = extractGuessRequest(request); return null; } catch (IOException e) { throw new RuntimeException(e); } }
private GuessRequest extractGuessRequest(Request request) throws IOException { return new Gson().fromJson(request.body(), GuessRequest.class); } ```
-
现在我们有了请求数据,是时候调用我们的领域层来执行实际操作了。我们将捕获返回的
GuessResult
对象,以便我们可以基于它来构建来自端点的 HTTP 响应。private Response guessWord(Request request) { try { GuessRequest gr = extractGuessRequest(request); GuessResult result = wordz.assess(gr.player(), gr.guess()); return Response.ok() .body(createGuessHttpResponse(result)) .done(); } catch (IOException e) { throw new RuntimeException(e); } } private String createGuessHttpResponse(GuessResult result) { GuessHttpResponse httpResponse = new GuessHttpResponseMapper().from(result); return new Gson().toJson(httpResponse); }
-
添加`GuessHttpResponseMapper`类 我们添加一个空的`GuessHttpResponseMapper`类来完成转换:
```java package com.wordz.adapters.api;
import com.wordz.domain.GuessResult;
public class GuessHttpResponseMapper { public GuessHttpResponse from(GuessResult result) { return null; } } ```
-
我们添加了一个空的对象来进行转换,即
GuessHttpResponseMapper
类。在这第一步中,它将简单地返回null
。package com.wordz.adapters.api; import com.wordz.domain.GuessResult; public class GuessHttpResponseMapper { public GuessHttpResponse from(GuessResult result) { return null; } }
-
这足以编译并能够运行
WordzEndpointTest
测试。Figure 1. Figure 15.8 – The test fails -
有了失败的测试之后,我们现在可以测试驱动转换类的细节。为此,我们切换到添加一个新的单元测试,名为
GuessHttpResponseMapperTest
。这些细节省略了,但可以在 GitHub 上找到——它遵循本书中使用的标准方法。
-
一旦我们通过测试驱动了
GuessHttpResponseMapper
类的详细实现,就可以重新运行集成测试。Figure 2. Figure 15.9 – The endpoint test passes
正如我们在前面的图片中看到的,集成测试已经通过了!是时候享受一下美好的咖啡休息时间了。嗯,我喜欢喝一杯地道的英式早餐茶,不过那只是我个人的偏好。休息过后,我们可以开始测试驱动错误发生时的响应。接着就是将微服务集成在一起。下一节,我们将把我们的应用程序组合成一个运行中的微服务。