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);
});
}
在进行了这个实现之后,再次运行之前的测试代码,这一次测试结果会变为通过。
总结
如果以测试为先的方式进行,即使只是实现一些简单的功能,也会花费相当长的时间。
然而,在”相当长的时间”里,包括了规格的考虑、实现、单元测试、重构、减少返工成本等等,所以从成本效益的角度来说,实则是非常不错的。