一,单元测试

单元测试是对软件基本组成单元进行的测试,如函数或一个类的方法。程序是由函数组成的,每个函数都要健壮,这样才能保证程序的整体质量。单元测试是对软件未来的一项必不可少的投资。”具体来说,单元测试有哪些收益呢?

  • 它是最容易保证代码覆盖率达到100%的测试。
  • 可以⼤幅降低上线时的紧张指数。
  • 单元测试能更快地发现问题。
  • 单元测试的性价比最高,因为错误发现的越晚,修复它的成本就越高,而且难度呈指数式增长,所以我们要尽早地进行测试
  • 编码人员,一般也是单元测试的主要执行者,是唯一能够做到生产出无缺陷程序的人,其他任何人都无法做到这一点。
  • 有助于源码的优化,使之更加规范,快速反馈,可以放心进行重构。

尽管单元测试有如此的收益,但在我们日常的工作中,仍然存在不少项目它们的单元测试要么是不完整要么是缺失的。

1,为什么人人都讨厌写单测

要额外写很多很多的代码

一个高覆盖率的单测代码,往往比你要测试的,真正开发的业务代码要多,甚至是业务代码的好几倍。这让人觉得难以接受,你想想开发 5 分钟,单测 2 小时是什么样的心情。而且并不是单测写完就没事了,后面业务要是变更了,你所写的单测代码也要同步维护。

时间成本

代码逻辑过于复杂,写单元测试时耗费的时间较长,任务重、工期紧,写一个单测的时间可以实现一个需求,那么你如何去选?显而易见。

写单测是一件很无趣的事情

因为他比较死,主要目的就是为了验证,相比之下他更像是个体力活,没有真正写业务代码那种创造的成就感。写出来,验证不出bug很失落,白写了,验证出bug又感到自己是在打自己脸。

2,为什么又必须写单测

所以得到的结论就是不写单测?那么问题又来了,出来混迟早是要还的,上线出了问题,最终责任人是谁?不是提需求的产品、不是没发现问题的测试同学,他们顶多就是连带责任。最该负责的肯定是写这段代码的你。

所以单元测试保护的不仅仅是程序,更保护的是写程序的你。 最后得出了一个无可奈何的结论,单测是个让人又爱又恨的东西,是不想做但又不得不做的事情。

虽然我们没办法改变要写单测这件事,但是我们可以改变怎么去写单元测试这件事。

既然我们不得不去写单元测试,那么今天就为大家推荐一款比较神奇的单元测试框架,Spock去提高你编写单测的效率。

二,Spock是什么

spock官网:https://spockframework.org/spock/docs/2.0/index.html

Spock是国外一款优秀的测试框架,基于BDD(行为驱动开发)思想实现,功能非常强大。Spock结合Groovy动态语言的特点,提供了各种标签,并采用简单、通用、结构化的描述语言,让编写测试代码更加简洁、高效。

那么spock 是如何提高编写单测的效率呢?

  1. 它可以用更少的代码去实现单元测试,让你可以更加专注于去验证结果而不是写单测代码的过程。(Spock使用groovy动态语言的特点)
  2. **他有更好的语义化,让你的单测代码可读性更高。**Spock提供多种语义标签,如: given、when、then、expect、where、with、and 等,从行为上规范单测代码,每一种标签对应一种语义,让我们的单测代码结构具有层次感,功能模块划分清晰,便于后期维护。

三,Spock使用

Spock引入

             <!--引入 groovy 依赖--><dependency><groupId>org.codehaus.groovy</groupId><artifactId>groovy-all</artifactId><version>2.4.15</version><scope>test</scope></dependency><!--引入spock 与 spring 集成包--><dependency><groupId>org.spockframework</groupId><artifactId>spock-spring</artifactId><version>1.2-groovy-2.4</version><scope>test</scope></dependency>

Spock自带Mock功能,所以我们可以来Mock非静态方法。但是遇到静态方法时,我们需要导入powermock

             <!--powermock --><dependency><groupId>org.powermock</groupId><artifactId>powermock-api-mockito2</artifactId><version>2.0.0</version><scope>test</scope></dependency><dependency><groupId>org.powermock</groupId><artifactId>powermock-module-junit4</artifactId><version>2.0.0</version><scope>test</scope></dependency>

但是当我们需要测试dao层的sql语句时,我们可以结合H2内存数据库使用,此时需要引入:

             <!--db unit--><dependency><groupId>com.github.janbols</groupId><artifactId>spock-dbunit</artifactId><version>0.4</version><scope>test</scope></dependency><dependency><groupId>org.dbunit</groupId><artifactId>dbunit</artifactId><version>2.5.1</version><scope>test</scope></dependency><dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId><version>1.4.200</version><scope>test</scope></dependency>

以上就能满足我们平时用Spock来写单测的日常功能。

下面通过一个简单的例子,来体验一下Spock和junit的不同:
public class IDNumberUtils {/*** 根据身份证号码获取出生日期和年龄** @param idNo 18位身份证号码* @return 返回格式: birth: 1992-01-01  age: 29*/public static Map<String, String> getBirthAge(String idNo) {String birthday = "";String age = "";int year = Calendar.getInstance().get(Calendar.YEAR);birthday = idNo.substring(6, 10) + "-" + idNo.substring(10, 12) + "-" + idNo.substring(12, 14);age = String.valueOf(year - Integer.valueOf(idNo.substring(6, 10)));Map<String, String> result = new HashMap<>();result.put("birthday", birthday);result.put("age",age);return result;}
}

Junit单测:

public class IDNumberUtilsJunitTest {@ParameterizedTest@MethodSource("getBirthAgeParams")public void testGetBirthAge(String idNo,Predicate<Map<String,String>> predicate){Map<String, String> birthAgeMap = IDNumberUtils.getBirthAge(idNo);Assertions.assertTrue(predicate.test(birthAgeMap));}public static Object[] getBirthAgeParams(){return new Object[]{new Object[]{"410225199208091234",(Predicate<Map<String,String>>) map -> "{birthday=1992-08-09, age=29}".equals(map.toString())},new Object[]{"410225199308091234",(Predicate<Map<String,String>>) map -> "{birthday=1993-08-09, age=28}".equals(map.toString())},new Object[]{"410225199408091234",(Predicate<Map<String,String>>) map -> "{birthday=1994-08-09, age=27}".equals(map.toString())},new Object[]{"410225199508091234",(Predicate<Map<String,String>>) map -> "{birthday=1995-08-09, age=26}".equals(map.toString())},new Object[]{"410225199608091234",(Predicate<Map<String,String>>) map -> "{birthday=1996-08-09, age=25}".equals(map.toString())},};}
}

Spock单测:

class IDNumberUtilsGroovyTest extends Specification {@Unrolldef "身份证号:#idNO 的生日,年龄是:#result"() {expect: "执行以及结果验证"IDNumberUtils.getBirthAge(idNO) == resultwhere: "测试用例覆盖"idNO                 || result"410225199208091234" || ["birthday": "1992-08-09", "age": "29"]"410225199308091234" || ["birthday": "1993-08-09", "age": "28"]"410225199408091234" || ["birthday": "1994-08-09", "age": "27"]"410225199508091234" || ["birthday": "1995-08-09", "age": "26"]"410225199608091234" || ["birthday": "1996-08-09", "age": "25"]}
}
  • def 是 groovy 的关键字,可以用来定义变量跟方法名。
  • 后面的是你的单元测试名称,可以用中文也可以用英文。
  • expect … where …语句块,expect 为核心的测试校验语句块。where 为多个测试用例的列举。

以上测试方法的语义为:列举多个where下面的多个测试用例,以idNo传参,result为结果,调用getBirthAge方法,来验证每条测试用例是否符合我们的预期,添加@Unroll注解,主要是让每一条测试用例都返回测试结果。

如果不加@Unroll注解,那么会把所有的测试用例返回一个结果。

四,Mock

我们的服务大部分是分布式微服务架构。服务与服务之间通常都是通过接口的方式进行交互。即使在同一个服务内也会分为多个模块,业务功能需要依赖下游接口的返回数据,才能继续后面的处理流程。这里的下游不限于接口,还包括中间件数据存储等等,所以如果想要测试自己的代码逻辑,就必须把这些依赖项Mock掉。因为如果下游接口不稳定可能会影响我们代码的测试结果,让下游接口返回指定的结果集(事先准备好的数据),这样才能验证我们的代码是否正确,是否符合逻辑结果的预期。

1,非静态方法Mock
public class UserService {@Autowiredprivate UserDao userDao;public UserInfo getUserInfoById(long id) {List<UserInfo> userInfoList = userDao.getAllUserInfo();for (UserInfo userInfo : userInfoList) {if (userInfo.getId()==id){return userInfo;}}return null;}
}public class UserDao {public List<UserInfo> getAllUserInfo(){return null;}
}

Junit单测:

