Оригинальный адрес:Серия галантереи Spring Boot: (12) Spring Boot использует модульное тестирование
адрес блога:tengj.top/
предисловие
На этот раз я введем интеграцию и использование тестирования подразделения в весенней ботинке. Эта статья введет следующие 4 очка, которые в основном соответствуют ежедневному потребностям
- Тест на сервисный слой
- Модульный тест уровня контроллера
- Новое утверждение assertThat использует
- Откат юнит-тестов
текст
Ввести модульные тесты в Spring Boot очень просто, а зависимости следующие:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
Версия Spring Boot этого примера — 1.5.9.RELEASE, После введения spring-boot-starter-test появились следующие библиотеки:
• JUnit — стандарт де-факто для модульного тестирования Java-приложений.
• Spring Test и Spring Boot Test — Утилиты и поддержка интеграционных тестов для приложений Spring Boot.
• AssertJ — Свободная библиотека утверждений.
• Hamcrest — библиотека объектов сопоставления (также известных как ограничения или предикаты).
• Mockito — фреймворк для имитации Java.
• JSONassert — библиотека утверждений для JSON.
• JsonPath — XPath для JSON.
Тест сервисного модуля
Классы юнит-тестов в Spring Boot записываются в каталог src/test/java. Вы можете вручную создавать определенные тестовые классы. Если это IDEA, вы можете автоматически создавать тестовые классы через IDEA, как показано на рисунке ниже, или вы можете использовать горячие клавиши⇧⌘T
(MAC) илиCtrl+Shift+T
(Окно) создать следующим образом:
Тестовые классы автоматически генерируются следующим образом:
Затем напишите созданный тестовый класс, конкретный код выглядит следующим образом:
package com.dudu.service;
import com.dudu.domain.LearnResource;
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.CoreMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnServiceTest {
@Autowired
private LearnService learnService;
@Test
public void getLearn(){
LearnResource learnResource=learnService.selectByKey(1001L);
Assert.assertThat(learnResource.getAuthor(),is("嘟嘟MD独立博客"));
}
}
Выше приведен самый простой метод написания юнит-тестов, нужно только верхнюю часть@RunWith(SpringRunner.class)
иSpringBootTest
То есть, когда вы хотите его выполнить, наведите указатель мыши на соответствующий метод, щелкните правой кнопкой мыши и выберите «Запустить метод».
В тестовом случае я использовал утверждение assertThat, которое будет представлено ниже, и вам также рекомендуется использовать это утверждение.
Модульный тест контроллера
Вышеупомянутое предназначено только для тестирования уровня службы, но иногда вам нужно протестировать уровень контроллера (API), тогда вы должны использовать MockMvc, вы можете протестировать эти интерфейсы, не запуская проект.
MockMvc реализует моделирование запросов Http и может напрямую использовать форму сети для преобразования в вызовы контроллера, что может ускорить тестирование, независимо от сетевой среды, и предоставить набор инструментов проверки, которые могут сделать проверку запросов унифицировано.И это очень удобно.
Класс контроллера:
package com.dudu.controller;
/** 教程页面
* Created by tengj on 2017/3/13.
*/
@Controller
@RequestMapping("/learn")
public class LearnController extends AbstractController{
@Autowired
private LearnService learnService;
private Logger logger = LoggerFactory.getLogger(this.getClass());
@RequestMapping("")
public String learn(Model model){
model.addAttribute("ctx", getContextPath()+"/");
return "learn-resource";
}
/**
* 查询教程列表
* @param page
* @return
*/
@RequestMapping(value = "/queryLeanList",method = RequestMethod.POST)
@ResponseBody
public AjaxObject queryLearnList(Page<LeanQueryLeanListReq> page){
List<LearnResource> learnList=learnService.queryLearnResouceList(page);
PageInfo<LearnResource> pageInfo =new PageInfo<LearnResource>(learnList);
return AjaxObject.ok().put("page", pageInfo);
}
/**
* 新添教程
* @param learn
*/
@RequestMapping(value = "/add",method = RequestMethod.POST)
@ResponseBody
public AjaxObject addLearn(@RequestBody LearnResource learn){
learnService.save(learn);
return AjaxObject.ok();
}
/**
* 修改教程
* @param learn
*/
@RequestMapping(value = "/update",method = RequestMethod.POST)
@ResponseBody
public AjaxObject updateLearn(@RequestBody LearnResource learn){
learnService.updateNotNull(learn);
return AjaxObject.ok();
}
/**
* 删除教程
* @param ids
*/
@RequestMapping(value="/delete",method = RequestMethod.POST)
@ResponseBody
public AjaxObject deleteLearn(@RequestBody Long[] ids){
learnService.deleteBatch(ids);
return AjaxObject.ok();
}
/**
* 获取教程
* @param id
*/
@RequestMapping(value="/resource/{id}",method = RequestMethod.GET)
@ResponseBody
public LearnResource qryLearn(@PathVariable(value = "id") Long id){
LearnResource lean= learnService.selectByKey(id);
return lean;
}
}
Здесь мы также автоматически создаем тестовый класс Controller, конкретный код выглядит следующим образом:
package com.dudu.controller;
import com.dudu.domain.User;
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.mock.web.MockHttpSession;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
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;
@RunWith(SpringRunner.class)
@SpringBootTest
public class LearnControllerTest {
@Autowired
private WebApplicationContext wac;
private MockMvc mvc;
private MockHttpSession session;
@Before
public void setupMockMvc(){
mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
session = new MockHttpSession();
User user =new User("root","root");
session.setAttribute("user",user); //拦截器那边会判断用户是否登录,所以这里注入一个用户
}
/**
* 新增教程测试用例
* @throws Exception
*/
@Test
public void addLearn() throws Exception{
String json="{\"author\":\"HAHAHAA\",\"title\":\"Spring\",\"url\":\"http://tengj.top/\"}";
mvc.perform(MockMvcRequestBuilders.post("/learn/add")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes()) //传json参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
/**
* 获取教程测试用例
* @throws Exception
*/
@Test
public void qryLearn() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
.andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
.andDo(MockMvcResultHandlers.print());
}
/**
* 修改教程测试用例
* @throws Exception
*/
@Test
public void updateLearn() throws Exception{
String json="{\"author\":\"测试修改\",\"id\":1031,\"title\":\"Spring Boot干货系列\",\"url\":\"http://tengj.top/\"}";
mvc.perform(MockMvcRequestBuilders.post("/learn/update")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes())//传json参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
/**
* 删除教程测试用例
* @throws Exception
*/
@Test
public void deleteLearn() throws Exception{
String json="[1031]";
mvc.perform(MockMvcRequestBuilders.post("/learn/delete")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(json.getBytes())//传json参数
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andDo(MockMvcResultHandlers.print());
}
}
Основные тестовые примеры добавления, удаления, изменения и проверки реализованы выше.При использовании MockMvc вам необходимо использовать MockMvcBuilders для сборки объектов MockMvc следующим образом.
@Before
public void setupMockMvc(){
mvc = MockMvcBuilders.webAppContextSetup(wac).build(); //初始化MockMvc对象
session = new MockHttpSession();
User user =new User("root","root");
session.setAttribute("user",user); //拦截器那边会判断用户是否登录,所以这里注入一个用户
}
Поскольку перехватчик будет решать, следует ли войти в систему, поэтому здесь я внедрил пользователя, вы также можете напрямую изменить перехватчик, чтобы отменить проверку входа пользователя, а затем открыть его после тестирования.
Вот пример, чтобы представить простой метод MockMvc
/**
* 获取教程测试用例
* @throws Exception
*/
@Test
public void qryLearn() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/learn/resource/1001")
.contentType(MediaType.APPLICATION_JSON_UTF8)
.accept(MediaType.APPLICATION_JSON_UTF8)
.session(session)
)
.andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(MockMvcResultMatchers.jsonPath("$.author").value("嘟嘟MD独立博客"))
.andExpect(MockMvcResultMatchers.jsonPath("$.title").value("Spring Boot干货系列"))
.andDo(MockMvcResultHandlers.print());
}
- mockMvc.perform выполняет запрос
- MockMvcRequestBuilders.get("/user/1") создает запрос, а запрос Post использует метод .post
- contentType(MediaType.APPLICATION_JSON_UTF8) означает, что формат данных, отправляемых отправителем,
application/json;charset=UTF-8
- accept(MediaType.APPLICATION_JSON_UTF8) представляет тип данных, который клиент хочет принять как
application/json;charset=UTF-8
- session(session) внедряет сеанс, чтобы перехватчик мог пройти
- ResultActions.andExpect добавляет утверждение после завершения выполнения.
- Метод ResultActions.andExpect(MockMvcResultMatchers.status().isOk()), чтобы узнать, равен ли код ответа статуса запроса 200, если нет, выдать исключение, и тест не пройден.
- andExpect(MockMvcResultMatchers.jsonPath("$.author").value("Независимый блог Dudu MD")) Здесь jsonPath используется, чтобы узнать, является ли сравнение полей автора
嘟嘟MD独立博客
, иначе тест не пройден - ResultActions.andDo добавляет обработчик результата, указывающий, что вы хотите что-то сделать с результатом, например, используйте здесь MockMvcResultHandlers.print() для вывода всей информации о результате ответа.
Этот пример проверяется следующим образом:
Дополнительные примеры mockMvc можно найти внизу этой статьи.
Новое утверждение assertThat использует
JUnit 4.4 в сочетании с Hamcrest предоставляет новый синтаксис утверждений — assertThat. Программисты могут использовать только один оператор утверждения assertThat в сочетании с сопоставлением, предоставленным Hamcrest, для выражения всех тестовых идей. Мы представили версию Junit4.12, поэтому она поддерживает assertThat.
Основной синтаксис assertThat следующий:
Листинг 1. Базовый синтаксис assertThat
assertThat( [value], [matcher statement] );
- value — это значение переменной, которую вы хотите протестировать следующей;
- Оператор сопоставления — это выражение ожидаемого значения предыдущей переменной, выраженное с помощью сопоставления Hamcrest.Если значение совпадает с ожидаемым значением, выраженным оператором сопоставления, тест завершается успешно, в противном случае тест завершается неудачно.
Преимущества assertThat
- Преимущество 1: в прошлом JUnit предоставлял множество операторов утверждений, таких как: assertEquals, assertNotSame, assertFalse, assertTrue, assertNotNull, assertNull и т. д. Теперь с JUnit 4.4 один assertThat может заменить все операторы утверждений, которые можно использовать в все модульные тесты.В тестовом примере используется только один метод утверждения, что упрощает написание тестовых случаев, стиль кода становится унифицированным, а тестовый код легче поддерживать.
- Про Как показано в листинге 2:
Листинг 2. Сравнение между использованием сопоставителя Matcher и его отсутствием
// 想判断某个字符串 s 是否含有子字符串 "developer" 或 "Works" 中间的一个
// JUnit 4.4 以前的版本:assertTrue(s.indexOf("developer")>-1||s.indexOf("Works")>-1 );
// JUnit 4.4:
assertThat(s, anyOf(containsString("developer"), containsString("Works")));
// 匹配符 anyOf 表示任何一个条件满足则成立,类似于逻辑或 "||", 匹配符 containsString 表示是否含有参数子
// 字符串,文章接下来会对匹配符进行具体介绍
-
Преимущество 3: assertThat больше не использует более сложный синтаксический шаблон «предикат-объект» (например, assertEquals(3, x);), как и assertEquals, напротив, assertThat использует легко читаемый шаблон «субъект-предикат-объект». синтаксический шаблон (например: assertThat(x,is(3));), что делает код более интуитивным и читабельным.
-
Преимущество 4: Эти матчеры можно комбинировать и гибко использовать для достижения большего количества целей. Как показано в листинге 3:
Листинг 3. Использование комбинации Matcher
// 联合匹配符not和equalTo表示“不等于”
assertThat( something, not( equalTo( "developer" ) ) );
// 联合匹配符not和containsString表示“不包含子字符串”
assertThat( something, not( containsString( "Works" ) ) );
// 联合匹配符anyOf和containsString表示“包含任何一个子字符串”
assertThat(something, anyOf(containsString("developer"), containsString("Works")));
- Преимущество 5: сообщения об ошибках более понятны, читабельны и описательны. Версии JUnit до 4.4 по умолчанию не будут выдавать дополнительные запросы, например:
assertTrue( s.indexOf("developer") > -1 || s.indexOf("Works") > -1 );
Если утверждение не выполняется, выдается только бесполезная информация об ошибке, например: junit.framework.AssertionFailedError: null. Если вы хотите распечатать некоторую полезную подсказку при возникновении ошибки, вы должны вручную написать ее программистом, например:
assertTrue( "Expected a string containing 'developer' or 'Works'",
s.indexOf("developer") > -1 || s.indexOf("Works") > -1 );
Очень неудобно и требует дополнительного кода. JUnit 4.4 по умолчанию автоматически предоставляет некоторые удобочитаемые описания, как показано в листинге 4: Листинг 4. JUnit 4.4 по умолчанию предоставляет несколько удобочитаемых описательных сообщений об ошибках.
String s = "hello world!";
assertThat( s, anyOf( containsString("developer"), containsString("Works") ) );
// 如果出错后,系统会自动抛出以下提示信息:
java.lang.AssertionError:
Expected: (a string containing "developer" or a string containing "Works")
got: "hello world!"
Как использовать assertThat
JUnit 4.4 поставляется с некоторыми сопоставителями Hamcrest, но только некоторые из них определены в классе org.hamcrest.CoreMatchers.Чтобы использовать их, вы должны импортировать пакет org.hamcrest.CoreMatchers.*.
В листинге 5 перечислены большинство примеров использования assertThat :
字符相关匹配符
/**equalTo匹配符断言被测的testedValue等于expectedValue,
* equalTo可以断言数值之间,字符串之间和对象之间是否相等,相当于Object的equals方法
*/
assertThat(testedValue, equalTo(expectedValue));
/**equalToIgnoringCase匹配符断言被测的字符串testedString
*在忽略大小写的情况下等于expectedString
*/
assertThat(testedString, equalToIgnoringCase(expectedString));
/**equalToIgnoringWhiteSpace匹配符断言被测的字符串testedString
*在忽略头尾的任意个空格的情况下等于expectedString,
*注意:字符串中的空格不能被忽略
*/
assertThat(testedString, equalToIgnoringWhiteSpace(expectedString);
/**containsString匹配符断言被测的字符串testedString包含子字符串subString**/
assertThat(testedString, containsString(subString) );
/**endsWith匹配符断言被测的字符串testedString以子字符串suffix结尾*/
assertThat(testedString, endsWith(suffix));
/**startsWith匹配符断言被测的字符串testedString以子字符串prefix开始*/
assertThat(testedString, startsWith(prefix));
一般匹配符
/**nullValue()匹配符断言被测object的值为null*/
assertThat(object,nullValue());
/**notNullValue()匹配符断言被测object的值不为null*/
assertThat(object,notNullValue());
/**is匹配符断言被测的object等于后面给出匹配表达式*/
assertThat(testedString, is(equalTo(expectedValue)));
/**is匹配符简写应用之一,is(equalTo(x))的简写,断言testedValue等于expectedValue*/
assertThat(testedValue, is(expectedValue));
/**is匹配符简写应用之二,is(instanceOf(SomeClass.class))的简写,
*断言testedObject为Cheddar的实例
*/
assertThat(testedObject, is(Cheddar.class));
/**not匹配符和is匹配符正好相反,断言被测的object不等于后面给出的object*/
assertThat(testedString, not(expectedString));
/**allOf匹配符断言符合所有条件,相当于“与”(&&)*/
assertThat(testedNumber, allOf( greaterThan(8), lessThan(16) ) );
/**anyOf匹配符断言符合条件之一,相当于“或”(||)*/
assertThat(testedNumber, anyOf( greaterThan(16), lessThan(8) ) );
数值相关匹配符
/**closeTo匹配符断言被测的浮点型数testedDouble在20.0¡À0.5范围之内*/
assertThat(testedDouble, closeTo( 20.0, 0.5 ));
/**greaterThan匹配符断言被测的数值testedNumber大于16.0*/
assertThat(testedNumber, greaterThan(16.0));
/** lessThan匹配符断言被测的数值testedNumber小于16.0*/
assertThat(testedNumber, lessThan (16.0));
/** greaterThanOrEqualTo匹配符断言被测的数值testedNumber大于等于16.0*/
assertThat(testedNumber, greaterThanOrEqualTo (16.0));
/** lessThanOrEqualTo匹配符断言被测的testedNumber小于等于16.0*/
assertThat(testedNumber, lessThanOrEqualTo (16.0));
集合相关匹配符
/**hasEntry匹配符断言被测的Map对象mapObject含有一个键值为"key"对应元素值为"value"的Entry项*/
assertThat(mapObject, hasEntry("key", "value" ) );
/**hasItem匹配符表明被测的迭代对象iterableObject含有元素element项则测试通过*/
assertThat(iterableObject, hasItem (element));
/** hasKey匹配符断言被测的Map对象mapObject含有键值“key”*/
assertThat(mapObject, hasKey ("key"));
/** hasValue匹配符断言被测的Map对象mapObject含有元素值value*/
assertThat(mapObject, hasValue(value));
откат модульного теста
Если вы не хотите создавать мусорные данные во время модульного тестирования, вы можете включить функцию транзакции и добавить ее в заголовок метода или класса.@Transactional
Аннотируйте его следующим образом:
@Test
@Transactional
public void add(){
LearnResource bean = new LearnResource();
bean.setAuthor("测试回滚");
bean.setTitle("回滚用例");
bean.setUrl("http://tengj.top");
learnService.save(bean);
}
Таким образом, данные будут откатываться после теста и не будут вызывать мусорные данные. Если вы хотите отключить откат, просто добавьте@Rollback(false)
Просто аннотируйте.@Rollback
Указывает, что транзакция откатывается после выполнения. Можно передать значение параметра. Значение по умолчанию — true, чтобы откатить, и false, чтобы не откатывать.
Если вы используете базу данных Mysql, иногда вы обнаружите, что аннотации добавляются@Transactional
Откатиться не получится, тогда нужно проверить, является ли ваш движок по умолчанию InnoDB, если нет, смените на InnoDB.
MyISAM и InnoDB в настоящее время являются двумя широко используемыми механизмами хранения баз данных для mysql.Основное различие между MyISAM и InnoDB заключается в производительности и управлении транзакциями. Вот краткое введение в разницу и метод преобразования между ними:
-
MyISAM: MyISAM является механизмом хранения базы данных по умолчанию для версий до MySQL 5.5. MYISAM обеспечивает высокоскоростное хранение и поиск, а также возможности полнотекстового поиска, подходящие для таких приложений, как хранилища данных с частыми запросами. Но нет поддержки ни транзакций, ни внешних ключей. Важным недостатком формата MyISAM является невозможность восстановления данных после повреждения таблицы.
-
InnoDB: InnoDB является механизмом хранения базы данных по умолчанию в MySQL 5.5, но InnoDB был приобретен Oracle, а новый механизм хранения Falcon, разработанный MySQL, будет представлен в MySQL 6.0. InnoDB безопасен для транзакций благодаря фиксации, откату и отказоустойчивости. Но по сравнению с механизмом хранения MyISAM записи InnoDB менее эффективны и занимают больше места на диске для хранения данных и индексов. Тем не менее, InnoDB включает поддержку транзакций и внешних ключей, которых нет в движке MyISAM.
-
MyISAM подходит для: (1) выполнения большого количества вычислений; (2) нечастой вставки и очень частого запроса; (3) отсутствия транзакций.
-
InnoDB подходит для: (1) относительно высоких требований к надежности или необходимости транзакций; (2) обновления таблиц и запросы происходят довольно часто, а вероятность блокировки таблицы относительно велика. (4) Для серверов с более высокой производительностью, таких как отдельные серверы баз данных, такие как реляционная база данных RDS Alibaba Cloud, рекомендуется использовать механизм InnoDB.
Чтобы изменить двигатель по умолчанию
Просмотрите текущий механизм хранения MySQL по умолчанию:
mysql> show variables like '%storage_engine%';
Вам нужно посмотреть, какой движок использует пользовательская таблица (в результате отображения параметр после движка указывает на движок хранилища, используемый в данный момент таблицей):
mysql> show create table user;
Измените пользовательскую таблицу на механизм хранения InnoDB (вы также можете использовать эту команду, чтобы заменить InnoDB на MyISAM):
mysql> ALTER TABLE user ENGINE=INNODB;
Если вы хотите изменить механизм хранения всей таблицы базы данных, то в общем случае необходимо изменить одну таблицу на одну таблицу, что довольно громоздко.Можно сначала экспортировать базу данных, получить SQL, заменить все MyISAM на INNODB, а затем импортировать базу данных. Перезапустите mysql после завершения преобразования
service mysqld restart
Суммировать
На данный момент модульный тест интеграции Spring Boot в основном завершен, и вы можете продолжить подробное изучение использования MockMvc и assertThat. В будущем пользовательский интерфейс Swagger будет интегрирован как инструмент документации API, который предоставляет как документацию API, так и интерфейс тестового интерфейса, который довольно прост в использовании.
Чтобы увидеть больше руководств по галантерейным товарам Spring Boot, перейдите по ссылке:Общий обзор серии галантерейных товаров Spring Boot
Ссылаться на
Пятое примечание к исследованию Junit: MockMVC Узнайте, что нового в JUnit 4.4
# Загрузка исходного кода ( ̄︶ ̄)↗[Связанный пример полного кода] - Chapter12=="Серия галантереи Spring Boot: (12) Spring Boot использует модульное тестирование
Я всегда чувствую, что пишу не о технике, а о чувствах, и каждая статья — это след моего пути. Успех опоры на профессиональные навыки является наиболее воспроизводимым.Я надеюсь, что мой путь поможет вам избежать окольных путей.Я надеюсь, что смогу помочь вам стереть пыль знаний.Я надеюсь, что смогу помочь вам прояснить контекст знаний.Я надеюсь, что в будущем Мы с тобой на вершине технологий.