Java服务端单元测试指南(2)

发表于:2022-4-25 09:26

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:墨源    来源:网络

  3 单元测试框架
  3.1 TestNG
  Junit4和TestNG是Java非常流行的单元测试框架。因TestNG更加简洁、灵活和功能丰富,所以我们选用TestNG。
  下面通过与Junit4的比较来了解一下TestNG的特性。
  注解支持
  Junit4和TestNG的注解对比:
  // TODO 测试 测试方法 测试套件 测试组 的区别
  在Junit4中,@BeforeClass和@AfterClass只能用于静态方法。TestNG无此约束。
  异常测试
  异常测试是指在单元测试中应该要抛出什么异常是合理的。
  ·JUnit4
  @Test(expected = ArithmeticException.class)
  public void divisionWithException() {
  int i = 1/0;
  }

  · TestNG
  @Test(expectedExceptions = ArithmeticException.class)
  public void divisionWithException() {
  int i = 1/0;
  }

  忽略测试
  忽略测试是指这个单元测试可以被忽略。
  · JUnit4
  @Ignore("Not Ready to Run")
  @Test
  public void divisionWithException() {
  System.out.println("Method is not ready yet");
  }

  · TestNG
  @Test(enabled=false)
  public void divisionWithException() {
  System.out.println("Method is not ready yet");
  }

  时间测试
  时间测试是指一个单元测试运行的时间超过了指定时间(毫秒数),那么测试将失败。
  · JUnit4
  @Test(timeout = 1000)
  public void infinity() {
  while (true);
  }

  · TestNG
  @Test(timeOut = 1000)
  public void infinity() {
  while (true);
  }

  套件测试
  套件测试是指把多个单元测试组合成一个模块,然后统一运行。
  ·JUnit4
  @RunWith和@Suite注解被用于执行套件测试。下面的代码是所展示的是在“JunitTest5”被执行之后需要“JunitTest1”和“JunitTest2”也一起执行。所有的声明需要在类内部完成。
  java
   @RunWith(Suite.class) @Suite.SuiteClasses({JunitTest1.class, JunitTest2.class}) 
  public class JunitTest5 { 

  · TestNG
  是使用XML配置文件来执行套件测试。下面的配置将“TestNGTest1”和“TestNGTest2”一起执行。
  <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" > 
  <suite name="My test suite">
   <test name="testing">
     <classes>
     <class name="com.fsecure.demo.testng.TestNGTest1" />
     <class name="com.fsecure.demo.testng.TestNGTest2" />
     </classes>
   </test>
  </suite> 

  TestNG的另一种方式使用了组的概念,每个测试方法都可以根据功能特性分配到一个组里面。例如:
  @Test(groups="method1") 
  public void testingMethod1() { 
  System.out.println("Method - testingMethod1()"); 
  } 
  @Test(groups="method2") 
  public void testingMethod2() { 
  System.out.println("Method - testingMethod2()"); 
  } 
  @Test(groups="method1") 
  public void testingMethod1_1() {
   System.out.println("Method - testingMethod1_1()"); 
  } 
  @Test(groups="method4") 
  public void testingMethod4() { 
  System.out.println("Method - testingMethod4()");
   }

  这是一个有4个方法,3个组(method1, method2 和 method4)的类。使用起来比XML的套件更简洁。
  下面XML文件配置了一个执行组为methed1的单元测试。
  <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
  <suite name="My test suite">
      <test name="testing">
          <groups>
              <run>
                  <include name="method1"/>
              </run>
          </groups>
          <classes>
              <class name="com.fsecure.demo.testng.TestNGTest5_2_0" />
          </classes>
      </test>
  </suite>

  分组使集成测试更加强大。例如,我们可以只是执行所有测试中的组名为DatabaseFuntion的测试。
  参数化测试
  参数化测试是指给单元测试传多种参数值,验证接口对多种不同参数的处理是否正确。
  ·JUnit4
  @RunWith和@Parameter注解用于为单元测试提供参数值,@Parameters必须返回List,参数将会被作为参数传给类的构造函数。
  @RunWith(value = Parameterized.class)
  public class JunitTest6 {
  private int number;
  public JunitTest6(int number) {
      this.number = number;
  }
  @Parameters
  public static Collection<Object[]> data() {
      Object[][] data = new Object[][] { { 1 }, { 2 }, { 3 }, { 4 } };
      return Arrays.asList(data);
  }
  @Test
  public void pushTest() {
      System.out.println("Parameterized Number is : " + number);
  }
  }

  它的使用很不方便:一个方法的参数化测试必须定义一个测试类。测试参数通过一个注解为@Parameters且返回值为List参数值列表的静态方法。然后将方法返回值成员通过类的构造函数初始化为类的成员。最后再将类的成员做为参数去测试被测试方法。
  ·TestNG
  使用XML文件或@DataProvider注解两种方式为测试提供参数。
  XML文件配置参数化测试
  方法上添加@Parameters注解,参数数据由TestNG的XML配置文件提供。这样做之后,我们可以使用不同的数据集甚至是不同的结果集来重用一个测试用例。另外,甚至是最终用户,QA或者QE可以提供他们自己的XML文件来做测试。
  public class TestNGTest6_1_0 {
      @Test
      @Parameters(value="number")
      public void parameterIntTest(int number) {
          System.out.println("Parameterized Number is : " + number);
      }
  }

  XML 文件
  <!DOCTYPE suite SYSTEM "http://beust.com/testng/testng-1.0.dtd" >
  <suite name="My test suite">
      <test name="testing">
          <parameter name="number" value="2"/>
          <classes>
              <class name="com.fsecure.demo.testng.TestNGTest6_0" />
          </classes>
      </test>
  </suite>

  @DataProvider注解参数化测试
  使用XML文件初始化数据虽然方便,但仅支持基础数据类型。如需复杂的类型可使用@DataProvider注解解决。
  @Test(dataProvider = "Data-Provider-Function")
  public void parameterIntTest(Class clzz, String[] number) {
      System.out.println("Parameterized Number is : " + number[0]);
      System.out.println("Parameterized Number is : " + number[1]);
  }
  //This function will provide the patameter data
  @DataProvider(name = "Data-Provider-Function")
  public Object[][] parameterIntTestProvider() {
      return new Object[][]{
      {Vector.class, new String[]{"java.util.AbstractList",   "java.util.AbstractCollection"}},
      {String.class, new String[] {"1", "2"}},
      {Integer.class, new String[] {"1", "2"}}
  };
  }

  @DataProvider作为对象的参数
  P.S “TestNGTest6_3_0” 是一个简单的对象,使用了get和set方法。
  @Test(dataProvider = "Data-Provider-Function")
  public void parameterIntTest(TestNGTest6_3_0 clzz) {
      System.out.println("Parameterized Number is : " + clzz.getMsg());
      System.out.println("Parameterized Number is : " + clzz.getNumber());
  }
  //This function will provide the patameter data
  @DataProvider(name = "Data-Provider-Function")
  public Object[][] parameterIntTestProvider() {
      TestNGTest6_3_0 obj = new TestNGTest6_3_0();
      obj.setMsg("Hello");
      obj.setNumber(123);
      return new Object[][]{{obj}};
  }

  TestNG的参数化测试使用起来非常方便,它可以在一个测试类中添加多个方法的参数化测试(JUnit4一个方法就需要一个类)。
  依赖测试
  依赖测试是指测试的方法是有依赖的,在执行的测试之前需要执行的另一测试。如果依赖的测试出现错误,所有的子测试都被忽略,且不会被标记为失败。
  ·JUnit4
  JUnit4框架主要聚焦于测试的隔离,暂时还不支持这个特性。
  · TestNG
  它使用dependOnMethods来实现了依赖测试的功能,如下:
  @Test
  public void method1() {
  System.out.println("This is method 1");
  }
  @Test(dependsOnMethods={"method1"})
  public void method2() {
  System.out.println("This is method 2");
  }

  如果method1()成功执行,那么method2()也将被执行,否则method2()将会被忽略。
  性能测试
  TestNG支持通过多个线程并发调用一个测试接口来实现性能测试。JUnit4不支持,若要进行性能测试需手动添加并发代码。
  @Test(invocationCount=1000, threadPoolSize=5, timeOut=100)
  public void perfMethod() {
      System.out.println("This is perfMethod");
  }

  并行测试
  TestNG支持通过多个线程并发调用多个测试接口执行测试,相对于传统的单线程执行测试的方式,可以很大程度减少测试运行时间。
  public class ConcurrencyTest {
      @Test
      public void method1() {
          System.out.println("This is method 1");
      }
      @Test
      public void method2() {
          System.out.println("This is method 2");
      }
  }

  并行测试配置:
  <suite name="Concurrency Suite" parallel="methods" thread-count="2" >
    <test name="Concurrency Test" group-by-instances="true">
      <classes>
        <class name="wow.unit.test.ConcurrencyTest" />
      </classes>
    </test>
  </suite>

  讨论总结
  通过上面的对比,建议使用TestNG作为Java项目的单元测试框架,因为TestNG在参数化测试、依赖测试以、套件测试(组)及并发测试方面功能更加简洁、强大。另外,TestNG也涵盖了JUnit4的全部功能。
  3.2 JMockit
  Mock的使用场景:
  比如Mock以下场景:
  1. 外部依赖的应用的调用,比如WebService等服务依赖。
  2. DAO层(访问MySQLOracle、Emcache等底层存储)的调用等。
  3. 系统间异步交互通知消息。
  4. methodA里面调用到的methodB。
  5. 一些应用里面自己的Class(abstract,final,static)、Interface、Annotation、Enum和Native等。
  Mock工具的原理:
  Mock工具工作的原理大都如下:
  1. Record阶段:录制期望。也可以理解为数据准备阶段。创建依赖的Class或Interface或Method,模拟返回的数据、耗时及调用的次数等。
  2. Replay阶段:通过调用被测代码,执行测试。期间会Invoke到第一阶段Record的Mock对象或方法。
  3. Verify阶段:验证。可以验证调用返回是否正确,及Mock的方法调用次数,顺序等。
  当前的一些Mock工具的比较:
  历史曾经或当前比较流行的Mock工具有EasyMock、jMock、Mockito、Unitils Mock、PowerMock、JMockit等工具。
  他们的功能对比如下:
  http://billben.iteye.com/blog/1872196
  从这里可以看到,JMockit的的功能最全面、强大!所以我们单元测试中的Mock工具也选择了JMockit。同时在开发的过程中,JMockit的“Auto-injection of mocks”及“Special fields for “any” argument matching”及各种有用的Annotation使单元测试的开发更简洁和高效。
  JMockit的简介:
  JMockit是用以帮助开发人员编写单元测试的Mock工具。它基于java.lang.instrument包开发,并使用ASM库来修改Java的Bytecode。正因此两点,它可以实现无所不能的Mock。
  JMockit可以Mock的种类包含了:
  ·class(abstract, final, static)
  · interface
  · enum
  · annotation
  · native
  JMockit有两种Mock的方式:
  · Behavior-oriented(Expectations & Verifications)
  · State-oriented(MockUp)
  通俗点讲,Behavior-oriented是基于行为的Mock,对Mock目标代码的行为进行模仿,像是黑盒测试。State-oriented是基于状态的Mock,是站在目标测试代码内部的。可以对传入的参数进行检查、匹配,才返回某些结果,类似白盒。而State-oriented的new MockUp基本上可以Mock任何代码或逻辑。
  以下是JMockit的APIs和tools:
  可以看到JMockit常用的Expectation、StrictExpectations和NonStrictExpectations期望录制及注解@Tested、@Mocked,@NonStrict、@Injectable等简洁的Mock代码风格。而且JMockit还自带了Code Coverage的工具供本地单元测试时候逻辑覆盖或代码覆盖率使用。
  JMockit的使用:
  以“第一个单元测试”代码为例:
  · 测试对象
  @Tested:JMockit会自动创建注解为“@Tested”的类对象,并将其做为被测试对象。 通过设置“availableDuringSetup=true”参数,可以使得被测试对象在“setUp”方法执行前被创建出来。
  @Tested(availableDuringSetup = true)
  private BookService bookService;

  · Mock对象
  @Injectable:JMockit自动创建注解为“@Injectable”的类对象,并将其自动注入被测试对象。
  @Injectable
  private BookDAO bookDAO;
  @Injectable
  private UserService userService;

  相关的注解还有:// TODO 待补充
  @Mocked:
  @Capturing:
  @Cascading:
  · 录制
  Expectations:块里的内容是用来Mock方法,并指定方法的返回值、异常、调用次数和耗时。此块中的方法是必须被执行的,否则单元测试失败。
  /**
  * 测试根据用户的Nick查询用户的图书列表方法
  * 其中“getUserBooksByUserNick”方法最终需要通过UserId查询DB,
  * 所以在调用此方法之前需要先对UserService类的getUserIdByNick方法进行Mock。
  */
  @Test
  public void testGetUserBooksByUserNick() throws Exception {
  new Expectations() {
  {
    userService.getUserIdByNick(anyString);
    result = 1234567;
    times = 1;
  }
  };
  List<BookDO> bookList = bookService.getUserBooksByUserNick("moyuan.jcc");
  Assert.assertNotNull(bookList);
  }

  相关的类还有:
  StrictExpectations:块里声明的Mock方法,是必须按照先后顺序被执行的。
  NonStrictExpectations:块里声明的Mock方法,是可以不被执行的。
  MockUP:可以添加到Expectations块中,用来Mock静态类、接口等具体类型的方法实现。也可以独立于Expectations块单独使用,不可放置Expectations块外部使用(即不可与Expectations同层、同时使用)。
  ·结果验证
  Assert:是最常见的断言验证
  Assert.assertNotNull(bookList);
  Verifications:一种特殊的验证块。比如:要验证一个被测试类中,调用的某个方法是否为指定的参数、调用次数。相比Expectations它放在单元测试的最后且没有Mock功能。

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号