@ExtendWith({MockitoExtension.class})
public class UserInfoJunitTest {@Mockprivate UserDao userDao;@InjectMocksprivate UserService userService;@Beforepublic void before(){MockitoAnnotations.openMocks(this);}@Testpublic void userinfoTest(){// 准备参数List<UserInfo> userInfoList = new ArrayList<>();UserInfo userInfo1 = new UserInfo();userInfo1.setId(0);userInfo1.setName("小明");UserInfo userInfo2 = new UserInfo();userInfo1.setId(1);userInfo1.setName("小强");userInfoList.add(userInfo1);userInfoList.add(userInfo2);// mock数据Mockito.when(userDao.getAllUserInfo()).thenReturn(userInfoList);UserInfo userInfo = userService.getUserInfoById(1);// 验证结果Assertions.assertEquals(userInfoList.get(1),userInfo);}
}

Spock单测

class UserInfoSpec extends Specification {def userService = new UserService();def "getUserInfoById"() {given: "准备参数"def user1 = new UserInfo(id: 0, name: "小明")def user2 = new UserInfo(id: 1, name: "小强")and: "mock数据"def userDao = Mock(UserDao)Whitebox.setInternalState(userService, "userDao", userDao)userDao.getAllUserInfo() >> [user1, user2]when: "方法调用"def response = userService.getUserInfoById(1)then: "结果验证"with(response) {id == 1name == "小强"}}
}

**given … when … then …**语句块:given为条件,when为执行方法,then为结果验证。

when … then 通常是成对出现的,;‘它代表着当执行了 when 块中的操作,会出现 then 块中的期望。

比较明显,上边的JUnit单元测试代码冗余,缺少结构层次,可读性差,随着后续的迭代,势必会导致代码的堆积,维护成本会变得越来越高。下面的单元测试代码Spock会强制要求使用givenwhenthen这样的语义标签(至少一个),否则编译不通过,这样就能保证代码更加规范,结构模块化,边界范围清晰,可读性强,便于扩展和维护。而且使用了自然语言描述测试步骤,让非技术人员也能看懂测试代码(given表示输入条件,when触发动作,then验证输出结果)。

Spock自带的Mock语法也非常简单:

def userDao = Mock(UserDao)使用Spock自导的Mock方法构造一个UserDao的Mock对象。

Whitebox.setInternalState(userService, "userDao", userDao)给userService对象中的userDao属性赋值刚才mock出来的userDao。

userDao.getAllUserInfo() >> [user1, user2]

两个右箭头>>表示模拟userDao.getAllUserInfo()接口的返回结果,再加上使用的Groovy语言,可以直接使用[]中括号表示返回的是List类型。

如果要指定返回多个值的话,可以使用3个右箭头>>>,比如:studentDao.getAllUserInfo() >>> [[user1,user2],[user3,user4],[user5,user6]]

每次调用userDao.getAllUserInfo()方法返回不同的值。

2,静态方法Mock

在Spock中可以通过powermock去模拟静态方法、final方法、私有方法等。

但是junit5不支持powermock,只能使用junit4来写

Junit单测

@RunWith(PowerMockRunner.class)
@PrepareForTest(IDNumberUtils.class)
public class IDNumberUtilsStaticTest {@InjectMocksprivate UserService userService;@Beforepublic void setup(){PowerMockito.mockStatic(IDNumberUtils.class);}@Testpublic void testStatic(){// 准备参数Map<String,String> map = new HashMap<>();map.put("birthday","1992-08-09");map.put("age","29");// mockPowerMockito.when(IDNumberUtils.getBirthAge(Mockito.anyString())).thenReturn(map);int age = userService.getUserAgeByCardId("123");// 验证结果Assertions.assertEquals(29,age);}
}

Spock单测:

powermock的PowerMockRunner继承自Junit,所以使用powermock的@PowerMockRunnerDelegate()注解可以指定Spock的父类Sputnik去代理运行power mock,这样就可以在Spock里使用powermock去模拟静态方法、final方法、私有方法等

@PrepareForTest(IDNumberUtils.class)
@RunWith(PowerMockRunner.class)
@PowerMockRunnerDelegate(Sputnik.class)
class IDNumberUtilsStaticSpec extends Specification {def userService = new UserService();void setup() {PowerMockito.mockStatic(IDNumberUtils.class)}def "getAge"() {given: "准备参数"def map = [birthday: "1992-08-09", age: "29"]and: "mock"PowerMockito.when(IDNumberUtils.getBirthAge(Mockito.anyString())).thenReturn(map)when: "方法调用"def respsnse = userService.getUserAgeByCardId("aa")then: "结果验证"with(respsnse){respsnse == 29}}
}

当使用powermock来mock静态方法的时候,必须加注解@PrepareForTest@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。

when模块里是真正调用要测试方法的入口userService.getUserAgeByCardId()

then模块作用是验证被测方法的结果是否正确,符合预期值,所以这个模块里的语句必须是boolean表达式,类似于JUnit的assert断言机制,但不必显示地写assert,这也是一种约定优于配置的思想。

then块中使用了Spock的with功能,可以验证返回结果response对象内部的多个属性是否符合预期值,这个相对于JUnit的assertNotNullassertEquals的方式更简单一些。

五,if…else多分支场景

在我们写业务代码时,经常会写到比较复杂的业务,对应的代码实现会走不通的逻辑,这样的 if else 嵌套代码因为业务的原因很难避免,如果要测试这样的代码,保证覆盖到每一个分支逻辑的话,使用传统的Junit单元测试代码写起来会很痛苦和繁琐。

业务代码:

public class TypeCodeUtil {public static String getTypeNameByCode(int code){String result = "";switch (code){case 1:result = "name1";break;case 2:result = "name2";break;case 3:result = "name3";break;case 4:result = "name4";break;case 5:result = "name5";break;default:break;}return result;}
}

junit单测:

public class TypeCodeUtilTest {@ParameterizedTest@MethodSource("getTypeCodeParams")public void testTypeCode(int code, Predicate<String> predicate){String typeNameByCode = TypeCodeUtil.getTypeNameByCode(code);Assertions.assertTrue(predicate.test(typeNameByCode));}public static Object[] getTypeCodeParams(){return new Object[]{new Object[]{1,(Predicate<String>) value -> "name1".equals(value)},new Object[]{2,(Predicate<String>) value -> "name2".equals(value)},new Object[]{3,(Predicate<String>) value -> "name3".equals(value)},new Object[]{4,(Predicate<String>) value -> "name4".equals(value)},new Object[]{5,(Predicate<String>) value -> "name5".equals(value)},new Object[]{6,(Predicate<String>) value -> "".equals(value)},};}
}

spock单测:

class TypeCodeUtilSpec extends Specification {@Unrolldef "code:#code,name:#name"() {expect:TypeCodeUtil.getTypeNameByCode(code) == namewhere:code || name1    || "name1"2    || "name2"3    || "name3"4    || "name4"5    || "name5"6    || ""}
}

where模块第一行代码是表格的列名,多个列使用|单竖线隔开,||双竖线区分输入和输出变量,即左边是输入值,右边是输出值。格式如下:

输入参数1 | 输入参数2 || 输出结果1 | 输出结果2

表格的每一行代表一个测试用例,即被测方法执行了6次,每次的输入和输出都不一样,刚好可以覆盖全部分支情况。

解析点:

  1. @Unroll注解:表示展开where标签下面的每一行测试用例,都作为一个单独的测试用例来运行。
  2. 方法名:#code和#name,把请求参数值和返回结果值的字符串动态替换掉,#号后面的变量是在方法内部定义的,实现占位符的功能。
  3. expect标签:when + then标签的组合,即 “什么时候做什么 + 验证什么结果” 组合起来。

六,异常测试

业务代码,这个大家应该都很熟悉,针对这种抛出多个不同错误码和错误信息的异常。如果使用JUnit的方式测试,会比较麻烦。如果是单个异常还好,如果是多个的话,测试代码就不太好写。

@Data
@AllArgsConstructor
public class APIException extends RuntimeException{private String errorCode;private String errorMessage;
}@Service
public class ValidateService {public void validateUser(UserInfo userInfo){if (userInfo == null){throw new APIException("1001","userInfo is null");}else if (userInfo.getId()==0){throw new APIException("1002","id is not legal");}else if (userInfo.getName() == null || "".equals(userInfo.getName())){throw new APIException("1003","name is not legal");}}
}

Junit单测:在junit3中可以通过一次次的方法调用并捕获异常来进行测试。这种方式的缺陷是显而易见的。如果有100个异常测试,那么这些调用要写100遍简直要疯掉。

而在junit4中,可以使用ExpectedException@Test这两种方式,但是都有缺陷,@Test方式不能指定断言的异常属性,比如codemessageExpectedException的方式也只提供了expectMessage的API,对自定义的code不支持,尤其像上面的有很多分支抛出多种不同异常码的情况。

在junit4中,可以使用Assertions.assertThrows方法来验证异常,但是依然有上面方式的缺陷。

public class ExceptionTest {private ValidateService validateService = new ValidateService();/*** junit3*/@Testpublic void testException1(){try {validateService.validateUser(null);}catch (APIException e){Assert.assertEquals(e.getErrorCode(),"1001");Assert.assertEquals(e.getErrorMessage(),"userInfo is null");}try {validateService.validateUser(new UserInfo(0,""));}catch (APIException e){Assert.assertEquals(e.getErrorCode(),"1002");Assert.assertEquals(e.getErrorMessage(),"id is not legal");}try {validateService.validateUser(new UserInfo(1,""));}catch (APIException e){Assert.assertEquals(e.getErrorCode(),"1003");Assert.assertEquals(e.getErrorMessage(),"name is not legal");}}/*** junit 4*/@Rulepublic ExpectedException expectedException = ExpectedException.none();@Testpublic void testException2(){expectedException.expect(APIException.class);expectedException.expectMessage("userInfo is null");validateService.validateUser(null);}@Test(expected = APIException.class)public void testException3(){validateService.validateUser(null);}/*** junit5*/@Testpublic void testException4(){Throwable throwable = Assertions.assertThrows(APIException.class,()->{validateService.validateUser(null);});Assertions.assertEquals("userInfo is null",throwable.getMessage());}
}

Spock单测:

class ExceptionSpec extends Specification {def validateService = new ValidateService()@Unrolldef "验证UserInfo"() {when: "调用校验方法"validateService.validateUser(user)then: "捕获异常并设置需要验证的异常值"def exception = thrown(expectedException)exception.errorCode == expectedErrCodeexception.errorMessage == expectedMessagewhere: "验证用户的合法性"user                || expectedException | expectedErrCode | expectedMessagenull                || APIException      | "1001"          | "userInfo is null"new UserInfo(0, "") || APIException      | "1002"          | "id is not legal"new UserInfo(1, "") || APIException      | "1003"          | "name is not legal"}
}

Spock内置thrown()方法,可以捕获调用业务代码抛出的预期异常并验证,再结合where表格的功能,可以很方便的覆盖多种自定义业务异常。

then标签里用到了Spock的thrown()方法,这个方法可以捕获我们要测试的业务代码里抛出的异常。thrown()方法的入参expectedException,是我们自己定义的异常变量,这个变量放在where标签里就可以实现验证多种异常情况的功能。expectedException类型调用validateUser方法里定义的APIException异常,可以验证它所有的属性,errorCodeerrorMessage是否符合预期值。

七,void方法测试

根据UserInfo中的IdNo属性,调用年龄计算工具,计算出年龄后赋值给UserInfo的age属性。

public class UserService {@Autowiredprivate IDNumberUtil idNumberUtil;/*** 根据idNo计算年龄然后赋值给age属性* @param list*/public void setAgeTest(List<UserInfo> list) {if (CollectionUtils.isEmpty(list)) {return;}for (UserInfo userInfo : list) {int age = idNumberUtil.getAge(userInfo.getCardId());userInfo.setAge(age);}}
}

Spock单测:

class VoidMethodSpec extends Specification {def userService = new UserService()def idNumberUtil = Mock(IDNumberUtil)def "测试void方法"() {given: "准备参数"def user1 = new UserInfo(id: 1, cardId: "410225199208091234")def user2 = new UserInfo(id: 2, cardId: "410225199208091234")def list = [user1, user2]and: "属性设置"Whitebox.setInternalState(userService, "idNumberUtil", idNumberUtil)when: "调用方法"userService.setAgeTest(list)then: "验证调用获取年龄是否符合预期: 一共调用2次, 第一次输出29, 第二次是28"2 * idNumberUtil.getAge(_) >> 29 >> 28and: "验证年龄计算后的结果是否符合预期"with(list) {list[0].age == 29list[1].age == 28}}
}

八,dao层测试

DAO层的测试有些不太一样,不能再使用Mock,否则无法验证SQL是否正确。对于DAO测试有一般最简的方式是直接使用@SpringBootTest注解启动测试环境,通过Spring创建Mybatis、Mapper实例,但这种方式并不属于单元测试,而是集成测试范畴了,因为当启用@SpringBootTest时,会把整个应用的上下文加载进来。不仅耗时时间长,而且一旦依赖环境上有任何问题,可能会影响启动,进而影响DAO层的测试。最后,需要到数据库尽可能隔离,因为如果大家都使用同一个Test环境的数据的话,一旦测试用例编写有问题,就可能会污染Test环境的数据。

针对以上场景,可采用以下方案:

