
文章目录
断言库包含很多,比如junit自带的、hamcrest等,这里推荐使用AssertJ,看它的官网就知道了,宣称fluent assertions java library。
AssertJ的断言采用assertThat(result)的形式,等同于then(result),这两种方式使用上没有区别;我们需要在pom中引入如下依赖:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.15.0</version>
<scope>test</scope>
</dependency>
下面列出常见的断言用法,其它的用法可以参考依赖库学习使用。
对文本的断言;
assertThat(result).isEqualTo("apple");
assertThat(result).isEqualToIgnoringCase("apple");
assertThat(result).contains("apple");
assertThat(result).containsIgnoringCase("apple");
assertThat(result).startsWith("apple");
assertThat(result).matches("^[A-Za-z0-9]{8}$");
assertThat(result).hasSize(10);
assertThat(result).containsSequence("a", "p", "l");
...
对数字的断言;
assertThat(result).isGreaterThanOrEqualTo(100);
assertThat(result).isCloseTo(100.0, Offset.offset(0.000001));
assertThat(result).isBetween(90.0, 91.0),
assertThat(result).isNaN();
...
对日期的断言;
assertThat(result).isAfter(startDate);
assertThat(result).isBefore("2020-01-01");
assertThat(result).isInSameMonthAs("2019-12-01");
...
对集合的断言;
assertThat(result).hasSize(3);
assertThat(result).contains("apple", "orange");
assertThat(result).doesNotcontain("apple", "orange");
assertThat(result).containsExactly("apple", "orange");
assertThat(result).startsWith("apple");
assertThat(result).endsWith("orange");
assertThat(result).doesNotContainNull();
assertThat(result).doesNotHaveDuplicates();
assertThat(result).isNotEmpty();
assertThat(result).isNullOrEmpty();
...
assertThat(result).hasSize(2);
assertThat(result).containsEntry("apple", "12");
assertThat(result).containsKeys("apple", "orange");
assertThat(result).containsOnlyKeys("apple", "orange");
assertThat(result).containsValues("apple", "orange");
...
对对象的断言;
assertThat(result).isEqualToComparingOnlyGivenFields(obj1, "name", "weight");
assertThat(result).isEqualToIgnoringGivenFields(obj1,"name", "weight");
assertThat(result).isEqualToIgnoringNullFields(obj1);
...
AssertJ-Core只适合为单元测试使用,如果要进行集成测试,或者只测试DAO层的SQL执行结果,就无能为力了,这是就需要用到AssertJ-DB,首先我们需要在pom中引入如下的依赖:
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-db</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>
下面是一些常用的功能:
数据源
如果我们想使用SpringBoot项目中配置的数据源,比如在application.properties中的数据库配置项:
spring.datasource.url=jdbc:postgresql://localhost:5432/mydb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true
spring.datasource.username=postgre
spring.datasource.password=postgre
spring.datasource.driver-class-name=org.postgresql.Driver
那么我们就需要在运行该单元测试的时候启动整个Spring Boot工程,首先需要先建立一个测试基类:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = DailyWorkServerApplication.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
@Transactional
@Rollback
public class BaseTest {
// 集成测试基类
// 如果使用maven运行测试用例,需要在maven-surefire-plugin插件中将本基类排除执行,否则会报错,因为没有测试用例
}
然后,我们的测试基类继承该测试基类:
public class SystemInfoDaoTest extends BaseTest {
// 获取系统数据源
@Autowired
private DataSource dataSource;
@Autowired
private PersonDao personDao;
@Test
public void getPersonCount() {
// 构造一个连接到数据源的Request,此处可以先略过,后面会有详细介绍
Request request = new Request(dataSource, "select count(1) from person where name = ?", "zhangsan");
// assertj-db执行如上Request中的SQL,对获取的数据进行断言
assertThat(request).row(0).column().value().isEqualTo(1);
}
}
如果你不想使用SpringBoot的数据源,需要自定义数据源,那么可以在测试类中这么写:
public class SystemInfoDaoTest extends BaseTest {
private Source dataSource;
@Autowired
private PersonDao personDao;
private static final String DB_URL = "jdbc:postgresql://localhost:5432/mydb?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true";
private static final String DB_USER_NAME = "postgre";
private static final String DB_PASSWORD = "postgre";
@Before
public void before(){
this.dataSource = new Source(DB_URL,DB_USER_NAME,DB_PASSWORD);
}
@Test
public void getUmCount() {
// 构造一个连接到数据源的Request,此处可以先略过,后面会有详细介绍
Request request = new Request(dataSource, "select count(1) from person where name = ?", "zhangsan");
// assertj-db执行如上Request中的SQL,对获取的数据进行断言
assertThat(request).row(0).column().value().isEqualTo(1);
}
}
当然,还可以使用其它类型的DataSource,详细信息可以参考文末关于AssertJ-DB的官网内容。
Table
当数据源连接上之后,我们可以使用如下的语句来代表某一张具体的表:
Table table = new Table(dateSource, "person");
Request
一个Request可以代表一个即将要执行的SQL请求:
Request request = new Request(dataSource, "select count(1) from person where name = ?", "zhangsan");
Row
Row是基于上面table和request的结果的某一行数据:
// 取当前表的第二行数据
table.row(1);
// 取当前请求的第4行数据,然后再跳到第11行数据
request.row(3).row(10);
Column
Column是基于上面table和request的结果的某一列数据:
// 取当前表的第二列数据
table.column(1);
// 取当前请求的第4列数据,然后再跳到第11列数据
request.column(3).column(10);
// 取当前请求的第2行数据,然后取当前行的第4列单元格
request.row(1).column(3);
Value
Value是基于Row或者Column的某一单元格中的值:
// 取当前请求的第2行数据,然后取当前行的第4列单元格的值
request.row(1).column(3).value();
总结下来,只有DAO层的对数据库的增、删、改操作才需要使用AssertJ-DB,而查询操作是不需要的,因为查询已经将数据加载到内存中,只要使用AssertJ-Core做断言比较即可。
关于这些常用功能的详细案例,可以参考文末的Assertj-DB文档。
PS:
实验表明,对于事务回滚控制的测试用例,assertJ-DB似乎并不能得到我们想要的结果。
如下案例中,测试用例是事务回滚的,但是使用JdbcTemplate可以得到正确的结果,但是使用assertJ-DB就不行了。只能针对非事务回滚的测试用例,assertJ-DB才能得到正确的结果。这个目前还不知道怎么解决,暂时只能用JdbcTemplate替代。
@Test
public void addSystemInfoTest(){
SystemUpdateDTO systemUpdateDTO = new SystemUpdateDTO();
systemUpdateDTO.setSysNameCN("测试-商品管理系统");
systemUpdateDTO.setSysNameEN("test-GMS");
// 测试DAO逻辑-插入一条数据
systemInfoDao.addSystemInfo(systemUpdateDTO);
String querySql = "select count(1) from dw_sys_info dsi";
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
Integer rows = jdbcTemplate.queryForObject(querySql,Integer.class);
// 1
System.out.println("总共有" + rows);
Request request = new Request(dataSource, querySql);
// 0
System.out.println("总共有" + request.getRow(0).getColumnValue(0).getValue());
}
Mock框架有很多,古老的JMock、社区活跃的Mockito、还有我们今天要介绍的主角JMockit。
Mock技术是为了隔离被测试方法依赖的外部变量,从而可以使得测试方法的表现只受被测试方法本身的逻辑影响。举个例子:
@Service("personService")
public class PersonServiceImpl implements PersonService{
@Autowired
private InvokeService invokeService;
@Override
public Integer getPersonCountBySchool(String school){
if(StringUtils.isEmpty(school)){
return 0;
}
// 调用关联方获取数据的数量
return invokeService.getPersonBySchool(school).size();
}
...
}
我们如果想测试getPersonCountBySchool能否正常返回数据的数量,我们不必真的去执行invokeService.getPersonBySchool(school)调用关联方,只要使用Mock技术,让其返回我们设定的值即可:
public class PersonServiceImplTest extends BaseTest {
@Tested
@Autowired
private PersonService personService;
@Test
public void testGetPersonCountBySchool(@Injectable InvokeService invokeService) {
// 准备数据
List<Person> personList = new ArrayList<>();
Person peter = new Person("东方高中");
Person jack = new Person("东方高中");
personList.add(peter);
personList.add(jack);
// 模拟录制
new Expectations(){
{
// 模拟调用关联方获取数据列表,无论入参是什么字符串,都返回上面准备好的列表
invokeService.getPersonBySchool(anyString);
result = personList;
}
};
// 重放
Integer personCount = personService.getPersonCountBySchool("华夏高中");
// 验证
assertThat(personCount).isEqualTo(2);
}
}
在这里,最重要的两个注解就是@Tested和@Injectable,前者代表需要测试的类,后者代表需要mock的对象。
JMockit支持mock一个类、mock一个对象实例、mock一个对象中的某个具体的方法,甚至还可以对传入的参数进行检查,更多细节请参考文末列举的JMockit的官方文档。
我们在运行单元测试的时候,为了满足调用参数的要求,不得不为参数对象设置值。比如,当参数对象为一个Person类的时候,倘若它的属性值不多,我们可以像上面的例子中一样,使用手动造数;但是,如果属性值很多,甚至中间还嵌套了其它对象怎么办?手动造数太繁琐了。
这些工具库的使用都非常简单,参考文末列出的官方文档看下即可。
我们在测试DAO层关于SQL的增删查改前,要先提供一批专供测试使用的假数据,一般有以下方式:
使用内存数据库
如果不希望测试用例的执行污染测试数据库,那么可以建立一个专为测试用例执行使用的内存数据库,缺点是,需要维护所有的建表语句。
使用数据库造数工具
可以使用DBFactory之类的造数工具,往测试数据库中提前准备数据,但是测试完成后删除数据是个问题。
测试用例使用事务回滚
好处是不会对测试数据库造成数据污染,但是需要在测试用例逻辑执行前,手动准备数据;
我们在如上的学习过程中,都是写完单元测试后直接运行了。倘若我们在提交代码前,要运行所有的单元测试该怎么操作呢?总不可能一个个地打开所有地测试类,都点击运行一遍吧。
这里介绍使用Maven的插件进行单元测试运行的集成操作。
首先,在pom文件中引入maven-surefire-plugin,版本选择最新版:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
</plugin>
其实,这就可以了,我们使用了默认配置,执行mvn生命周期的test,就可以运行src/test/java目录下的所有单元测试和集成测试了。
有时候我们编译工程并不像运行测试用例,那么可以增加如下配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<!--跳过执行测试用例-->
<skipTests>true</skipTests>
</configuration>
</plugin>
有些场景下,我们只想运行某一个/一类/一路径的测试用例,我们可以使用<include>来配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<!--只运行包含的测试-->
<includes>
<include>unit/**/*.java</include>
</includes>
</configuration>
</plugin>
如上配置表示,只运行src/test/java/unit路径下所有java结尾的测试类中的测试用例;
有时候,我们需要排除运行一些测试用例。比如,在编译阶段,我们只想快速地运行单元测试,而不要运行集成测试,那么就可以将集成测试所在的文件路径做如下的配置:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<!--需要排除的测试-->
<excludes>
<!--当不需要运行集成测试时添加如下文件内容-->
<exclude>integration/**/*.java</exclude>
</excludes>
<!--设置并发执行测试,节约时间-->
<parallel>methods</parallel>
<threadCount>10</threadCount>
</configuration>
</plugin>
前提是,我们所有的集成测试类都必须放在src/test/java/integration目录下。
有时候,项目中的单元测试和集成测试非常多,一次执行会耗时比较久,那么可以设置多线程来执行:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19</version>
<configuration>
<!--设置并发执行测试,节约时间-->
<parallel>methods</parallel>
<threadCount>10</threadCount>
</configuration>
需要注意的时,要确保各测试用例之间没有调用依赖,否则便不可使用多线程的方式。
如果仅靠上面引入的maven-surefire-plugin插件,那么你只能在控制台看到运行的测试报告,如果要跟别人分享,十分不方便。可以通过引入maven-surefire-report-plugin插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
<version>2.12.2</version>
<configuration>
<!--报告中是否显示成功率为100%的项目-->
<showSuccess>false</showSuccess>
</configuration>
</plugin>
执行其中的surefire-report:report命令,就可以重新运行所有测试用例,并在target/site目录下生成一个html测试报告。
如果想查看测试覆盖率,那么就需要引入另外一个插件cobertura-maven-plugin:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>cobertura-maven-plugin</artifactId>
<version>2.5.1</version>
</plugin>
执行其中的cobertura:cobertura命令,就可以重新运行所有测试用例,并在target/site/cobertura目录下生成一个静态站点文件,找到其中的index.html,打开就可以看到各个测试覆盖率数据了。
关于maven-surefire-plugin插件还有很多其它配置内容,可以参考文末引用自行阅读尝试。
@Test(expected=SomeException),当然还可以使用ExpectedException;我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
总的来说,我对ruby还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru
在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev