单元测试(unit test)是为了检验程序的正确性。一个单元可能是单个程序、类、对象、方法 等,它是应用程序的最小可测试部件。
单元测试的必要性如下:
Spring Boot提供了 spring-boot-starter-test启动器。通过它,能引入一些有用的测试库, 如下所示。
JUnit是对程序代码进行单元测试的Java框架。它用来编写自动化测试工具,降低测试的难度、 减少烦琐性,并有效避免岀现程序错误。
JUnit测试是白盒测试(因为知道测试如何完成功能和完成什么样的功能)。要使用JUnit,则 只需要继承TestCase类。
JUnit提供以下注解。
Unit 4.4结合Hamcrest提供了一个新的断言语法 --------- assertThat。使用assertThat的一个断言语句结合Hamcrest提供的匹配符,就可以表达全部的测试思想。
assertThat( [value], [matcher statement] )
成功,否则失败。简单地说,就是“两个值逬行比较”
assertThat(testNumber,allof(greaterThan(5),lessThan(8))):allOf 表示,所有条件必须都成立,测试才能通过
assertThat(testNumber,anyOf(greaterThan(5),lessThan(8))):anyOf 表示,所有 条件只要有一个成立,则测试通过G
assertThat(testNumber,anything()):anything表示,无论什么条件,结果永远为“true”
assertThat(testNumber,is("buretuzi")):is 表示,如果前面待测的 testString 等于后面给出的String,则测试通过。
assertThat(testNumber,not("buretuzi")): not 表示,如果前面待测的 String 不等于后面给出的String,则测试通过。
assertThat(testNumber,containsString("buretuzi")):containsString 表示,如果测试的字符串teststring 子字符串 "buretuzi" ,则测试通过
assertThat(testNumber,endWith("zi")):endsWith 表示,如果测试的字符串 teststring以子字符串 "zi" 结尾,则测试通过。
assertThat(testNumber,startWith("bu")):starts With 表示,如果测试的字符串 teststring以子字符串“bu”开始,则测试通过。
assertThat(testNumber,equalTo("bu")):equalTo 表示,如果测试的 testValue 等于Value,则测试通过。equalTo可以用来测试数值、字符串和对象。
assertThat(testNumber,equalToIgnoreCase("bu")):equalToIgnoringCase 表示, 如果测试的字符串teststring在忽略大小写的情况下等于“bu” ,则测试通过。
assertThat(testNumber,equalToIgnoreWhiteSpace("bu")):equalToIgnoring- WhiteSpace表示,如果测试的字符串teststring在忽略头尾的任意一个空格的情况下等于 “bu”,则测试通过。字符串中的空格不能被忽略。
assertThat(testDouble,closeTo(1.0,8.8)):closeTo 表示,如果测试的浮点型数 testDouble在1.0 ~8.8之间,则测试通过。
assertThat(testNumber,greaterThan(2.0)):greaterThan 表示,如果测试的数值 testNumber大于2.0,则测试通过
assertThat(testNumber,lessThan(35.0))):lessThan 表示,如果测试的数值 testNumber小于35.0,则测试通过
assertThat(testNumber,greaterThanOrEqualTo(3.0)):greaterThanOrEqualTo表示,如果测试的数值estNumber大于或等于3.0,则测试通过。
assertThat(testNumber,lessThanOrEqualTo(3.0)):lessThanOrEqualTo 表示, 如果测试的数值testNumber小于或等于3.0,则测试通过。
assertThat(mObject,hasEntry("key","value")):hasEntry 表示,如果测试的 Map 对象mObject含有一个键值为“key”对应元素值为“value”的Entry项,则测试通过
assertThat(mObject,hasKey("key")):hasKey 表示,如果测试的 Map 对象 mObject 含有键值“key”,则测试通过。
assertThat(mObject,hasValue("key")):hasVafue 表示,如果测试的 Map 对象 mObject含有元素值“value”,则测试通过。
assertThat(mObject,hasItem("bu")):hasltem 表示,如果测试的迭代对象 iterableObject含有元素“bu”项,则测试通过。
Mockito是GitHub上使用最广泛的Mocking框架。它提供简洁的API用来测试。Mockito简单易学、可读性强、验证语法简洁。
与JUnit结合使用,Mockito框架可以创建和配置Mock对象。
JSONPath是xPath在JSON中的应用。它的数据结构通常不一定有根元素,它用一个抽象的名字来表示最外层对象,而且允许使用通配符“*”表示所有的子元素名和数组素引。
JSONPath表达式可以使用符号解析JSON,如以下代码:
$.person.card[0].num
或使用符号,如以下代码:
$['person']['card'][0]['num']
在单元测试中可能会产生垃圾数据,可以开启事务功能进行回滚——在方法或类头部添加注解 @Transactional即可。用法见以下代码:
package com.itheima.repository;
import com.itheima.domain.Book;
import com.itheima.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class CardRepositoryTest {
@Autowired
private BookService bookService;
@Test
public void testRollback() {
Book book = new Book();
book.setUsername("username");
book.setPassword("password");
bookService.insert(book);
}
}
上述代码在类上添加了注解@Transactional,测试完成后就会回滚,不会产生垃圾数据。如果要关闭回滚,则只要加上注解@Rollback(false)即可
在Spring Boot中进行单元测试很简单,它已经自动添加好了 Test的Starter依赖,见下方依赖元素:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
创建一个测试类:
package com.itheima;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
public class MyTest {
@Test
public void test() throws Exception {
}
}
代码解释如下。
除用这种方式创建测试单元外,还可以通过IDEA的快捷键快速完成创建。
在IDEA中,快速创建测试单元主要有以下3种方式:
package com.itheima.controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class JunitController {
@RequestMapping("/hello")
public String hello(String name){
return "hello " + name;
}
}
代码解释如下:
package com.itheima;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
@SpringBootTest
@RunWith(SpringRunner.class)
public class HelloControllerTest {
//启用web上下文
@Autowired
private WebApplicationContext webApplicationContext;
private MockMvc mockMvc;
@Before
public void setUp() throws Exception {
//使用上下文构建MockMvc
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void testHello() throws Exception {
//得到MvcResult自定义验证,执行请求
MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/hello")
//发送post请求
.contentType(MediaType.APPLICATION_JSON_UTF8)
//传入参数
.param("name","buretuzi")
//接收的类型
.accept(MediaType.APPLICATION_JSON_UTF8))
//判断接收到的状态是否是200,等同于Assert.assertEquals(200, status);
.andExpect(MockMvcResultMatchers.status().isOk())
//等同于Assert.assertEquals("hello buretuzi",content);
.andExpect(MockMvcResultMatchers.content().string("hello buretuzi"))
.andDo(MockMvcResultHandlers.print())
//返回MockMvc
.andReturn();
int status = mvcResult.getResponse().getStatus();
String content = mvcResult.getResponse().getContentAsString();
Assert.assertEquals(200, status);
Assert.assertEquals("hello buretuzi",content);
}
}
代码解释如下:
运行测试,在控制器中会输出以下结果:
MockHttpServletRequest:
HTTP Method = GET
Request URI = /hello
Parameters = {name=[buretuzi]}
Headers = [Content-Type:"application/json;charset=UTF-8", Accept:"application/json;charset=UTF-8"]
Body = null
Session Attrs = {}
Handler:
Type = com.itheima.controller.JunitController
Method = com.itheima.controller.JunitController#hello(String)
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = [Content-Type:"application/json;charset=UTF-8", Content-Length:"14"]
Content type = application/json;charset=UTF-8
Body = hello buretuzi
Forwarded URL = null
Redirected URL = null
Cookies = []
package com.itheima.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private long id;
private String name;
private int age;
}
这里用@Service来标注服务类,并实例化了一个User对象,见以下代码。
package com.itheima.service;
import com.itheima.domain.User;
import org.springframework.stereotype.Service;
@Service
public class UserService {
public User getUser(){
User user = new User();
user.setId(1);
user.setAge(13);
user.setName("八嘎");
return user;
}
}
编写测试用于比较实例化的实体User和测试预期值是否一样,见以下代码:
package com.itheima;
import com.itheima.domain.User;
import com.itheima.service.UserService;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import static org.hamcrest.Matchers.is;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void getUserinfo() {
User user = userService.getUser();
Assert.assertEquals(13,user.getAge());
Assert.assertThat(user.getName(), is("嘎"));
}
}
运行测试,结果显示出错,表示期望的值和实际值不一样,如下所示。
Expected: is "嘎"
but: was "八嘎"
package com.itheima;
import com.itheima.domain.Book;
import com.itheima.service.BookService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class BookRepositoryTest {
@Autowired
private BookService bookService;
@Test
public void testQuery(){
List<Book> books = (List<Book>) bookService.findAll();
for (Book book : books) {
System.out.println(book.getUsername());
}
}
@Test
public void testRollback(){
Book book = new Book();
book.setUsername("干净又卫生");
bookService.insert(book);
}
}
代码解释如下。
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当
我有一个围绕一些对象的包装类,我想将这些对象用作散列中的键。包装对象和解包装对象应映射到相同的键。一个简单的例子是这样的:classAattr_reader:xdefinitialize(inner)@inner=innerenddefx;@inner.x;enddef==(other)@inner.x==other.xendenda=A.new(o)#oisjustanyobjectthatallowso.xb=A.new(o)h={a=>5}ph[a]#5ph[b]#nil,shouldbe5ph[o]#nil,shouldbe5我试过==、===、eq?并散列所有无济于事。
我有一些Ruby代码,如下所示:Something.createdo|x|x.foo=barend我想编写一个测试,它使用double代替block参数x,这样我就可以调用:x_double.should_receive(:foo).with("whatever").这可能吗? 最佳答案 specify'something'dox=doublex.should_receive(:foo=).with("whatever")Something.should_receive(:create).and_yield(x)#callthere
Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/
我遵循MichaelHartl的“RubyonRails教程:学习Web开发”,并创建了检查用户名和电子邮件长度有效性的测试(名称最多50个字符,电子邮件最多255个字符)。test/helpers/application_helper_test.rb的内容是:require'test_helper'classApplicationHelperTest在运行bundleexecraketest时,所有测试都通过了,但我看到以下消息在最后被标记为错误:ERROR["test_full_title_helper",ApplicationHelperTest,1.820016791]test
我已经构建了一些serverspec代码来在多个主机上运行一组测试。问题是当任何测试失败时,测试会在当前主机停止。即使测试失败,我也希望它继续在所有主机上运行。Rakefile:namespace:specdotask:all=>hosts.map{|h|'spec:'+h.split('.')[0]}hosts.eachdo|host|begindesc"Runserverspecto#{host}"RSpec::Core::RakeTask.new(host)do|t|ENV['TARGET_HOST']=hostt.pattern="spec/cfengine3/*_spec.r
我在app/helpers/sessions_helper.rb中有一个帮助程序文件,其中包含一个方法my_preference,它返回当前登录用户的首选项。我想在集成测试中访问该方法。例如,这样我就可以在测试中使用getuser_path(my_preference)。在其他帖子中,我读到这可以通过在测试文件中包含requiresessions_helper来实现,但我仍然收到错误NameError:undefinedlocalvariableormethod'my_preference'.我做错了什么?require'test_helper'require'sessions_hel
只是想确保我理解了事情。据我目前收集到的信息,Cucumber只是一个“包装器”,或者是一种通过将事物分类为功能和步骤来组织测试的好方法,其中实际的单元测试处于步骤阶段。它允许您根据事物的工作方式组织您的测试。对吗? 最佳答案 有点。它是一种组织测试的方式,但不仅如此。它的行为就像最初的Rails集成测试一样,但更易于使用。这里最大的好处是您的session在整个Scenario中保持透明。关于Cucumber的另一件事是您(应该)从使用您的代码的浏览器或客户端的角度进行测试。如果您愿意,您可以使用步骤来构建对象和设置状态,但通常您
我有:When/^(?:|I)follow"([^"]*)"(?:within"([^"]*)")?$/do|link,selector|with_scope(selector)doclick_link(link)endend我打电话的地方:Background:GivenIamanexistingadminuserWhenIfollow"CLIENTS"我的HTML是这样的:CLIENTS我一直收到这个错误:.F-.F--U-----U(::)failedsteps(::)nolinkwithtitle,idortext'CLIENTS'found(Capybara::Element