  1. 通过MyBatis的SqlSession启动mapper实例(避免通过Spring启动加载上下文信息)。
  2. 通过内存数据库(如H2)隔离大家的数据库连接(完全隔离不会存在互相干扰的现象)。
  3. 通过DBUnit工具,用作对于数据库层的操作访问工具。
  4. 通过扩展Spock的注解,提供对于数据库创建和数据Data加载的方式。
1,通过MapperUtil工具类启动MyBatis的SqlSession获取Mapper实例
public class MapperUtil {private static final String MAPPER_CONFIG_LOCATIONS = "cn/huolala/link/svc/support/mapper-config.xml";/*** 通过MyBatis的SqlSession启动mapper实例* @param type* @param dataSource* @param <T>* @return* @throws IOException*/public static <T> T getMapper(Class<T> type, DataSource dataSource) throws IOException {// 获取资源InputStream inputStream = Resources.getResourceAsStream(MAPPER_CONFIG_LOCATIONS);JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();Environment environment = new Environment("development", jdbcTransactionFactory, dataSource);// 启动SqlSessionFactory获取mapperSqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);sqlSessionFactory.getConfiguration().setEnvironment(environment);SqlSession sqlSession = sqlSessionFactory.openSession();return sqlSession.getMapper(type);}
}
2,通过内存数据库(H2)隔离大家的数据库连接
class SpecUtils {/*** 初始化H2数据库* @return*/static DataSource inMemoryDataSource() {return new HikariDataSource().with { dataSource ->dataSource.driverClassName = 'org.h2.Driver'dataSource.jdbcUrl = 'jdbc:h2:file:/Users/zhaosongbo/spock'dataSource.username = 'root'dataSource.password = 'test'}}/*** 创建相应的表结构* @param dataSource*/static void createAppMainTable(javax.sql.DataSource dataSource){assert dataSourcenew Sql(dataSource).execute("DROP TABLE IF EXISTS app_main; CREATE TABLE `app_main` (\n" +"  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自动增长主键',\n" +"  `app_id` varchar(32) NOT NULL COMMENT '分配给接入方的唯一标识',\n" +"  `app_key` varchar(32) DEFAULT NULL COMMENT 'appkey',\n" +"  `app_secret` varchar(64) NOT NULL COMMENT '分配给接入方的密钥'\n" +") ;")}}
3,Spock来编写相应的测试代码
class AppMainDB2Spec extends Specification {def dataSource/*** 相当于junit的@efore* @return*/def setup() {dataSource = inMemoryDataSource3()createAppMainTable(dataSource)}/*** 构造数据* @return*/@DbUnit(content = {app_main(id: 1001,app_id: "appId1",app_key: "appKey1",app_secret: "appSecret1")app_main(id: 1002,app_id: "appId2",app_key: "appKey2",app_secret: "appSecret2")app_main(id: 1003,app_id: "appId3",app_key: "appKey3",app_secret: "appSecret3")}, configure = { IDatabaseTester it ->it.setUpOperation = DatabaseOperation.CLEAN_INSERTit.tearDownOperation = DatabaseOperation.TRUNCATE_TABLE}, datasourceProvider = { dataSource })def "dao层测试"() {given: "获取mapper"def appMainMapper = MapperUtil.getMapper(AppMainMapper.class,dataSource)when: "执行"def result = appMainMapper.appSecretByAppId("appId1")then: "验证数据"result == 'appSecret1'}
}

吃透单元测试:Spock单元测试框架的应用与实践相关推荐

