- 对于同步方法的测试很简单,调用完后可立马检查执行状态; 而异步方法,由于我们无法确切的知道何时结束,因此以往的办法是用
Thread.sleep(500)来预估一个执行时间。然后通常我们估计的要长于实际的时间,这就很浪费,况且偶然的超过预估的等待时间也并不意味着代码有问题。还有sleep方法还抛出一个检测异常InterruptedException, 一般会要对Thread.sleep(500)作下简单包装。
于是今天要介绍的 Awaitility 就应运而生了,专门针对异步方法的测试。它的官方文档在 https://github.com/awaitility/awaitility/wiki/Usage。本文主要关注在 Java 8 环境下用 Lambda 的代码书写方式。Awaitlity 实际运行是以某种轮询的方式来检查是否达到某个运行状态,可设定最多,最少等待时间,或永久等待,或自定义轮询策略,之后就开始进行需要的断言,所以它可以尽可能的节省测试异步方法所需的时长。而不像Thread.sleep(500)一路等到黑,并且没有回头路。
通常我会在项目中给 JUnit 配上三个最佳伴侣,它们是(按mvn dependency:tree中的显示方式):- org.awaitility:awaitility:2.0.0:test
- org.assertj:assertj-core: version: 3.8.0:test
- org.mockito:mockito-core:2.7.22:test
当然如果项目中没有异步调用自然是不需要 Awaitility, 在我的项目中是基本不可能的。以上三种都追求 DSL,以流畅的方式进行愉快的测试。
现在来尝试下 Awaitility 的几种基本的用法,先假定有下面的代码UserServiceRead More - 实际中可能有这样的应用场景,得到一个记录不需要立即去处理它,而是等累积到一定数量时再批量处理它们。我们可以用一个计数器,来一个加一个,量大时一块处理,然后又重零开始计数。如果记录的来源单一还好办,要是有多个数据源来提供记录就会有多线程环境下数据丢失的问题。
这里我编写了一个最简单的任务批处理的队列,构造了告诉它批处理数量,消费者,然后就只管往队列里添加记录,队列在满足条件时自动进行批处理。因为内部使用的是BlockingQuque来存储记录,所以多线程往里同时添加记录也没关系,最后的未达到batchSize, 的那些记录可主动调用completeAll()函数或在达到 timeout 后来触发批处理,并且结束队列内的循环线程。
注意: 多线程环境下往一个无线程保护的集合或结构中,如 ArrayList, LinkedList, HashMap, StringBuilder 中添加记录非常容易造成数据的丢失,而往有线程保护的目的地写东西就安全了,如 Vector, Hashtable, StringBuffer, BlockingQueue。当然性能上要付出一点代价,不过对于使用了可重入锁(ReentrantLock), 而非同步锁(synchronized) 的数据结构还是可以放心使用的。
下面是 BatchQueue 的简单实现 Read More
继续对 CompletableFuture 的学习,本然依然不对它的众多方法的介绍,其实也不容易通过一篇述说完所有 CompletableFuture 的操作。此处重点了解下 CompletableFuture 几类操作时所使用的线程,CompletableFuture 的方法重点在它的静态方法以及实现自 CompletionStage 接口的方法,如果是意图异步化编程,反而自我标榜的 Future 中的方法用的少了。
CompletableFuture 根据任务的主从关系为- 提交任务的方法,如静态方法 supplyAsync(supplier[, executor]), runAsync(runnable[, executor])
- 回调函数,即对任务执行后所作出回应的方法,多数方法了,如 thenRun(action), thenRunAsync(action[, executor]), whenComplete(action), whenCompleteAsync(action[, executor]) 等
根据执行方法可分为同步与异步方法,任务都是要被异步执行,所以提交任务的方法都是异步的。而对任务作出回应的方法很多分为两个版本,如- 同步方法,如 thenRun(action), whenComplete(action)
- 异步方法,如 thenRunAsync(action[, executor]), whenCompleteAsync(action[, executor]), 异步方法可以传入线程池,否则用默认的
因此所要理解的 CompletableFuture 的线程会涉及到任务与回调函数所使用的线程。 Read More- 似乎 C/C++ 的编程人员相比于 Java 更偏爱于断言,JDK 1.4 才开始引入 assert 的支持,但默认是关闭的,需要用
-ea编译选项打开,否则代码中的assert语句全被忽略,一般会在单元测试中开启该选项。简单回顾一下 JDK 自带的断言,它用两种写法assert object != null;
第一个参数是个 bool 值,断言失败只会笼统的抛出
assert object != null : "object can't be null";java.lang.AssertionError异常,并不区分是在检验方法参数还是中间运算结果。严谨来说我们会希望参数检查不通过时抛出java.lang.IllegalArgumentException; 而中间运算结果的断言不过希望抛出java.lang.AssertionError, 最好是java.lang.IllegalStateException。
很多时候我们不会去使用-ea编译选项,也就是主动放弃了 JDK 本身的断言功能。介于两个因素(不同的断言错误和默认的断言选项关闭),Scala 为我们提供了更方便的参数检查与断言方法,它们来自于 Predef, 其所定义的方法可以直接使用
Read More - 当我们调用 Hibernate 的 saveOrUpdate() 或 JPA 的 save() 方法的 Hibernate 实现时,都会做两步操作:1)按 ID 查询记录是否已存在,2)不存在插入新记录,存在则更新原记录。这种两步操作其实可以在 SQL Server 和 HSQLDB 中一条语句完成,这就是本文要介绍的
merge into语句。感觉到用数据库自己的特性,并且一条语句会比saveOrUpdate()两步操作性能要好,还需实测。
之所以把 SQL Server 和 HSQLDB 扯到一块来讲,是因为我们在实际项目中的单元测试是基于 HSQLDB 内存数据库的。merge into如其名所示,它应该是给予我们便利的去根据把一个表中符合条件的记录合并到另一个表中去。我们这里只利用它的这特性去实现类似 Hibernate 的saveOrUpdate()操作。
假设我们有一个简单的表1CREATE TABLE user ( 2 id INT, 3 name VARCHAR(32), 4 address VARCHAR(128) 5);
如果指 id 的记录已存在更新原来记录的 name 和 address, 不存在则插入新记录 Read More
我们在使用 JDBC 时, 如果把所有的 SQL 语句全写在 Java 文件中, 由于 Java 不支持 Here Document, 多行字符串要么用加号, 要么用 Java 8 的String.join()方法来连接, 同时不能对 SQL 语句进行语法加亮, 所以这样的 SQL 字符串阅读性很差. 别说为何不用 Hibernate 之类的而不直接写原始的 SQL 语句, 在操作复杂的系统时还是会用到 JdbcTemplate 吧.
所以我们希望能把 SQL 语句写在单独的*.sql文件里, 这样很多编辑器就能语法高亮显示, 或在输入时还能得到智能提示. 有种办法是把*.sql用作为属性文件, 那么在其中定义多行的 SQL 语句时就得这样select.user=select id, firstname, lastname, address \
加载后就能用
from users \
where id=?getProperty("select.user")来引用相应的语句了. 属性文件的换行与 Bash 一样, 也是用\, 但如此, 则*.sql并非一个纯粹的 SQL 文件, 不能正确的进行语法加亮, 一旦写上 SQL 的注释--就更是在添乱了.
所以我们的第二个方案是: 首先*.sql就该是一个真正的 SQL 文件, 而不是伪装的属性文件, 为了能在程序中引用每一条 SQL 语句, 我们该如何表示各自的 Key 呢? 这里的灵感仍然是来自于 Linux Shell, 在 Linux Shell 中指定执行环境的用了特殊的注释方式#!, 如#!/bin/bash
Read More
#!/usr/bin/env python
前阵想试下 Spring MVC 4 有了些什么新特性, 可真正用 Maven 在 IDE 中建立一个项目并不那么容易. Spring 当初在笑 EJB 的笨重时如今把自己也搞大了, 继而出台了一个 Spring Boot 来响应微服务的号召.
Spring Boot 的出世可以大大提升使用 Spring 框架时的开发效率. Spring 尽量简化 Spring 项目的配置, 一个mvn package就轻轻松的把一个 Web 项目打成一个fat jar, 运行java -jar spring-boot-sample-1.0-SNAPSHOT.jar就能通过内嵌的 Tomcat 或 Jetty 来启动一个 Web 应用了, 更别提怎么应对普通控制台应用了.
现在就来体验一下 Spring Boot 做一个 Spring MVC 项目有多简单, 我们仍然是建立一个 Maven 项目, 最简单的pom.xml文件内容如下: Read More- .NET Core 上不光可以做控制台的程序, 还可也实现 AST.NET 的 Web 应用, 而且是自带服务器的那种. 像 NodeJS, Spring Boot, Netty 那种非容器型的嵌入式的 Web Server, 非常适合于做微服务应用. 谁说 ASP.NET 就一定要部署到 IIS 上呢?
本文参考 https://docs.asp.net/en/latest/getting-started.html 而来, 基本步骤是一致的1. 安装 .NET Core
参考上一篇 .NET Core 上手体验 Hello World2. 创建 .NET Core 项目
mkdir appnetcoreapp
cd aspnetcoreapp
dotnet new3. 更新
Read Moreproject.json引入 Kestrel HTTP server 依赖 拟此篇以温习 Scala 对方法调用上的一些约定. 标题中说是关于操作符的事, 其实 Scala 像有了访问方法和属性的一致性原则一样, 可以说操作符与方法更是统一的, 或者说只有方法调用. 此处所称的操作符只不过是 Scala 对无参(prrameterless), 或只有一个参数的方法, 和特殊的四个
unary_+,unary_-,unary_!,unary_~方法的便捷的调用约定格式.一. 中置操作符(对只有一个参数方法的调用约定,
a plus b)case class MyNumber(value: Int) {
def +(that: MyNumber) = MyNumber(this.value + that.value)
}调用方式
1MyNumber(10).+(MyNumber(20)) //标准调用格式 2MyNumber(10) + MyNumber(20) //只有一个参数时, 不用点, 不用括号第一行是用点语法的标准方法调用格式, Scala 在当方法只有一个参数时, 可以省略点, 以及括号, 因此可写为上面第二行种的格式. 所以方法
+就化身为了中置操作符了. Read More
对于大多数的脚本编程语言来说, 提供有现成的分别进入控制台与执行脚本文件的命令. 例如 Scala, Python 默认进入控制台(REPL), 接文件路径为参数则执行脚本文件. 还有分别进入控制台和执行脚本的命令是: irb 与 ruby, groovsh 与 groovy, php -a 与 php, perl -de1 和 perl. 可以 Clojure 本身就没有 clojure 这样的命令. 当我们试图在 Mac 下用 brew install clojure 安装时, 得到的提示是没有 clojure, 应该用brew install leiningen去安装 leiningen, 它是一个类似于 Scala sbt 的工具.
所以启动 Clojure REPL 的命令就是lein repl, 其实还有一个办法来启动 Clojure 的控制台, 因为 Clojure 也是构筑于 JVM 之上的, 所以也能像启 Groovy/Scala 一样通过java指令加载 jar 文件来启动. 去官网 http://clojure.org/ 下载 Clojure 安装包(例如: ), 解压, 假定它的 jar 文件是~/Developers/clojure-1.8.0/clojure-1.8.0.jar, 那么也可以用命令java -jar ~/Developers/clojure-1.8.0/clojure-1.8.0.jar进到 Clojure 控制台.
进到 Clojure 的提示符user=>下就可以测试 Clojure 代码了, 那么如何加载一个写在clj文件里的代码呢? 我们可以在 Clojure 控制台下用方法load-file. 假定 ~/hello.clj 文件的内容是 Read More