使用 Mockito 修改私有属性

修改私有属性来 Mock 可能不是一种很好的测试方式, 因为属性名是动态的,但有时不得已而为了,例如下面的代码:

public class UserService {
    private ExternalApi external = ExternalApi.default();
    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User findUserById(int id) {
        return userDao.findById(external.convertId(id));
}

测试时欲隔离对 ExternalApi 的外部依赖, 当然可以把它也作为构造函数的一个参数,这样创建 UserService 实例时就可以 Mock external 属性。不过 external 经常是不变的,所以作为方法参数的必要性也不大。这就希望能在构造出 UserService 之后对 external 私有属性进行 Mock 处理。

在 Mockito 1.x 和 2.x 下要使用不同的方式,分别使用到 Whitebox 和 FieldSetter 类,它们都来自于  mockito.internal.util.reflection 包,可见 Mockito 打心底不推荐直接使用它们,但谁叫它们是 public 的呢。还有一种方式是使用 PowerMock + Mockito, 这是后话。 阅读全文 >>

AWS Java Lambda 与环境变量

一句话概要:对 Lambda 环境变量的任何改动都会引起一次 Lambda 的冷启动,大可放心在 handleRequest(...) 方法外使用环境变量。

AWS 上 Java Lambda 应用记要 中,我学到了 Lambda 的实例是跨请求共享的,所以为使用 Lambda 配置的环境变量时曾写出了下面复杂而多余的  AWS  Lambda 代码:

public class Handler implements RequestHandler<SNSEvent, String> {

    private int threadPoolSize = getThreadPoolSizeFromEnv();
    private ExecutorService threadPool = Executors.newFixedThreadPool(threadPoolSize);

    @Override
    public String handleRequest(SNSEvent snsEvent, Context context) {

        int configuredThreadPoolSize = getThreadPoolSizeFromEnv();
        if(configuredThreadPoolSize != threadPoolSize) {
            threadPoolSize = configuredThreadPoolSize;
            threadPool = Executors.newFixedThreadPool(threadPoolSize);
        }

        return "Hello Lambda";
    }

    private int getThreadPoolSizeFromEnv() {
        return Integer.parseInt(System.getenv().getOrDefault("threadpool_size", "50"));
    }
}

这段代码看起来很在理,既然 Lambda 实例是共享的,那么在必变环境变量之后就可能不会重新初始始化实例,所以在每次的请求方法中对比如果环境变量值改动了就重新用最新的配置值来初始化线程池。然而上面的代码结结实实是多余的,真是把 Lambda 想得太简单了,如果是很多环境变量岂不是逐一判断。 阅读全文 >>

AWS Java Lambda 使用 Logback 记录日志

直接一句话:去掉 Log4J 的依赖,把  Slf4J, Logback, 和 log4j-over-slf4j 依赖加进来就行了,配置文件换成 logback.xml,这就完了,不要往下看了,都是些废话。

当我们用 Serverless 命令 sls create -t aws-java-maven -p hello-lambda 创建的示例项目中直接用的是 Log4J 日志组件,而且也没用像  Slf4j, 或 Apache Common Logging 更上一层的通用日志框架。查看了几个 AWS 本身的组件 S3, SNS, 和 Kinesis 的 SDK, 它们内部是用的 Apache Common Logging 声明的日志变量

import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Logger;

private static final Log log = LogFactory.getLog(AmazonKinesis.class)

而我们自己的组件中通用日志组件是 Slf4j, 底层实现为 Logback, 所以我们希望在 Lambda 中使用 Logback 来写日志。

选用一个通用日志框架总是明智之举,因为一个项目经常杂糅了多种日志实现,使用 Slf4J 或 Apache Common Logging 可以把它们(Log4J, Logback, 或更多)输出到共同的目的地,并且有统一的日志输出接口。而我们认为通用日志框架还是 Slf4J 要先进些,所以我们在 Java Lambda 中的日志方案是 Slf4J + Logback,还需要把 Log4J 的日志桥接到 Slf4J 上来,再经由 Logback 输出。

回到前面创建的 hello-lambda 项目,看其中怎么用的 Log4J,先瞧瞧 pom.xml 文件怎么引入的 Log4J, 它是间接通过一个 AWS 定义的 Log4J Appender 引入的 阅读全文 >>

AWS 上 Java Lambda 应用记要

AWS 的 Lambda 给了那些不想自己管理 EC2 服务器和配置负载人员很大的便利,所以 Lambda 被描述为 Serverless。真正的只关注业务就行,怎么调度,同时有多少个实例运行交给亚马逊去处理就是了。运行 Lambda 的环境也是亚马逊内部的 EC2 服务器,镜像是 Amazon Linux, 所以如果想运行系统命令,那是 Linux 的。Lambda 支持多种语言 Node.js, Python, C#(.net core), 还有 Java 8,我们就选择了 Java 8, 一开始还担心它与别的语言比起来会多大劣势,其实不然。而且所谓的 Java 8, 并非单指 Java 语言,而是指 JVM 平台,所以也可以用 Scala, Clojure, Groovy, Kotlin 来写。

Java 与脚本语言如 Node.js, Python 相比给人一个明显的感觉是启动慢,还有人用统计数据来比划 AWS Lambda cold start(pseudeo-)benchmark. 不过真不用担心,人家说的是冷启动,也就发生在部署后第一次执行启动会比较慢。要是我们的 Lambda 经常被调用,或每天触发比较集中,Lambda 在任务到来之前处理待续状态,就不会有冷启动的耗时过程。或者是每次任务要执行 3 分钟左右,又何必在乎毫秒级的冷启动时间。

说到底就是别理会下面的数据