  1. 高质量的单元测试 Spock单元测试框架详讲

    文章目录 方法篇 为什么需要单元测试 单元测试的定义 单元测试与其他测试的区别 单元测试的作用 关于单元测试的成本 如何写好单元测试 什么场景适合单元测试 单元测试的粒度 关于TDD TDD的三定律 ...

  2. 系统稳定型建设之单元测试Spock落地

    文章目录 背景 项目紧,没时间就不用写单测了吗 单元测试与集成测试的区别 单元测试可以带来的好处 目前主流的mock(单元测试)框架 实际调研过程 为什么不用最流行的mockito Groovy简单培 ...

  3. Spock测试框架浅尝

    文章目录 0. 写在前面 1. 为什么要进行单元测试? 1.1 什么是单元测试? 1.2 单元测试的几个好处 2. Spock框架介绍 2.1 规范 Specification 夹具方法 Fixtur ...

  4. Spock测试框架如何Mock静态方法

    1. 问题场景 在写单元测试时,难免会遇到需要Mock的静态方法.当使用Spock测试框架时,Spock提供Moc静态方法Mock只支持Groovy语言,无法支持Java语言实现的静态方法Mockin ...

  5. @sql 单元测试_SQL单元测试:使用异常

    @sql 单元测试 With this article, we will complete our journey with SQL Unit Testing. But first, let's re ...

