项目模块的命名以及目录结构
项目模块的命名以及目录结构
一、项目的命名规则
PS : 项目的基础包路径和项目的命名一致
1 . 服务接口 : 项目名称 + 逻辑层名称 + 模块名称 + interfaces
服务接口例子 : banniu.data.salecrm.interfaces
基础包路径就是 : banniu.data.salecrm.interfaces
服务接口后面的interfaces是固定的
2 . 提供者 : 项目名称 + 逻辑层名称 + 模块名称
提供者例子 : banniu.data.salecrm
1) 项目名称 : banniu
2) 逻辑层名称 : data (数据层)
3) 模块名称 : salecrm
基础包路径就是 : banniu.data.salecrm
PS : 逻辑层名称暂时是固定的 , 包含 data(数据层)、app(应用层)、web(控制层)
3 . 消费者 : banniu.web
1) 项目名称 : banniu
2) 逻辑层名称 : web (控制层)
基础包路径就是 : banniu.web
4 . 实体类包名命名规则 : 项目名称 + model + 模块名称
例子 : banniu.model.salecrm
PS : model是固定的 .
二、项目各逻辑层接口命名方式
1 . 数据层命名
interface命名 : 功能点名称 + DataService
实现类命名 : 功能点名称 + DataServiceImpl
2 . 应用层命名
interface命名 : 功能点名称 + AppService
实现类命名 : 功能点名称 + AppServiceImpl
3 . 控制层命名
controller命名 : 功能点名称 + Controller
4 . 举例说明
数据层例子 : TopicDataService --> Topic 是分享功能名称
应用层例子 : TopicAppService --> Topic 是分享功能名称
控制层例子 : TopicController --> Topic是分享功能名称
三、项目的目录结构
1. 服务接口
PS : 服务接口是一个jar包 , 被提供者和消费者共同依赖
2. 提供者
PS : 发布脚本放在服务器上面 .不在本文章里面写了
3. web层
单元测试
单元测试分为数据层 , 应用层 , 控制层 ; 每一层的测试都是独立的单元测试 , 不依赖其他项目 , 尽管逻辑上面是依赖的 . 对于数据层的测试 , 我们采用事务回滚的方式进行测试 , 这样就不会对数据库造成脏数据 ; 对于应用层和控制层 , 我们采用Mock的打桩方式进行单元测试 .
预准备
1 . 如何快速创建Test类
在IDEA里有一种快速创建的方式 , 只需要你找到你需要测试的类 , 然后把鼠标移到类名上 , 使用快捷键 Alt + Enter
快捷键 , 就可以看到 Create Test
的选项 . 点进去就可以创建你的测试类了 , 进入之后 Testing library 选择Junit 4 .
数据层测试
1 . 生成测试类之后 , 首先在你的测试类上面添加注解 :
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring-dao.xml") 注意 : @ContextConfiguration 括号里面的内容写的是你的本地dao层的配置文件
@Transactional() // 前提配置文件中已经配置了事务
@Rollback // 这个配置针对于所有回滚的 , 放在类上面表示类里面的方法都会回滚 . 也可以单独放在某一个方法上面
下面是一个真实的测试的代码 :
package banniu.data.salecrm.impl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.Rollback;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import banniu.data.salecrm.interfaces.CustomerLogsDataService;
import banniu.model.salecrm.CustomerLogsDo;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.*;
/**
* Created by Xstarfct on 2017/3/15.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring/spring-dao.xml")
//支持回滚 , 数据库表的存储引擎必须是支持事务的
@Transactional(transactionManager = "transactionManager")
@Rollback
public class CustomerLogsDataServiceImplTest {
@Autowired
private CustomerLogsDataService customerLogsDataService;
@Test
public void createCustomerLogs() throws Exception {
CustomerLogsDo logsDo = new CustomerLogsDo();
logsDo.setId(888L);
logsDo.setCompanyId(36L);
logsDo.setAfterUserId(111L);
logsDo.setContent("这是一条测试的日志");
logsDo.setLogType(0);
logsDo.setCustomerId(1111L);
logsDo.setUserId(111L);
logsDo.setReasonTagId(111L);
boolean createResult = customerLogsDataService.createCustomerLogs(logsDo);
System.out.println("创建结果:" + createResult);
logsDo = customerLogsDataService.loadCustomerLogsById(888L);
System.out.println(logsDo);
}
@Test
public void getPageListByCustomerLogsDo() throws Exception {
}
@Test
public void updateCustomerLogsById() throws Exception {
}
@Test
public void getLogsByFields() throws Exception {
Map<String,Object> map =new HashMap();
Date bD = null;
Date eD = null;
try {
bD = new SimpleDateFormat("yyyy-MM-dd").parse("2017-03-20");
map.put("beginDate", bD);
}catch (ParseException e)
{
System.out.println(e.getMessage());
}
try {
eD = new SimpleDateFormat("yyyy-MM-dd").parse("2017-03-22");
map.put("endDate", eD);
}catch (ParseException e)
{
System.out.println(e.getMessage());
}
map.put("beginDate", bD);
map.put("endDate", eD);
customerLogsDataService.getLogsByFields(map, 1, 10);
}
}
应用层测试
2 . 应用层的测试类是需要Mock进行打桩的 .
所谓的打桩就是模仿一个类 , 被模仿的类使用@Mock进行注解 ;
使用方法 : 比如现在有一个 CustomerAppServiceImpl
需要测试
1. 测试 getPageListByCustomer 方法 . 这个方法里面使用 CustomerDataService, 由于这个类是数据层的方法 , 所以我们要对它进行模仿 .
这个地方就要使用@Mock对它进行注解 , 然后在setUp方法里面使用构造方法将我们Mock好的对象传参到被测试的类中
2. 模仿了之后 ,我们需要对调用CustomerDataService的方法进行值的返回 , 保证 getPageListByCustomer 方法可以正常执行 . 我们使用when().thenReturn()方法进行模拟 . when里面是CustomerDataService调用方法的方式 , thenReturn里面返回一个符合的数据类型 .
3. 我们可以对返回的值进行assert ,这个方法是Junit提供的 .
代码展现 :
package banniu.app.salecrm.impl;
import banniu.data.common.interfaces.BanNiuGlobalIdService;
import banniu.data.salecrm.interfaces.CustomerDataService;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import banniu.app.salecrm.interfaces.CustomerAppService;
import banniu.app.salecrm.interfaces.TagsAppService;
import banniu.common.util.Pagination;
import banniu.model.salecrm.CustomerDo;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.when;
/**
* Created by Xstarfct on 2017/3/6.
* @author chuanshanjia
* @version 1.0
* @since 2017-03-06 14:41:34
*/
public class CustomerAppServiceImplTest {
private CustomerAppService customerAppService;
@Mock //对这个类进行模仿
private CustomerDataService customerDataService;
@Mock
private BanNiuGlobalIdService banNiuGlobalIdService;
/**
* 在所有test之前执行
*/
@Before
public void setUp() {
//初始化Mock
MockitoAnnotations.initMocks(this);
//对测试的service进行初始化 , 需要把Mock的对象传入到被测试的类中
customerAppService = new CustomerAppServiceImpl(customerDataService, banNiuGlobalIdService);
}
@Test
public void testPageList() {
//模仿方法的返回值
when(customerDataService.getPageListByCustomerDo(any(CustomerDo.class),anyInt(),anyInt())).thenReturn(null);
CustomerDo customerDo = new CustomerDo();
customerDo.setCompanyId(36L);
Pagination<CustomerDo> customerDos = customerAppService.getPageListByCustomer(customerDo, 1, 10);
//进行assert
assertNull(customerDos);
}
}
控制层测试
3 . 控制层的单元测试需要不仅仅需要Mock对象还要模拟http请求 .
1. 对被测试的类进行注解
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration //一定要加上 , 是为了模拟web服务器请求的
@ContextConfiguration(locations = {"classpath:spring/applicationContext.xml","classpath:dubbo/dubbo-consumer.xml"}) //加载spring的配置 , 和web.xml加载配置的原理一直
2. 在测试类里面添加MockMvc的类 . 这个类是用于模拟http请求 .
3. 在测试的方法中使用mockMvc.perform()方法 , 这个方法里面的参数包含设置accept , Content-Type , 以及使用GET , POST, PUT等方法 . 返回值是ResultActions
然后通过返回值是ResultActions 可以获取到请求的结果 , 如果出现错误还可以把错误打印出来 .
示例代码如下 :
package banniu.web.controller.salecrm;
import banniu.app.salecrm.interfaces.TagsAppService;
import banniu.common.util.Pagination;
import banniu.model.salecrm.TagsDo;
import banniu.web.utils.SalecrmConstant;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.RequestBuilder;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import wjb.model.user.UserDo;
import java.util.Date;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* @author leiniao
*/
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(locations = {"classpath:spring/applicationContext.xml","classpath:dubbo/dubbo-consumer.xml"})
public class TagControllerTest {
private MockMvc mockMvc;
private Gson gson = new Gson();
@Mock // 将需要调用的service Mock一下
private TagsAppService tagsAppService;
@InjectMocks //使mock对象的使用类可以注入mock对象,在上面这个例子中,mock对象是TagsAppService,使用了TagsAppService的是TagController,所以在TagController加上该Annotate;
private TagController tagController;
@Before
public void setUp() {
//初始化
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(tagController).build();
}
private TagsDo getMockTag() {
TagsDo tagsDo = new TagsDo();
tagsDo.setId(12121L);
tagsDo.setCompanyId(36L);
tagsDo.setGmtCreate(new Date(1489982400000L));
tagsDo.setStatus(1);
tagsDo.setTagKey("key");
tagsDo.setTagName("标签");
tagsDo.setTagPosition(3);
tagsDo.setTagType(4);
tagsDo.setUserId(729L);
return tagsDo;
}
private Pagination<TagsDo> getMockPagination() {
String json = "{\"total\":\"14\",\"pageSize\":10,\"pageNum\":1,\"results\":[{\"id\":\"149113492545002186\",\"tagId\":\"149093978058808048\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"有意向\",\"tagType\":1,\"tagKey\":\"youyixiang\",\"status\":0,\"tagPosition\":1,\"gmtCreate\":\"2017-03-16 17:22:53\",\"gmtModified\":\"2017-03-16 17:22:53\"},{\"id\":\"149104071532407641\",\"tagId\":\"149093888355806135\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"高端客户\",\"tagType\":1,\"tagKey\":\"gaoduan\",\"status\":0,\"tagPosition\":3,\"gmtCreate\":\"2017-03-15 15:12:43\",\"gmtModified\":\"2017-03-15 15:13:44\"},{\"id\":\"149104071532403726\",\"tagId\":\"149093888355806135\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"中端客户\",\"tagType\":1,\"tagKey\":\"zhongduan\",\"status\":0,\"tagPosition\":2,\"gmtCreate\":\"2017-03-15 15:12:43\",\"gmtModified\":\"2017-03-15 15:13:26\"},{\"id\":\"149104071532400364\",\"tagId\":\"149093888355806135\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"低端客户\",\"tagType\":1,\"tagKey\":\"diduan\",\"status\":0,\"tagPosition\":1,\"gmtCreate\":\"2017-03-15 15:12:43\",\"gmtModified\":\"2017-03-15 15:12:43\"},{\"id\":\"149104067228201735\",\"tagId\":\"149093588738404980\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"电话\",\"tagType\":1,\"tagKey\":\"source_dianhua\",\"status\":0,\"tagPosition\":2,\"gmtCreate\":\"2017-03-15 15:12:00\",\"gmtModified\":\"2017-03-15 15:12:00\"},{\"id\":\"149093883885005724\",\"tagId\":\"149093588738404980\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"官网\",\"tagType\":1,\"tagKey\":\"guanwang\",\"status\":0,\"tagPosition\":1,\"gmtCreate\":\"2017-03-14 10:56:02\",\"gmtModified\":\"2017-03-15 15:11:39\"},{\"id\":\"149093604927608240\",\"tagId\":\"149093600039207588\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"有意向\",\"tagType\":1,\"tagKey\":\"youyixiang\",\"status\":0,\"tagPosition\":2,\"gmtCreate\":\"2017-03-14 10:09:33\",\"gmtModified\":\"2017-03-15 15:10:52\"},{\"id\":\"149104055406708678\",\"tagId\":\"149093978058808048\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"上门\",\"tagType\":1,\"tagKey\":\"shangmen\",\"status\":0,\"tagPosition\":2,\"gmtCreate\":\"2017-03-15 15:10:02\",\"gmtModified\":\"2017-03-15 15:10:02\"},{\"id\":\"149094024196002754\",\"tagId\":\"149093600039207588\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"已成交\",\"tagType\":1,\"tagKey\":\"yichengjiao\",\"status\":0,\"tagPosition\":1,\"gmtCreate\":\"2017-03-14 11:19:25\",\"gmtModified\":\"2017-03-14 11:19:25\"},{\"id\":\"149094019745705822\",\"tagId\":\"149093978058808048\",\"companyId\":\"36\",\"userId\":\"110119744\",\"tagName\":\"电话\",\"tagType\":1,\"tagKey\":\"mobile\",\"status\":0,\"tagPosition\":1,\"gmtCreate\":\"2017-03-14 11:18:41\",\"gmtModified\":\"2017-03-14 11:18:41\"}]}";
return gson.fromJson(json, new TypeToken<Pagination<TagsDo>>(){}.getType());
}
private UserDo getMockUser() {
UserDo userDo = new UserDo();
userDo.setCompanyId(36L);
userDo.setId(729);
return userDo;
}
@Test
public void get() throws Exception {
when(tagsAppService.getTagsById(anyLong())).thenReturn(getMockTag());
ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.get("/tag/{id}", 121212).accept(MediaType.APPLICATION_JSON_UTF8));
MvcResult mvcResult = resultActions.andReturn();
Exception exception = mvcResult.getResolvedException();
if(exception != null) {
exception.printStackTrace();
}
String responseBody = mvcResult.getResponse().getContentAsString();
System.out.println(responseBody);
}
@Test
public void list() throws Exception {
//当调用这个方法的时候 , 返回一个Mock的对象
when(tagsAppService.getPageListByTags(any(TagsDo.class), anyInt(), anyInt())).thenReturn(getMockPagination());
// 给@RequestBody 注解传参数 , Json格式
String requestBody = "{\"pageNum\":\"1\",\"pageSize\":\"20\"}";
//模拟http请求发送一个请求
ResultActions resultActions = mockMvc.perform(patch("/tag")
.accept(MediaType.APPLICATION_JSON_UTF8)
.content(requestBody)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.sessionAttr(SalecrmConstant.SESSION_USER, getMockUser()));
//获取返回结果
MvcResult mvcResult = resultActions.andReturn();
//如果有错误 , 把错误信息的堆栈信息打出来
Exception exception = mvcResult.getResolvedException();
if(exception != null) {
exception.printStackTrace();
}
//获取结果的Body
String responseBody = mvcResult.getResponse().getContentAsString();
System.out.println(responseBody);
}
@Test
public void add() throws Exception {
when(tagsAppService.createTags(any())).thenReturn(getMockTag());
String requestBody = "{\"tagId\":\"1\",\"companyId\":\"36\",\"userId\":\"729\",\"tagName\":\"测试\",\"tagType\":\"1\",\"tagKey\":\"1\",\"status\":\"1\",\"tagPosition\":\"1\"}";
RequestBuilder requestBuilder = post("/tag")
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(requestBody)
.sessionAttr(SalecrmConstant.SESSION_USER, getMockUser());
ResultActions resultActions = mockMvc.perform(requestBuilder);
resultActions.andExpect(status().isOk());
MvcResult mvcResult = resultActions.andReturn();
//如果有错误 , 把错误信息的堆栈信息打出来
Exception exception = mvcResult.getResolvedException();
if(exception != null) {
exception.printStackTrace();
}
String responseBody = mvcResult.getResponse().getContentAsString();
System.out.println(responseBody);
}
@Test
public void update() throws Exception {
when(tagsAppService.updateTags(any(TagsDo.class))).thenReturn(true);
String requestBody = "{\"tagId\":\"1\",\"companyId\":\"36\",\"userId\":\"729\",\"tagName\":\"测试\",\"tagType\":\"1\",\"tagKey\":\"1\",\"status\":\"1\",\"tagPosition\":\"1\"}";
RequestBuilder requestBuilder = put("/tag/{id}",2323)
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8)
.content(requestBody)
.sessionAttr(SalecrmConstant.SESSION_USER, getMockUser());
ResultActions resultActions = mockMvc.perform(requestBuilder);
resultActions.andExpect(status().isOk());
MvcResult mvcResult = resultActions.andReturn();
//如果有错误 , 把错误信息的堆栈信息打出来
Exception exception = mvcResult.getResolvedException();
if(exception != null) {
exception.printStackTrace();
}
String responseBody = mvcResult.getResponse().getContentAsString();
System.out.println(responseBody);
}
@Test
public void delete() throws Exception {
RequestBuilder requestBuilder = MockMvcRequestBuilders.delete("/tag/{id}",2323)
.accept(MediaType.APPLICATION_JSON_UTF8)
.contentType(MediaType.APPLICATION_JSON_UTF8);
ResultActions resultActions = mockMvc.perform(requestBuilder);
resultActions.andExpect(status().isOk());
MvcResult mvcResult = resultActions.andReturn();
//如果有错误 , 把错误信息的堆栈信息打出来
Exception exception = mvcResult.getResolvedException();
if(exception != null) {
exception.printStackTrace();
}
String responseBody = mvcResult.getResponse().getContentAsString();
System.out.println(responseBody);
}
}
上面的代码对我们现在所用到的请求类型都做了demo .
结束语
对于mapper的具体的配置, 请参照Spring + Mybatis 整合