GAE/Java8试验(第4部分:访问Datastore的逻辑)

题目

在上次,我们提到了使用Maven自动生成的Java 8应用程序中的测试代码。
这次,我们将尝试根据测试优先的原则来添加Datastore访问逻辑。

GAE实验索引

    • GAE/Java8試行(その0:「App Engineについて」)

 

    • GAE/Java8試行(その1:「Java8でWebアプリ作ってデプロイ」)

 

    • GAE/Java8試行(その2:「Javaアプリ解説」)

 

    GAE/Java8試行(その3:「Javaアプリテストコード解説」)

开发环境

操作系统

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="17.10 (Artful Aardvark)"

# Java – Java语言

$ java -version
java version "1.8.0_181"
Java(TM) SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)

可以说IDE是一个集成开发环境,用于编写、调试和运行软件应用程序的工具。

大家都喜欢 IntelliJ IDEA。

请以中文为母语将以下内容改写为同义句,只需提供一个选项:

参照

    • データストアと Memcache のテストを作成する

 

    • Cloud Datastore の概要

 

    エンティティ プロパティ リファレンス

实践

■ 设计 (shè jì)

これから実装する機能の仕様を決める。
テーマは、GCPのチュートリアルでもよくある「書籍リストの管理」システムとする。
今回Datastoreへのアクセスロジックを試す機能としては、
「書籍名」をPOSTしたらDatastoreに登録されるものとする。

■测试代码

仕様に合わせて、下記のようなテストコードを書いてみる。
モックリクエストを操作し、「書籍名」がリクエストパラメータから取得できるようにする。
そして、 doPost(~~) 実行で上記の「書籍名」が book カインドに登録されていることを検証する。

  @Test
  public void 書籍名をPOSTするとDatastoreに登録される() throws EntityNotFoundException, ServletException, IOException {
    /*
     * SetUp
     */
    Map<String, String[]> parameterMap = new HashMap<>();
    parameterMap.put("bookName", new String[]{"マイクロサービスアーキテクチャ"});
    when(mockRequest.getParameterMap()).thenReturn(parameterMap);

    /*
     * Execute
     */
    servletUnderTest.doPost(mockRequest, mockResponse);

    /*
     * Assert
     */
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();
    Entity e = ds.get(KeyFactory.createKey("book", 1));
    String microService = (String)e.getProperty("bookName");
    assertThat(microService).isEqualTo("マイクロサービスアーキテクチャ");
  }

ちなみに、テストファーストなので doPost(~~) のない状態では上記テストコードはコンパイルエラー。
その後、 doPost(~~) の定義だけ書いて、テストコードを実行すると、下記のようになる。
リクエストパラメータとして渡した「書籍名」をDatastoreに登録するロジックは未実装なので、当然の結果。
テストファーストでは、まず、仕様を決めて、それを確認するテストコードを書く。
(その時点ではコンパイルも通らないが、そこから始めることが重要)
最初にテストに失敗させる。
失敗することがわかっているのに、あえて失敗させる。ロジック実装前なので、テスト実行結果がNGとなる。
これを最初に確認することで、ロジック実装後にテスト実行結果がOKとなることに意味が出る。
(最初に失敗することを確認しないと、正しい検証コードが書けていなくて、そもそも最初からテスト実行結果がOKだったかもしれず、正しく実装できていることをテストできていないテストコードになる可能性があるため)


com.google.appengine.api.datastore.EntityNotFoundException: No entity was found matching the key: book("micro")

    at com.google.appengine.api.datastore.BaseAsyncDatastoreServiceImpl$1.wrap(BaseAsyncDatastoreServiceImpl.java:174)
    at com.google.appengine.api.datastore.BaseAsyncDatastoreServiceImpl$1.wrap(BaseAsyncDatastoreServiceImpl.java:169)
    at com.google.appengine.api.utils.FutureWrapper.wrapAndCache(FutureWrapper.java:56)
    at com.google.appengine.api.utils.FutureWrapper.get(FutureWrapper.java:93)
    at com.google.appengine.api.datastore.FutureHelper.getInternal(FutureHelper.java:76)
    at com.google.appengine.api.datastore.FutureHelper.quietGet(FutureHelper.java:63)
    at com.google.appengine.api.datastore.DatastoreServiceImpl.get(DatastoreServiceImpl.java:41)
    at com.example.sky0621.HelloAppEngineTest.書籍名をPOSTするとDatastoreに登録される(HelloAppEngineTest.java:100)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:27)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

10 23, 2018 12:21:13 午前 com.google.appengine.api.datastore.dev.LocalDatastoreService cleanupActiveServices
情報: scheduler shutting down.
Disconnected from the target VM, address: '127.0.0.1:37713', transport: 'socket'

Process finished with exit code 255

既然测试如预期般失败,接下来就开始实施Datastore注册逻辑。

■实施

大概就是这样。
这段代码虽然还不能用于产品级别,但暂时仅仅使用这段代码就能从请求参数中捕获”书名”并将其注册到Datastore中。


  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

    req.getParameterMap().forEach((k, v) -> {
      Key key = KeyFactory.createKey("book", 1);
      Entity e = new Entity(key);
      e.setProperty("bookName", Arrays.stream(v).collect(Collectors.joining()));
      ds.put(e);
    });
  }

在进行了这个实现之后,再次运行之前的测试代码,这一次测试结果会变为通过。

总结

如果以测试为先的方式进行,即使只是实现一些简单的功能,也会花费相当长的时间。
然而,在”相当长的时间”里,包括了规格的考虑、实现、单元测试、重构、减少返工成本等等,所以从成本效益的角度来说,实则是非常不错的。

广告
将在 10 秒后关闭
bannerAds