  6. @sql 单元测试_SQL单元测试最佳实践

    @sql 单元测试 SQL unit testing is a testing method which allows us to test the smallest, atomic programm ...

  7. AlexeyAB DarkNet YOLOv3框架解析与应用实践(六)

    AlexeyAB DarkNet YOLOv3框架解析与应用实践(六) Tiny Darknet 听过很多人谈论SqueezeNet. SqueezeNet很酷,但它只是优化参数计数.当大多数高质量的 ...

  8. AlexeyAB DarkNet YOLOv3框架解析与应用实践(五)

    AlexeyAB DarkNet YOLOv3框架解析与应用实践(五) RNNs in Darknet 递归神经网络是表示随时间变化的数据的强大模型.为了更好地介绍RNNs,我强烈推荐Andrej K ...

  9. AlexeyAB DarkNet YOLOv3框架解析与应用实践(四)

    AlexeyAB DarkNet YOLOv3框架解析与应用实践(四) Nightmare 从前,在一所大学的大楼里,西蒙尼亚.维达第和齐瑟曼有一个很好的主意,几乎和你现在坐的大楼完全不同.他们想,嘿 ...

  10. AlexeyAB DarkNet YOLOv3框架解析与应用实践(三)

    AlexeyAB DarkNet YOLOv3框架解析与应用实践(三) ImageNet分类 您可以使用Darknet为1000级ImageNet挑战赛分类图像.如果你还没有安装Darknet,你应该 ...

最新文章

  1. ADPLUS使用配置文件设置断点时无法创建DUMP文件原因分析
  2. CentOS 7 网络连接优先由与无线问题解决
  3. OCP考试052考试,新的考题还有答案整理-23题
  4. 谈谈2018年区块链大事件
  5. 判断一段文件是UTF-8编码还是GB2312的编码方式
  6. java 同步锁_Java多线程:synchronized同步锁的使用和实现原理
  7. 尝试做一个简单的文件系统
  8. 【Allwinner】---全志GPIO号 计算 最新最详细
  9. 最好用电脑录音软件推荐
  10. Ubuntu 16.04 双网卡 同时上内外网
  11. html视频全套教程,用Dreamweaver讲解html全套视频教程分享
  12. bat脚本删除文件夹下的重复文件
  13. 如何用Java读取单元格的数据_Java读取Excel中的单元格数据
  14. python安装 Autodesk FBX 包
  15. 宝贝快出生的这三个表现,孕妈尽快去医院待产
  16. antd Mobile Tabs 垂直布局 后台传入数据
  17. 【2020版冲刺年薪30W】超全大数据学习路线+思维导图
  18. android全景设置高度,Android全景SDK | 百度地图API SDK
  19. 机器学习----纯手撸线性回归代码
  20. android实现mysql数据库存储_一个简单的Android端对象代理数据库系统的实现(二、执行+存储)...

热门文章

  1. VS 2010 设置堆栈大小
  2. Boke光纤交换机的snmp配置
  3. 微信模板消息推送成功 但没有内容
  4. SurfaceView 黑屏
  5. 攻防世界-misc-warmup题解
  6. 变动率指标(ROC)
  7. 欧拉距离,曼哈顿距离与明可夫斯基距离探讨
  8. 衡量CPU的三个指标:CPU Utilization、Load Average和Context Switch Rate
  9. λ动力学可自动、准确和扩展的计算蛋白与配体的相对结合自由能
  10. ios GPS 精度