单元测试实践

  1. 云栖社区>
  2. 博客>
  3. 正文

单元测试实践

hz-constantine 2018-01-15 22:29:58 浏览1173
展开阅读全文

说在前面的话:

就像阿里规约里提到, 单元测试需要满足的AIR原则 :

  • A:Automatic(自动化)
  • I:Independent(独立性)
  • R:Repeatable(可重复)
    image

工欲善其事,必先利其器. 单元测试三剑客:

  • TestNg:单元测试框架
  • AssertJ:断言工具
  • Jmockit:mock工具

TestNg


testNg是个unit test/sut框架, 支持很多功能。但本篇文章重点不是介绍testNg,简单提一下我觉得比较有用的功能

  • Group:测试用例分组,根据业务/逻辑分组,以更小的粒度来执行case.
  • Parallel:并行执行,可以配置多个线程来执行case.需要注意并发问题.

    <suite name="My suite" thread-count="10" parallel="classes">
     <test name="mocking">
         <packages>
             <package name="com.hz.constantine.jmockit.slideshare.mocking"/>
         </packages>
       </test>
     <test name="faking">
         <packages>
             <package name="com.hz.constantine.jmockit.slideshare.faking"/>
         </packages>
     </test>
     <test name="testng">
         <packages>
             <package name="com.hz.constantine.jmockit.slideshare.testng"/>
         </packages>
     </test>
    </suite>
  • Listener:通过监听器集成自定义的功能
  • DataProvider:测试用例和测试数据分离

    static final String expectData = DataProviderTest.class.getSimpleName();
    static final class DataProvider{
       @org.testng.annotations.DataProvider(name = "str")
       public static Object[][] provide(){
           return new Object[][]{{expectData}};
       }
    }
    @Test(dataProvider = "str",dataProviderClass = DataProvider.class)
    public void dataProviderTest(String data){
       Assert.assertEquals(data,expectData);
    }
  • Timeout/ExpectExceptions:支持用例执行超时 和 异常

    @Test(timeOut = 5,expectedExceptions = {ThreadTimeoutException.class,NullPointerException.class})
    public void timeout(){
       try {
           Thread.sleep(10);
       } catch (InterruptedException e) {
       }
    }

    Tips:附上TestNg Tutorial, http://testng.org/doc/documentation-main.html

TestNg 集成Jmockit


image

  • Java instrumentation:开发者可以构建一个独立于应用程序的代理程序,监测和协助运行在JVM上的程序,虚拟机级别的AOP实现
  • TestNg Listener: TestNg Listener特性提供开发者扩展TestNg的能力.

Jmockit


在jmockit的世界里,它提供两套不同的语法和api. 分别是mocking和faking.下面分别针对这两套做详细的说明

-1- mocking

注解

image

特性
  • Expectations.代表一组调用关联到当期的case.
  • Record-Replay-Verify Mode.
  • Record: 预准备依赖和数据.
  • Replay:执行业务.
  • Verify:校验.
  • RecordingResult
  • FlexibleArgumentTest
  • Delegate表达式
  • Caputring: 用在Verification里.
  • CasCadeMock: 级联Mock

其中最重要的是Record-Replay-Verify Mode ,三段式的表述方式。即

  1. Record: 记录,即定义数据、并mock相关的依赖.
  2. Replay:回放,即执行被测试的业务逻辑.
  3. Verify:校验,即校验逻辑是否正确.
    废话不多说,直接上代码:

    @Test(expectedExceptions = MissingInvocation.class)
    public void recordReplyVerifyNotInOrder() {
       //Record
       ClassUnderTest classUnderTestInstance = newClassUnderTestInstance();
       final String data = this.getClass().getSimpleName();
       new Expectations(classUnderTestInstance.getEye(), classUnderTestInstance.getRepository()) {
           {
               classUnderTestInstance.getEye().find();
               result = data;
               classUnderTestInstance.getRepository().insert(data);
               times = 1;
           }
       };
       //Replay
       classUnderTestInstance.action();
       //Verify
       new VerificationsInOrder(){
           {
               classUnderTestInstance.getRepository().insert(data);
               classUnderTestInstance.getEye().find();
           }
       };
    }

其中 ClassUnderTest 是被测试类,依赖了Eye 和 Repository, 在Record阶段被定义了mocking.
其他特性的测试类,可以参看我的github: https://github.com/cscpswang/java-practice

-2- faking

  • Faking
  • unspecifiedFaking
  • Invocation Context
    第2个和第3个特性依然可以参看我的github,重点说明一下特性1:

1.定义一个被测试对象,一个echoServer(在io编程中,echoServer表示接受到任何消息不做处理,直接返回原消息)

       class EchoServer {
               private Dependency dependency=new Dependency();
               public String echo(String msg) {
                   return msg;
               }
               public String run(){
                  return dependency.run();
               }
       }

2.定义一个依赖类,这个类会在单测中被faking.

         class Dependency {
             public String run(){
                 return "dependency run";
             }
         }

3.case,测试EchoServer.

      public void applyFakesWithDependency(){
          final String msg = "i'm constantine";
          EchoServer echoServer = new EchoServer();
          final class DependencyMock extends MockUp<Dependency> {
              @Mock
              public String run(){
                  return msg;
              }
          }
          new DependencyMock();
          String actualMsg =  echoServer.run();
          Assert.assertEquals(actualMsg,msg);
      }

tips:

  1. spring 中使用faking api时泛型指定到具体的impl类.
  2. 被jdk 代理的类(如spring bean被aop),并不是实现类的实例. 所以要么基于接口mocking(需要mock接口的所有方法),要么使用faking.

小结:

Faking和Mocking是两套jmockit的api,在其官方文档,有下面一句话:

In particular, the use of both the Faking API and the Mocking API in the same test class should be viewed with suspicion, as it strongly indicates misuse.

image
我更偏爱使用Mocking api, 因为它的特性很酷。 但它不能mocking私有方法,这点有时会不太方便。

最后,如果你要下jmockit的源码,并希望研究,注意一个坑。由于jmockit基于java agent(instrumentation),你需要在你的classpath下放置一个jmockit-xxx.jar.

网友评论

登录后评论
0/500
评论
hz-constantine
+ 关注