  • 20ms startup time for Python ~ $0.041675
  • 40ms startup time for Node.js ~ $0.08335
  • 80ms startup time for Java ~ $0.1667

Lambda 实例重用

Java 的 Lambda 就是一个微服务,在首次触发时微服务冷启动有些慢,但一旦启动之后就可以用这个微服务实例接受后续的请求,只有在比较长的一段时间内未被触发 AWS 才会把这个微服务杀掉。 阅读全文 >>

代码整洁之道(Clean Code) 笔记(一)

第一章:整洁代码

  1. 代码即设计
  2. 童子军军规:把露营地清理得比来时还干净,签入代码前是否已做重构
  3. 你湎自行实践,且体验自己失败。你须观察他人的实践与失败
  4. 勒布朗(LeBlanc) 法则:稍后等于永不 (Later equals never)
  5. 赶上期限的唯一方法--做得快的唯一方法--就是始终尽可能保持代码整洁
  6. 缺乏“代码感”的程序员,看混乱是混乱,无处着手。有“代码感”的程序员能从混乱中看出其他的可能与变化
  7. 不好的代码引诱别人做没规矩的优化,搞出一堆混乱来
  8. 整洁的代码只做好一件事,糟糕的代码想做太多事,它意图混乱,目的含混
  9. 整洁的代码从不隐藏设计者的意图
  10. 整洁的代码只提供一种而非多种做一件事的途径
  11. 整洁的代码总是看起来像是某位特别在意它的人写的
  12. 回放我们编码过程显示,多多数时间都是在滚动屏幕,浏览其他模块
  13. 读与写花费时间的比例超过 10:1, 写新代码时,我们一直在读旧代码
  14. 编写代码的难度,取决于读周边代码的难度 阅读全文 >>

使用 JMockit 来 mock 构造函数

Java 测试的 Mock 框架以前是用 JMockit, 最近用了一段时间的 Mockito, 除了它流畅的书写方式,经常这也 Mock 不了,那也 Mock 不了,需要迁就于测试来调整实现代码,使得实现极不优雅。比如 Mockito 在 私有方法,final 方法,静态方法,final 类,构造方法面前统统的缴械了。powermock 虽然可作 Mockito 的伴侣来突破 Mockito 本身的一些局限,但是我一用它来 Mock 一个构造方法就出错

Caused by: java.lang.ClassNotFoundException: org.mockito.exceptions.Reporter

原因是 Mockito 变化太快,powermock 跟不上它的步伐 -- https://github.com/powermock/powermock/issues/684,于是我只能止步。

不得已再祭出 JMockit 这号称(也确实是)一无所不能的大杀器,在此见识一下它怎么 Mock 构造函数的

本篇实例所使用的 JMockit 版本是 1.30, 当前最新版 1.31, 由于尚未被 Maven 中央仓库收录,所以暂用 1.30。在 pom.xml 中如下方式引入 阅读全文 >>

JdbcTemplate 易被 Java 8 Lambda 带入的坑

Spring 的 JdbcTemplate 为我们操作数据库提供非常大的便利,不需要显式的管理资源和处理异常。在我们进入到了 Java 8 后,JdbcTemplate 方法中的回调函数可以用 Lambda 表达式进行简化,而本文要说的正是这种 Lambda 简化容易给我们带来的一个 Bug, 这是我在一个实际项目中写的单元测试发现的。

下面就是我们的一个样板代码,在我们的 UserRespository 中有一个方法 findAll() 用于获得所有用户:

阅读全文 >>

Java 的参数检查与断言 - Spring Assert

之前有一篇 Java 的参数检查与断言 介绍了在 Java 中如何应用 Guava 的 Preconditions 来进行参数检查与状态断言,原本是可以心无旁骛,专心的用它就行了,可是刚刚因琢磨我们使用 JdbcTemplate 存在的一大 Bug,阅读 Spring 的源代码时发现 spring-util 也提供了一个类似于 Gruva Preconditions 的工具类 -- Assert,它是自 Spring 1.1.2 开始就静静的躺在那儿了。

所以现在要检查参数或状态断言时反而犯上了选择综合证,虽然内心还是偏向于 Guava Preconditions,但总之不那么坚决了,也不知到底是谁在重新发明着轮子。可以说 Spring Assert 与 Guava Preconditions 的功能基本一致,也是针对入口参数或中间运行结果的检查分别抛出 IllegalArgumentException 和 IllegalStateException . 下面一张图来了解它的所有方法。 阅读全文 >>

Java 可变参 Object...objects 方法的陷进

Java 的可变参数(Varargs) 方法让我们调用起来很方便,不需要总是去构造一个数组来传递不定数量的参数,而且还可以作为方法的一个可选参数,如

void foo(int id, String...name) {
    String yourName = name.length == 0 ? "Anonymous" : name[0];
        ......
}

想要告诉名字就调用 foo(1, "Yanbin"), 不想的话就用 foo(1).

但我们在使用 Java Varargs 时,当变参类型定义为 Object...objects 时就要当心了,因为 Object 类型的包容性原因一不小心就可以掉到坑里去了,例如下面的方法

void foo(Object...objects) {
     Arrays.stream(objects).forEach(System.out::println());
}

当引用类型是 Object[] 时调用没问题,下面代码调用可以得到预期的结果 阅读全文 >>

Java 与 Python 通过 Apache Avro 交换数据

最近转战到 Amazon 的云服务 AWS 上,考虑到在使用它的 Lambda 服务时 Python 应用有比较可观的启动速度,与之相比而言,Java 总是慢热型,还是一个内存大户。所以有想法 Lambda 函数用 Python 来写,来增强响应速度,而内部的应用仍然采用 Java, 于是就有了 Java 与 Python 的数据交换格式。使用 Kafka 的时候是用的 Apache Avro, 因此继续考察它。

注意,本文的内容会有很大部份与前一篇 Apache Avro 序列化与反序列化 (Java 实现) 雷同,不过再经一次的了应用,了解更深了。

在不同类型语言间进行数据交换,很容易会想到用 JSON 格式,那我们为什么还要用 Apache Avro 呢?通过接下来的内容,我们可以看到以下几点:

  1. Apache Avro 序列化的格式也是 JSON 的,Java 的 Avro 库依赖于 Jackson 库
  2. 序列化数据库本身带有 Schema 定义的,方便于反序列化,特别是对于 Java 代码; 而 JavaScript 会表示多此一举
  3. 自动支持序列化数据的压缩,在官方提供的库中,Java 可支持 deflate, snappy, bzip2, 和 xz. 其他语言中可能少些,如 Python 只支持 deflate, 和 snappy, 应该可扩充。序列化数据中 Schema 部分不被压缩
  4. 天然支持序列化对象列表,这样在序列化数据中只需要一份 Schema,类似于数据库表 Schema 加上多记录行的表示方式。只用 Apache Avro 传输小对象的话,数据量比 JSON 事 JDK 序列化的数据要大。

Apache Avro 官方提供有 C, C++, C#, Java, PHP, Python 和 Ruby 的支持库,可在网上找到其他语种的类库,如 NodeJS, Go 的,等等。 阅读全文 >>