使用Groovy 编写Java 代码的测试

作者: 沪上最强亚巴顿 | 来源:发表于2017-01-14 13:44 被阅读2895次

    Groovy

    Groovy 是一种带有可选类型系统的动态语言. 借助Groovy语言, 可以在需要强类型时得到类型系统的静态检查保障, 而在需要灵活性时, 享受到Duck Typing 的便利性.

    在编写测试代码方面上, Groovy 的优势主要体现在optional syntax rulepower assertion statement 两个方面上.

    • optional syntax rule. 在Java 中强制的部分语法规则, 如分号, 变量类型, 访问修饰符,在Groovy 中都是可选的.

      • 对测试的影响: 跳过Java private 修饰符的封装性, 测试类可以读取被测试类的内部状态.
    • power assertion statement. 提供了强大的多样化的assert.

      • 主要优势: 比Java 更有可读性, 且能够清晰地展示验证失败时的结果.

      • 例如, 在Java 中的断言语句:

        Assert.isTrue(foo.bar.equals("hello"));

        在Goovy 中可以写成这样:

        assert foo.bar == "hello".

        更进一步, 使用Spock 测试框架, 可以进一步简写为:

        expect:
        foo.bar == "hello"
        

    Spock

    Spock 集成了Junit, JMock 和RSpec 等测试框架的优势, 使开发者能够使用BDD DSL 语言进行测试代码的编写.

    它完全兼容Junit, 同时不需要依赖任何的Mock 框架(如Mockito).

    关于Spock 技术的更多信息, 请参考Spock Primer.

    在这里, 给出Spock 与JUnit 的术语对比表. 以增加大家的直观理解.

    Spock JUnit
    Specification Test class
    setup() @Before
    cleanup() @After
    setupSpec() @BeforeClass
    cleanupSpec() @AfterClass
    Feature Test
    Feature method Test method
    Data-driven feature Theory
    Condition Assertion
    Exception condition @Test(expected=…)
    Interaction Mock expectation (e.g. in Mockito)

    实践

    在Intellij IDEA 作为IDE, 并使用Gradle 作为工程构建工具.

    环境准备

    • 在Intellij 中安装gmavnen intelliJ pluginspock plugin 两个插件.

    • build.gradle 中应用Groovy 插件:

      apply plugin: 'groovy'

      该插件会在编译期间, 编译src/main/groovysrc/test/groovy 目录下的Groovy 源文件.

    • build.gradle 中添加Spock 的依赖:

      testCompile(
          ...
          "org.spockframework:spock-core:$spockCoreVersion",
      )
      

    对Java 单元测试的改造

    • 首先, 这是遗留的使用Java 语言编写的单元测试代码:

      @RunWith(SpringJUnit4ClassRunner.class)
      public class DefaultGatewayInterruptServiceTest {
      
          @Mock
          private GatewayInterruptMapper gatewayInterruptMapper;
      
          @Mock
          private CompanyManageService companyManageService;
      
          @Mock
          private AreaManageService areaManageService;
      
          @Mock
          private RolePermissionManageService rolePermissionManageService;
      
          @InjectMocks
          DefaultGatewayInterruptService service;
      
          @Test
          public void should_return_gateway_interruptions() {
              List<GatewayInterrupt> interrupts = Lists.newArrayList();
              GatewayInterrupt interrupt1 = new GatewayInterrupt();
              interrupt1.setCompanyId(1L);
              interrupt1.setDistrictId(11L);
              interrupt1.setSiteId(111L);
              interrupt1.setGatewayId(1111L);
              interrupt1.setInterruptTime(new GregorianCalendar(2000, 1, 1).getTime());
              interrupt1.setRecoveryTime(new GregorianCalendar(2000, 1, 2).getTime());
              interrupt1.setStatus(false);
              interrupts.add(interrupt1);
      
              GatewayInterrupt interrupt2 = new GatewayInterrupt();
              interrupt2.setCompanyId(1L);
              interrupt2.setDistrictId(11L);
              interrupt2.setSiteId(111L);
              interrupt2.setGatewayId(2222L);
              interrupt2.setInterruptTime(new GregorianCalendar(2000, 1, 1).getTime());
              interrupt2.setStatus(true);
              interrupts.add(interrupt2);
      
              when(gatewayInterruptMapper.getAll()).thenReturn(interrupts);
      
              HashMap<Long, Company> companyHashMap = Maps.newHashMap();
              Company company = new Company();
              company.setName("compnay1");
              companyHashMap.put(1L, company);
              when(companyManageService.getCachedReadOnlyCompanyMap()).thenReturn(companyHashMap);
      
              HashMap<Long, District> districtHashMap = Maps.newHashMap();
              District district = new District();
              district.setName("district1");
              districtHashMap.put(11L, district);
              when(areaManageService.getCachedReadOnlyDistrictMap()).thenReturn(districtHashMap);
      
              HashMap<Long, Site> siteHashMap = Maps.newHashMap();
              Site site = new Site();
              site.setName("site1");
              siteHashMap.put(111L, site);
              when(areaManageService.getCachedReadOnlySiteMap()).thenReturn(siteHashMap);
      
              HashMap<Long, Gateway> gatewayHashMap = Maps.newHashMap();
              Gateway gateway1 = new Gateway();
              gateway1.setId(1111L);
              gateway1.setName("gateway1");
              Gateway gateway2 = new Gateway();
              gateway2.setId(2222L);
              gateway2.setName("gateway2");
              gatewayHashMap.put(1111L, gateway1);
              gatewayHashMap.put(2222L, gateway2);
              when(areaManageService.getCachedReadOnlyGatewayMap()).thenReturn(gatewayHashMap);
      
              List<User> users = Lists.newArrayList();
              User user = new User();
              user.setName("user1");
              users.add(user);
              when(rolePermissionManageService.getManagerOfSite(any())).thenReturn(users);
      
              List<GatewayInterruptDTO> interruptions = service.getGatewayInterruptions();
              assertThat(interruptions.size(), is(2));
          }
      }
      
    • 使用Groovy 进行改造后的代码如下:

      class DefaultGatewayInterruptServiceSpec extends Specification {
          def gatewayInterruptMapper = Mock(GatewayInterruptMapper)
          def companyManageService = Mock(CompanyManageService)
          def areaManageService = Mock(AreaManageService)
          def rolePermissionManageService = Mock(RolePermissionManageService)
      
          def service = new DefaultGatewayInterruptService
                  (gatewayInterruptMapper, companyManageService, areaManageService, rolePermissionManageService)
      
          def "should return gateway interruptions"() {
              given:
              def interrupt1 = new GatewayInterrupt(
                      companyId: 1L, districtId: 11L, siteId: 111L, gatewayId: 1111L,
                      interruptTime: new GregorianCalendar(2000, 1, 1).getTime(),
                      recoveryTime: new GregorianCalendar(2000, 1, 2).getTime(),
                      status: false
              )
              def interrupt2 = new GatewayInterrupt(
                      companyId: 1L, districtId: 11L, siteId: 111L, gatewayId: 2222L,
                      interruptTime: new GregorianCalendar(2000, 1, 1).getTime(),
                      status: true
              )
      
              when:
              def interruptions = service.getGatewayInterruptions()
      
              then:
              gatewayInterruptMapper.getAll() >> [interrupt1, interrupt2]
              companyManageService.getCachedReadOnlyCompanyMap() >> [1L: new Company(name: "company1")]
              areaManageService.getCachedReadOnlyDistrictMap() >> [11L: new District(name: "district1")]
              areaManageService.getCachedReadOnlySiteMap() >> [111L: new Site(name: "site1")]
              areaManageService.getCachedReadOnlyGatewayMap() >> [1111L: new Gateway(id: 1111L, name: "gateway1"), 2222L: new Gateway(id: 2222L, name: "gateway2")]
              rolePermissionManageService.getManagerOfSite(_ as Site) >> [new User(name: "user1")]
      
              interruptions.size() == 2
          }
      }
      

      这段代码中体现了Groovy 的强大便利:

      • 构造器中能够给字段赋值.

        def interrupt1 = new GatewayInterrupt(
                        companyId: 1L, districtId: 11L, siteId: 111L, gatewayId: 1111L,
                        interruptTime: new GregorianCalendar(2000, 1, 1).getTime(),
                        recoveryTime: new GregorianCalendar(2000, 1, 2).getTime(),
                        status: false
                )
        
      • List 字面量和Map 字面量:

        [interrupt1, interrupt2]
        [11L: new District(name: "district1")]
        
      • 简洁的Mock 写法:

        then:
        gatewayInterruptMapper.getAll() >> [interrupt1, interrupt2]
        

    相关文章

      网友评论

        本文标题:使用Groovy 编写Java 代码的测试

        本文链接:https://www.haomeiwen.com/subject/pypebttx.html