使用Groovy 编写Java 代码的测试

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]
      

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • 什么是单元测试? 单元测试(又称为模块测试, Unit Testing)是针对程序模块(软件设计的最小单位)来进行...
    常晓csc阅读 9,375评论 0 6
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • groovy是什么 Groovy 是下一代的Java语言,跟java一样,它也运行在 JVM 中。 作为跑在JVM...
    ronaldo18阅读 671评论 0 4
  • 单元测试正是敏捷方法的核心所在。 1.测试是可执行的代码范例,即使文档有过期的风险,测试则会紧跟代码,因为这是编译...
    平飞兄阅读 4,412评论 0 6
  • 今天我们休息,中午吃饭可以说是战场,是我的战场。最终,我控制住了自己情绪,结果虽然不是最完美,但至少我没有替...
    璇子_5373阅读 195评论 0 0