- 前面说过 JMockit 因身处前线,所以简直无不可,本节例子演示 JMockit 怎么 Mock 私有方法和私有属性,示例虽然是静态方法和属性,但因采用的是反射手法,所以这种 Deencapsulation 的 Mock 手段同样适用于公有的方法或属性,无论是否静态。
本文所用 JMockit 版本为 1.6, 可能网上所搜索的方法与此有所不同,请注意 JMockit 版本差异。仍需重复一下,运行 JMockit 的例子 classpath 上必须让 jmockit.jar 在 junit.jar 之前,或用 javaagent 参数来加载 jmockit.jar,并且 junit 要 4.8 及以上版本.
1. Mock 私有方法(非静态类似) Read More - TDD 推求测试先行,不光在自己代码未实现时可以先做好测试,即使平台依赖或第三方接口未准备好我们也能先行一步的,这就要对接口依赖进行 Mock。同时 Mock 也使得我们的测试代码在运行当中不至于随着第三方接口的沦陷而坠入深渊。
Java 中 Mock 工具也不少,像通用 EasyMock, jMock, Mockito, Unitils Mock, PowerMock, 再比如偏专业的 HttpMock, StrutsMock 等。但 JMock 与前面各位相比简直是全能选手,对 final/static/native/private 方法都能 Mock,功能上还远不止这些了,可以看看一个对比图 https://code.google.com/p/jmockit/wiki/MockingToolkitComparisonMatrix。
JMockit 是基于 Java5 的 java.lang.instrument 包开发的,所以它才能夺得先机,也可陷得更深。自然它要求 JDK5 及以上,JUnit 4.8 及以上版本。命令行下原来用 -javaagent:/.../lib/jmockit.jar 加载 JMockit,现在发现把 jmockit.jar 放在 classpath 下就 OK 的,但是必须放在 junit.jar 包之前,否则你会看到这个 java.lang.IllegalStateException: JMockit wasn't properly initialized; check that jmockit.jar precedes junit.jar in the classpath。 JMockit 有两种 Mock 方式:
1. Behavior-oriented(Expectations & Verifications) --- 基于代码执行行为的模仿,象黑盒测试
2. State-oriented(MockUp<GenericType>) --- 侵入类内部,随意模仿,似白盒,可以说是能为所欲为
此篇体验下第一种 Mock 方式,在测试代码中最直观就是那个new Expectations(...){{result = some;}},下面来看个实际的例子。应用场景是 Read More - 使用 Ant 进行自动化处理时,不想记住每一个 target 的名称,而是让默认的 target 列出需要的 target 出来,让用户输入名称或数字选择执行哪个 target。这样做自然是多了一步,有时候确也方便不少,但 Ant 还是有个缺点,它不能持久性的保持在 Ant 控制台下,持续的进行用户交互。
对于实现 Ant 的简单用户交互,我们可以借助于两个 Task,input 和 antcall, input 用来提示用户输入值,再根据 input 设定的属性来确定 antcall 调用哪个 target。执行完退出到系统 Shell 下,想要再来,就再执行一下 ant 吧,我也只能做到这一步了。
看下面的例子 build.xml 内容 Read More - 最早的时候,我们只需要 GET 和 POST 方法,POST 方法的引入也只是为了消除 URL 过长,参数隐藏,上传文件的问题,完全和语义无关。接触到 RESTful 之后,我们开始思考 GET 和 POST 的不同语义,并且十分必要的去发掘出所有的 HTTP method,HTTP/1.1 所实现的 method,见 RFC 2616, 有这些:
OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT
规范是这么定义的,这还要看容器实现了多少,比如 Tomcat 7 中的 servlet api 实现了
doOptions, doGet, doHead, doPost, doPut, doDelete, doTrace 就差个 doConnect 了。
而我们这里要说的 PATCH method 是在 Servlet 3.0 和当前 Tomcat 7 中都提到的,也就是尚未实现它。
这也难怪,PATCH 在 2010 年三月份才成为正式的方法,见 RFC 5789。没有 PATCH 的时候我们进行更新的操作采用的是 PUT 方法。那么 PATCH 和 PUT 有什么区别呢?
同样可以从语义上去理解,有两方面的对比: Read More - JDK8 都快要出来了,在 JDK 5 中仍有许多好宝贝值得去挖掘。提到 JDK5 我们或许只知道它给了我们泛型,其实还有那个并发包 java.util.concurrent 却不那么引人注目,其实就是 NIO。
若是并发包是在某个 JDK 版本中单独奉上,反响就不同了,想想 JDK 6 似乎未带来多少改变--至少对于编程者来说没有明显感受。java.util.concurrent 包中的东西对于我们处理线程带来了很大的便利,例如线程池,线程同步,Future, Callable 等。
这里我记录一下 CountDownLatch 的使用,在此之前在处理
线程 A 等待线程 B,C,D 全部执行完后才继续执行 (比如要每个线程都访问一个 Web 服务,等所有的请求响应成功后进行结果处理)
这样场景的时候,我一般能想到的办法是,初始一个计数器,线程 B,C,D 各自初始化的时候,计数器加一,然后 A 线程等待,每个线程执行完后计数器减一,当计数器为 0 时表明所有任务执行完毕,就通知 A 可以开始运作起来。但这样的方案还是得小心的处理好同步的问题。 Read More - Lambda 允许我们定义匿名方法(即那个 Lambda 表达式,或叫闭包),作为一个功能性接口的实例。如果你不想把一个 Lambda 表达式写得过大,那么你可以把表达式的内容分离出来写在一个方法中,然后在放置 Lambda 表达式的位置上填上对那个方法的引用。
方法引用也应看作是一个 Lambda 表达式,所以它也需要一个明确的目标类型充当功能性接口的实例。简单说就是被引用的方法要与功能接口的 SAM(Single Abstract Method) 参数、返回类型相匹配。方法引用的引入避免了 Lambda 写复杂了可读性的问题,也使得逻辑更清晰。
为了应对方法引用这一概念, JDK8 又重新借用了 C++ 的那个 “::” 域操作符,全称为作用域解析操作符。
上面的表述也许不好明白,我看官方的那份 State of the Lambda 也觉得不怎么容易理解,特别是它举了那个例子很难让人望文生意。我用个自己写的例子来说明一下吧。
目前的 Eclipse-JDK8 版还不能支持方法引用的特性,幸好就是在昨天正式版的 NetBeans IDE 7.4 对 JDK8 有了较好的支持,所以在 NetBeans 7.4 中写测试代码。 Read More - Lambda 表达式的词法范围,一言以蔽之就是没有引入新的词法范围。这里的词法范围要研究的课题是 this 的指向有没有在变。我们知道在匿名类内部 this 指向的匿名类的实例,这种关系是在编译期就确定的。而在 Lambda 表达式中的 this 与外部的 this 没有差别,也就是说你可以把 Lambda 表达式当成一般的语句来看待,多简单啊,不像 JavaScript 中的 this 被搞的那么魔幻。
可以这么测试:1public void foo(){ 2 System.out.println(this); 3 Arrays.asList("Unmi").forEach((s) -> System.out.println(this)); 4}
输出的是同一个地址里的东西:
cc.unmi.testjdk8.TestLambda@28a418fc
cc.unmi.testjdk8.TestLambda@28a418fc
接下来瞧瞧 Lambda 表达式对外层变量的捕获。Lambda 表达式的有个好处就是它是轻量级,可重用单元,并且可捕获外层变量。 Read More - 现在我们来看看 JDK8 是怎么对 Lambda 表达式进行类型推断的,Lambda 的实际类型叫做它的目标类型(target type),因为 JDK8 沿承了已有的类型系统,所以象这样的写法:
button.addActionListener((ActionEvent e) -> foo());
addActionListener() 方法接收的是一个 ActionListener 类型的参数,所以这里的 Lambda 表达式 (ActionEvent e) -> foo() 代表的就是一个 ActionListener 实例,编译器是怎么知道这一点的,而且相同的 Lambda 是可以表示不同的类型的,见:
Callable<String> c = () -> "done"; //这个 () -> "done" 是 Callable<String> 类型
PrivilegedAction<String> a = () -> "done"; //同样的 () -> "done" 却是 PrivilegedAction<String> 类型
答曰,根据 Lambda 表达式所处的上下文去感知。上下文决定了 Lambda 所期盼的类型,比如说变量声明类型,方法要求的输入参数类型,所以它必须是明确,不能模棱两可。所以一个 Lambda 表达式能否放在某处需要满足以下几个条件:- 期盼的类型必须是一个功能性接口,这样就能唯一定位到那个抽象方法上去,确定方法签名,接下来就是 Read More
- JDK8 使用一行 Lambda 表达式可以代替先前用匿名类五六行代码所做的事情,那么它是怎么实现的呢?从所周知,匿名类会在编译的时候生成与宿主类带上 $1, $2 的类文件,如写在 TestLambda 中的匿名类产生成类文件是 TestLambda$1.class, TestLambda$2.class 等。
我试验了一下,如果使用的是 Lambda 表达式并不会生成额外的类文件,那么字节码里是什么样子的?来看下用 javap -c 反编译出下面文件产生的 TestLambda.class,两个方法,一个是 byAnonymousClass() 使用匿名类,另一个是 byLambda 使用 Lambda 的方式:1package cc.unmi.testjdk8; 2 3import java.awt.event.ActionEvent; 4import java.awt.event.ActionListener; 5 6import javax.swing.JButton; 7 8public class TestLambda{ 9 private JButton button = new JButton(); 10 11 public void byLambda() { 12 button.addActionListener((ActionEvent e) -> System.out.println("Lambda")); 13 } 14 15 public void byAnonymousClass(){ 16 17 button.addActionListener(new ActionListener() { 18 @Override 19 public void actionPerformed(ActionEvent e) { 20 System.out.println("Anonymous class"); 21 } 22 }); 23 } 24}
Read More - 过去,我们使用匿名类的实现像 ActionListener 这样的接口,即使最简单的情景都需要写上五六行代码,这就显得有些累赘了。因为其实我们在 new ActionListener {} 时就是在创建一个 ActionListener 子类型,重载了方法,并初始化一个实例出来。现在我们手上有了 Lambda 表达式这一武器的话,就变得简单明了了,所以说 Lambda 是一种轻量级的实现机制。
这里是几个 Lambda 表达式的样子:1(int x, int y) -> x + y //两整形参数,返回它们的和 2() -> 42 //无参数,直接返回 42 3x -> 100; //可推断出参数 x 的类型 4(String s) -> { System.out.println(s); } //传入一个字符, 只执行一个操作, 无返回值
Lambda 的格式就是三部分:参数列表,-> 分隔符,执行体。其他语方的 Lambda 格式都这样的,可能就是分隔符不一样,如 Ruby 的 |,Scala 的 => 等。
执行体里的 return 只是标明返回到匿名方法的调用者,不是返回值的意思; 最上层是不能用 break 和 continue 关键字的,可用在循环中; 如果 Lambda 表达式有返回值,那么在每一条路径上都要有返回值或是抛出异常。路径上最后一条语句的返回值即为 Lambda 表达的返回值。 Read More