【java测试】Junit、Mock+代码覆盖率

原文见此处

单元测试是编写测试代码,用来检测特定的、明确的、细颗粒的功能。单元测试并不一定保证程序功能是正确的,更不保证整体业务是准备的。

单元测试不仅仅用来保证当前代码的正确性,更重要的是用来保证代码修复改进重构之后的正确性。

一般来说,单元测试任务包括

  1. 接口功能测试:用来保证接口功能的正确性。
  2. 局部数据结构测试(不常用):用来保证接口中的数据结构是正确的
    1. 比如变量有无初始值
    2. 变量是否溢出
  3. 边界条件测试
    1. 变量没有赋值(即为NULL)
    2. 变量是数值(或字符)
      1. 主要边界:最小值,最大值,无穷大(对于DOUBLE等)
      2. 溢出边界(期望异常或拒绝服务):最小值-1,最大值+1
      3. 临近边界:最小值+1,最大值-1
    3. 变量是字符串
      1. 引用“字符变量”的边界
      2. 空字符串
      3. 对字符串长度应用“数值变量”的边界
    4. 变量是集合
      1. 空集合
      2. 对集合的大小应用“数值变量”的边界
      3. 调整次序:升序、降序
    5. 变量有规律
      1. 比如对于Math.sqrt,给出n^2-1,和n^2+1的边界
  4. 所有独立执行通路测试:保证每一条代码,每个分支都经过测试
    1. 代码覆盖率
      1. 语句覆盖:保证每一个语句都执行到了
      2. 判定覆盖(分支覆盖):保证每一个分支都执行到
      3. 条件覆盖:保证每一个条件都覆盖到true和false(即if、while中的条件语句)
      4. 路径覆盖:保证每一个路径都覆盖到
    2. 相关软件
      1. Cobertura:语句覆盖
      2. Emma: Eclipse插件Eclemma
  5. 各条错误处理通路测试:保证每一个异常都经过测试

JUNIT

JUnit是Java单元测试框架,已经在Eclipse中默认安装。目前主流的有JUnit3和JUnit4。JUnit3中,测试用例需要继承TestCase类。JUnit4中,测试用例无需继承TestCase类,只需要使用@Test等注解。

Junit3

先看一个Junit3的样例

  1 package com.ishansong.efficiency.avatar.api.service.impl;
  2 
  3 import com.google.common.collect.Lists;
  4 import com.google.common.collect.Maps;
  5 import com.ishansong.efficiency.avatar.api.configuration.apollo.ApolloConfigBusiness;
  6 import com.ishansong.efficiency.avatar.api.dao.entity.generated.*;
  7 import com.ishansong.efficiency.avatar.api.dao.mapper.generated.ShowWholeEqpResultEntityMapper;
  8 import com.ishansong.efficiency.avatar.api.enums.StatisticsLvEnum;
  9 import com.ishansong.efficiency.avatar.api.manager.*;
 10 import com.ishansong.efficiency.avatar.api.service.DataMarketDBSyncService;
 11 import com.ishansong.efficiency.avatar.api.service.DataMarketService;
 12 import com.ishansong.efficiency.avatar.api.utils.DateUtils;
 13 import com.ishansong.efficiency.avatar.api.utils.DingMsgUtils;
 14 import com.ishansong.efficiency.avatar.api.utils.YearMonth;
 15 import com.ishansong.efficiency.avatar.api.view.model.IndexModelData;
 16 import com.ishansong.efficiency.avatar.api.view.model.MonthlyIndexModel;
 17 import lombok.extern.slf4j.Slf4j;
 18 import org.springframework.beans.factory.annotation.Autowired;
 19 import org.springframework.stereotype.Service;
 20 import org.springframework.util.CollectionUtils;
 21 import org.springframework.util.StringUtils;
 22 import tk.mybatis.mapper.entity.Example;
 23 import tk.mybatis.mapper.weekend.WeekendSqls;
 24 
 25 import java.time.LocalDate;
 26 import java.util.*;
 27 import java.util.stream.Collectors;
 28 
 29 import static java.util.stream.Collectors.toList;
 30 
 31 @Slf4j
 32 @Service
 33 public class DataMarketDBSyncServiceImpl implements DataMarketDBSyncService {
 34     @Autowired
 35     private DataMarketDBSyncManager dataMarketDBSyncManager;
 36     @Autowired
 37     private ShowWholeEqpResultEntityMapper showWholeEqpResultEntityMapper;
 38     @Autowired
 39     private StoryTeamUserWeekManager storyTeamUserWeekManager;
 40     @Autowired
 41     private StoryTeamUserManager storyTeamUserManager;
 42     @Autowired
 43     private StoryTeamUserTestManager storyTeamUserTestManager;
 44     @Autowired
 45     private DataMarketService dataMarketService;
 46     @Autowired
 47     private OrganizationManager organizationManager;
 48     @Autowired
 49     private OrganizationTestManager organizationTestManager;
 50     @Autowired
 51     private StoryManager storyManager;
 52     @Autowired
 53     private StoryTestManager storyTestManager;
 54     @Autowired
 55     private ApolloConfigBusiness apolloConfigBusiness;
 56 
 57     @Override
 58     public List<MonthlyIndexModel> queryMonthsIndexModelList(Integer years, Integer maxMonth, int count, String metricCode) {
 59         List<ShowWholeEqpResultEntity> list = dataMarketDBSyncManager.queryByMaxMonth(years, maxMonth, count, metricCode);
 60         return this.transferShowEntity2IndexModel(list);
 61     }
 62 
 63     @Override
 64     public List<MonthlyIndexModel> queryWeeksIndexModelList(String maxTitleSort, int count, String metricCode) {
 65         List<ShowWholeEqpResultEntity> list = dataMarketDBSyncManager.queryByMaxWeeks(maxTitleSort, count, metricCode);
 66         return this.transferShowEntity2IndexModel(list);
 67     }
 68 
 69     @Override
 70     public ShowWholeEqpResultEntity addOne(ShowWholeEqpResultEntity entity) {
 71         entity.setCtime(new Date().getTime());
 72         List<ShowWholeEqpResultEntity> saveList = new ArrayList<>(1);
 73         saveList.add(entity);
 74         showWholeEqpResultEntityMapper.insertBatch(saveList);
 75         return null;
 76     }
 77 
 78     @Override
 79     public List<ShowWholeEqpResultEntity> query(String model, String typeCode, Integer years, Integer months, String title, String titleSort) {
 80         WeekendSqls<ShowWholeEqpResultEntity> weekendSqls = WeekendSqls.custom();
 81         if (! StringUtils.isEmpty(model)) {
 82             weekendSqls.andEqualTo(ShowWholeEqpResultEntity::getModel, model);
 83         }
 84         if (! StringUtils.isEmpty(typeCode)) {
 85             weekendSqls.andEqualTo(ShowWholeEqpResultEntity::getTypeCode, typeCode);
 86         }
 87         if (! StringUtils.isEmpty(title)) {
 88             weekendSqls.andEqualTo(ShowWholeEqpResultEntity::getTitle, title);
 89         }
 90         if (! StringUtils.isEmpty(titleSort)) {
 91             weekendSqls.andEqualTo(ShowWholeEqpResultEntity::getTitleSort, titleSort);
 92         }
 93         if (years != null) {
 94             weekendSqls .andEqualTo(ShowWholeEqpResultEntity::getYears, years);
 95         }
 96         if (months != null) {
 97             weekendSqls .andEqualTo(ShowWholeEqpResultEntity::getMonths, months);
 98         }
 99         Example example = new Example.Builder(ShowWholeEqpResultEntity.class)
100                 .andWhere(weekendSqls).build();
101         return showWholeEqpResultEntityMapper.selectByExample(example);
102     }
103 
104     @Override
105     public ShowWholeEqpResultEntity update(ShowWholeEqpResultEntity entity) {
106         showWholeEqpResultEntityMapper.updateByPrimaryKeySelective(entity);
107         ShowWholeEqpResultEntity oldEntity = showWholeEqpResultEntityMapper.selectByPrimaryKey(entity);
108         return oldEntity;
109     }
110 
111     @Override
112     public void delete(Long id) {
113         ShowWholeEqpResultEntity entity = new ShowWholeEqpResultEntity();
114         entity.setId(id);
115         showWholeEqpResultEntityMapper.deleteByPrimaryKey(entity);
116     }
117 
118     /**
119      * 检查某个月份中对应的记录是否存在,不存在则自动添加
120      */
121     @Override
122     public void checkAndAddByMonth(String year_month) {
123         try {
124             String[] codeArr = new String[]{"Produce", "Quality", "Efficiency"};
125             Map<String, String> codeMap = new HashMap<>();
126             codeMap.put("Produce", "产出");
127             codeMap.put("Quality", "质量");
128             codeMap.put("Efficiency", "效率");
129             if (StringUtils.isEmpty(year_month)) return;
130             // 检查月度记录:应该存在3条记录才正确
131             List<ShowWholeEqpResultEntity> monthList = showWholeEqpResultEntityMapper.selectForMonth(year_month);
132             Map<String, ShowWholeEqpResultEntity> monthMap = new HashMap<>();
133             if (!CollectionUtils.isEmpty(monthList)) {
134                 monthList.forEach(m->monthMap.put(m.getTypeCode(), m));
135             }
136             List<ShowWholeEqpResultEntity> saveList = new ArrayList<>();
137             // 检查是否存在
138             for (String code : codeArr) {
139                 if (monthMap.get(code)==null) {
140                     saveList.add(this.newShowResMonth(year_month, code, codeMap.get(code)));
141                 }
142             }
143 
144             // 得到月份对应的周
145             String[] ym = year_month.split("-");
146             List<YearMonth> weeks = DateUtils.getAllWeekOfMonth(ym[0], ym[1]);
147             Map<String, String> weekSEMap = new HashMap<>();
148             List<String> titleSorts = new ArrayList<>();
149             for (YearMonth week : weeks) {
150                 titleSorts.add(week.getStart());
151                 weekSEMap.put(week.getStart(), week.getEnd());
152             }
153             List<ShowWholeEqpResultEntity> weekList = this.queryByWeeks(titleSorts);
154             Map<String, List<ShowWholeEqpResultEntity>> weekEntityMap = new HashMap<>();
155             if (! CollectionUtils.isEmpty(weekList)) { // code 分组
156                 weekEntityMap = weekList.stream().collect(Collectors.groupingBy(w -> w.getTypeCode()));
157             }
158             for (String code : codeArr) {
159                 List<ShowWholeEqpResultEntity> weList = weekEntityMap.get(code);
160                 Map<String, ShowWholeEqpResultEntity> oneWeekMap = new HashMap<>();
161                 if (!CollectionUtils.isEmpty(weList)) {
162                     weList.forEach(w->oneWeekMap.put(w.getTitleSort(), w));
163                 }
164                 // 遍历
165                 for (String titleSort : titleSorts) {
166                     String weekEnd = weekSEMap.get(titleSort);
167                     String title = titleSort.substring(5)+"/"+weekEnd.substring(5);
168                     if (oneWeekMap.get(titleSort)==null) {
169                         saveList.add(this.newShowResWeek(title, titleSort, code, codeMap.get(code)));
170                     }
171                 }
172             }
173 
174             // 检查批量保存
175             if (!CollectionUtils.isEmpty(saveList)) {
176                 showWholeEqpResultEntityMapper.insertBatch(saveList);
177             }
178         } catch (Exception e) {
179             log.warn("checkAndAddByMonth:{}", e);
180         }
181     }
182 
183     private List<ShowWholeEqpResultEntity> queryByWeeks(List<String> titleSorts) {
184         Example example = new Example.Builder(ShowWholeEqpResultEntity.class)
185                 .andWhere(
186                         WeekendSqls.<ShowWholeEqpResultEntity>custom()
187                                 .andIn(ShowWholeEqpResultEntity::getTitleSort, titleSorts)
188                 )
189                 .build();
190         return showWholeEqpResultEntityMapper.selectByExample(example);
191     }
192 
193 
194     private ShowWholeEqpResultEntity newShowResMonth(String year_month, String typeCode, String typeName) {
195         ShowWholeEqpResultEntity entity = new ShowWholeEqpResultEntity();
196         entity.setModel("month");
197         entity.setTitle(year_month);
198         String[] ym = year_month.split("-");
199         entity.setYears(Integer.parseInt(ym[0]));
200         entity.setMonths(Integer.parseInt(ym[1]));
201         entity.setScore(60.0);
202         entity.setTypeCode(typeCode);
203         entity.setTypeName(typeName);
204         entity.setCtime(new Date().getTime());
205         return entity;
206     }
207 
208     private ShowWholeEqpResultEntity newShowResWeek(String title, String titleSort, String typeCode, String typeName) {
209         ShowWholeEqpResultEntity entity = new ShowWholeEqpResultEntity();
210         entity.setModel("week");
211         entity.setTitle(title);
212         entity.setTitleSort(titleSort);
213         entity.setScore(60.0);
214         entity.setTypeCode(typeCode);
215         entity.setTypeName(typeName);
216         entity.setCtime(new Date().getTime());
217         return entity;
218     }
219 
220 
221     private List<MonthlyIndexModel> transferShowEntity2IndexModel(List<ShowWholeEqpResultEntity> list) {
222         List<MonthlyIndexModel> resList = Lists.newArrayList();
223         try {
224             // 将数据转换成 MonthlyIndexModel
225             if (CollectionUtils.isEmpty(list)) {
226                 return resList;
227             }
228             list.forEach(s -> {
229                 MonthlyIndexModel indexModel = new MonthlyIndexModel();
230                 indexModel.setYear(s.getYears());
231                 indexModel.setMonth(s.getMonths());
232                 indexModel.setTitle(s.getTitle());
233                 indexModel.setTitleSort(s.getTitleSort());
234                 // 组装 indexModelData
235                 List<IndexModelData> indexModelData = new ArrayList<>(1);
236                 IndexModelData data = new IndexModelData();
237                 data.setName(s.getTypeName());
238                 data.setScore(s.getScore());
239                 data.setCode(s.getTypeCode());
240                 indexModelData.add(data);
241                 indexModel.setIndexModelData(indexModelData);
242                 resList.add(indexModel);
243             });
244         } catch (Exception e) {
245             log.warn("{}", e);
246             return resList;
247         }
248         return resList;
249     }
250 
251     @Override
252     public String syncDataMarket2DB() {
253         // 先进行聚合
254         return this.syncDataMarket2DB(null, null, null, null);
255     }
256 
257     Map<Long, String> orgIdNameMap = Maps.newHashMap();
258     @Override
259     public void exceptStorySendDingTalk(String currDay, Integer currYear, Integer currMonth) {
260         String NEWLINE = "\n\n";
261         // 有问题的统计记录
262         List<StoryTeamUserWeekEntity> storyTeamUserWeekEntities;
263         if (currYear != null && currMonth != null) {
264             storyTeamUserWeekEntities = storyTeamUserWeekManager.queryAll(currYear, currMonth);
265         } else {
266             storyTeamUserWeekEntities = storyTeamUserWeekManager.queryByCtimeIsCurrMonth();// 查询两月内数据
267         }
268         if (CollectionUtils.isEmpty(storyTeamUserWeekEntities)) return;
269 
270         // 筛选出问题故事点
271         List<StoryEntity> stories;
272         if (currYear != null && currMonth != null) {
273             stories = storyManager.queryByYearAndMonth(currYear, currMonth);
274         } else {
275             stories = storyManager.queryByCtimeIsCurrMonth();// 查询两月内数据
276         }
277         if (CollectionUtils.isEmpty(stories)) return;
278 
279         // 查询出所有的teamId
280         List<Long> teamIds = null;
281         // 提测准时率问题
282         List<StoryEntity> submitTestOnTimeExcepStorys = dataMarketService.getWeekLvByType(teamIds, stories, storyTeamUserWeekEntities, StatisticsLvEnum.ForTestOntimeRatio.getValue());
283         // 上线准时率问题
284         List<StoryEntity> onlineOnTimeExcepStorys = dataMarketService.getWeekLvByType(teamIds, stories, storyTeamUserWeekEntities, StatisticsLvEnum.ForPublishOntimeRatio.getValue());
285         // 评审通过率问题
286 //        List<StoryEntity> reviewExcepStorys = dataMarketService.getWeekLvByType(teamIds, stories, storyTeamUserWeekEntities, StatisticsLvEnum.ReviewSuccessRatio.getValue());
287         // 提测通过率问题
288         List<StoryEntity> submitTestExcepStorys = dataMarketService.getWeekLvByType(teamIds, stories, storyTeamUserWeekEntities, StatisticsLvEnum.TestRatio.getValue());
289 
290         // 收集需求id
291         Set<Long> pidSet = new HashSet<>();
292         if (!CollectionUtils.isEmpty(submitTestOnTimeExcepStorys)) {
293             submitTestOnTimeExcepStorys.forEach(s->pidSet.add(s.getStoryId()));
294         }
295         if (!CollectionUtils.isEmpty(onlineOnTimeExcepStorys)) {
296             onlineOnTimeExcepStorys.forEach(s->pidSet.add(s.getStoryId()));
297         }
298 //        if (!CollectionUtils.isEmpty(reviewExcepStorys)) {
299 //            reviewExcepStorys.forEach(s->pidSet.add(s.getStoryId()));
300 //        }
301         if (!CollectionUtils.isEmpty(submitTestExcepStorys)) {
302             submitTestExcepStorys.forEach(s->pidSet.add(s.getStoryId()));
303         }
304         if (CollectionUtils.isEmpty(pidSet)) {
305             return;
306         }
307         // 查询父需求记录
308         List<StoryTeamUserEntity> teamUserEntities = storyTeamUserManager.queryByStoryIds(pidSet);
309         if (CollectionUtils.isEmpty(teamUserEntities)) return;
310         Map<Long, String> creatorMap = new HashMap<>();
311         teamUserEntities.forEach(s->creatorMap.put(s.getStoryId(), s.getCreator()));
312 
313         List<OrganizationEntity> allOrgs = organizationManager.queryAll();
314         allOrgs.forEach(o -> orgIdNameMap.put(o.getId(), o.getTitle()));
315         boolean allowSend = false;
316         // 汇总消息allowSend:需求数据
317         StringBuilder builder = new StringBuilder();
318         String title = "## 阿凡达【待处理异常需求】提醒 ";
319         if (currMonth !=null) {
320             title = "## 阿凡达 "+currYear+"."+currMonth+"【待处理异常需求】提醒 ";
321         }
322 
323         builder.append(title).append(NEWLINE);
324         builder.append("> 注:(用户)为需求创建人。").append(NEWLINE);
325         builder.append("[阿凡达详情](http://avatar.bingex.com/dashboard)").append(NEWLINE);
326         int rowNum = 1;
327 
328         if (! CollectionUtils.isEmpty(submitTestOnTimeExcepStorys)) {
329             allowSend = true;
330             builder.append("**").append(rowNum++).append("、提测准时、通过率异常需求").append("**").append(NEWLINE);
331             builder.append("> 请检查提测时间和是否通过").append(NEWLINE);
332             for (StoryEntity s : submitTestOnTimeExcepStorys) {
333                 String creator = creatorMap.get(s.getStoryId());
334                 creator = StringUtils.isEmpty(creator)?"无":creator;
335                 builder.append("* [").append(s.getName()).append("(准时率)").append("](https://www.tapd.cn/21483581/prong/stories/view/")
336                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
337             }
338         }
339         if (! CollectionUtils.isEmpty(submitTestExcepStorys)) {
340             allowSend = true;
341             // 如果没有记录 前面的提测准时率问题需求
342             if (CollectionUtils.isEmpty(submitTestOnTimeExcepStorys)) {
343                 builder.append("**").append(rowNum++).append("、提测准时、通过率异常需求").append("**").append(NEWLINE);
344                 builder.append("> 请检查提测时间和是否通过").append(NEWLINE);
345             }
346             // 追加问题需求
347             for (StoryEntity s : submitTestExcepStorys) {
348                 String creator = creatorMap.get(s.getStoryId());
349                 creator = StringUtils.isEmpty(creator)?"无":creator;
350                 builder.append("* [").append(s.getName()).append("(通过率)").append("](https://www.tapd.cn/21483581/prong/stories/view/")
351                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
352             }
353         }
354 
355         if (! CollectionUtils.isEmpty(onlineOnTimeExcepStorys)) {
356             allowSend = true;
357             builder.append("**").append(rowNum++).append("、上线准时率异常需求").append("**").append(NEWLINE);
358             builder.append("> 请检查实际上线时间、预计结束时间和状态").append(NEWLINE);
359             for (StoryEntity s : onlineOnTimeExcepStorys) {
360                 String creator = creatorMap.get(s.getStoryId());
361                 creator = StringUtils.isEmpty(creator)?"无":creator;
362                 builder.append("* [").append(s.getName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
363                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
364             }
365         }
366         // 产品指标去掉
367 //        if (! CollectionUtils.isEmpty(reviewExcepStorys)) {
368 //            allowSend = true;
369 //            builder.append("**").append(rowNum++).append("、评审通过率异常需求").append("**").append(NEWLINE);
370 //            builder.append("> 请检查需求评审时间和通过状态").append(NEWLINE);
371 //            for (StoryEntity s : reviewExcepStorys) {
372 //                String creator = creatorMap.get(s.getStoryId());
373 //                creator = StringUtils.isEmpty(creator)?"无":creator;
374 //                builder.append("* [").append(s.getName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
375 //                        .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
376 //            }
377 //        }
378 
379         if (allowSend) {
380             DingMsgUtils.sendMarkdown(title, builder.toString(), apolloConfigBusiness.getAvatarDataMarketExceptionDingDingUrl());
381         }
382 
383         // 需求信息补全提醒
384         this.alarmStoryInfoLack();
385     }
386 
387     // 父需求信息完整性告警:只进行月度告警
388     private void alarmStoryInfoLack()  {
389         // 换行
390         String NEWLINE = "\n\n";
391         // 根据月份查询父需求 story
392         List<StoryEntity> storyEntities = storyManager.queryByCtimeIsCurrMonth(); // storyManager.queryByYearAndMonth(currYear, currMonth);
393         if (CollectionUtils.isEmpty(storyEntities)) {
394             return;
395         }
396         // 筛选出父需求
397         List<StoryEntity> parentStoryList = storyEntities.stream().filter(s -> s.getParentStoryId() == 0).collect(toList());
398         if (CollectionUtils.isEmpty(parentStoryList)) {
399             return;
400         }
401         Map<Long, StoryEntity> allStoryMap = new HashMap<>();
402         // 添加一个不存在的预设置, 方式空条件查询
403         parentStoryList.forEach(s-> allStoryMap.put(s.getStoryId(), s));
404         List<StoryTeamUserEntity> teamUserEntities = storyTeamUserManager.queryByCtimeIsCurrMonth(); // storyTeamUserManager.queryByStoryIdSet(parentStoryIdSet);
405         // 检查项目名称、需求来源
406         List<StoryEntity> twoLackList = new ArrayList<>();
407         List<StoryEntity> demanderLackList = new ArrayList<>();
408         List<StoryEntity> projectLackList = new ArrayList<>();
409         for (StoryTeamUserEntity teamUser : teamUserEntities) {
410             if (StringUtils.isEmpty(teamUser.getDemander()) && StringUtils.isEmpty(teamUser.getProject())) { // 都缺少
411                 twoLackList.add(allStoryMap.get(teamUser.getStoryId()));
412             }
413             else if (StringUtils.isEmpty(teamUser.getDemander())) { // 检查需求来源
414                 demanderLackList.add(allStoryMap.get(teamUser.getStoryId()));
415             }
416             else if (StringUtils.isEmpty(teamUser.getProject())) { // 检查项目名称
417                 projectLackList.add(allStoryMap.get(teamUser.getStoryId()));
418             }
419         }
420         // teamUserMap
421         Map<Long, StoryTeamUserEntity> teamUserMap = new HashMap<>();
422         teamUserEntities.forEach(st->teamUserMap.put(st.getStoryId(), st));
423 
424         List<OrganizationEntity> allOrgs = organizationManager.queryAll();
425         allOrgs.forEach(o -> orgIdNameMap.put(o.getId(), o.getTitle()));
426         boolean allowSend = false;
427         // 组装告警信息
428         // 汇总消息:需求数据
429         StringBuilder builder = new StringBuilder();
430         String title = "## TAPD 开发团队待补充信息需求提醒";
431         builder.append(title).append(NEWLINE);
432         builder.append("> 注:(用户)为需求创建人。").append(NEWLINE);
433         int rowNum = 1;
434         Map<String, String> demanderMap = new HashMap<>();
435         Map<String, String> projectMap = new HashMap<>();
436         if (! CollectionUtils.isEmpty(twoLackList)) {
437             allowSend = true;
438             builder.append("**").append(rowNum++).append("、待补充【需求来源】和【项目名称】需求").append("**").append(NEWLINE);
439             for (StoryEntity s : twoLackList) {
440                 if (demanderMap.get(s.getName()) != null) continue;
441                 StoryTeamUserEntity teamUserEntity = teamUserMap.get(s.getStoryId());
442                 String teamName = teamUserEntity!=null? orgIdNameMap.get(teamUserEntity.getTeamId()):"无小组";
443                 String creator =  teamUserEntity!=null? teamUserEntity.getCreator():"无";
444                 builder.append("* [").append(s.getName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
445                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
446                 demanderMap.put(s.getName(), s.getName());
447             }
448         }
449         if (! CollectionUtils.isEmpty(demanderLackList)) {
450             allowSend = true;
451             builder.append("**").append(rowNum++).append("、待补充【需求来源】需求").append("**").append(NEWLINE);
452             for (StoryEntity s : demanderLackList) {
453                 if (demanderMap.get(s.getName()) != null) continue;
454                 StoryTeamUserEntity teamUserEntity = teamUserMap.get(s.getStoryId());
455                 String teamName = teamUserEntity!=null? orgIdNameMap.get(teamUserEntity.getTeamId()):"无小组";
456                 String creator =  teamUserEntity!=null? teamUserEntity.getCreator():"无";
457                 builder.append("* [").append(s.getName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
458                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
459                 demanderMap.put(s.getName(), s.getName());
460             }
461         }
462         if (! CollectionUtils.isEmpty(projectLackList)) {
463             allowSend = true;
464             builder.append("**").append(rowNum++).append("、待补充【项目名称】需求").append("**").append(NEWLINE);
465             for (StoryEntity s : projectLackList) {
466                 if (projectMap.get(s.getName()) != null) continue;
467                 StoryTeamUserEntity teamUserEntity = teamUserMap.get(s.getStoryId());
468                 String teamName = teamUserEntity!=null? orgIdNameMap.get(teamUserEntity.getTeamId()):"无小组";
469                 String creator =  teamUserEntity!=null? teamUserEntity.getCreator():"无";
470                 builder.append("* [").append(s.getName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
471                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
472                 projectMap.put(s.getName(), s.getName());
473             }
474         }
475         if (allowSend) {
476             DingMsgUtils.sendMarkdown(title, builder.toString(), apolloConfigBusiness.getAvatarDataMarketExceptionDingDingUrl());
477         }
478     }
479 
480 
481     Map<Long, String> testOrgIdNameMap = Maps.newHashMap();
482     // 父需求信息完整性告警:只进行月度告警
483     @Override
484     public void alarmTestStoryInfoLack()  {
485         // 换行
486         String NEWLINE = "\n\n";
487         // 根据月份查询父需求 story
488         List<StoryTestEntity> storyEntities = storyTestManager.queryByCtimeIsCurrMonth(); // storyManager.queryByYearAndMonth(currYear, currMonth);
489         if (CollectionUtils.isEmpty(storyEntities)) {
490             return;
491         }
492         // 筛选出父需求
493         List<StoryTestEntity> parentStoryList = storyEntities.stream().filter(s -> s.getParentStoryId() == 0).collect(toList());
494         if (CollectionUtils.isEmpty(parentStoryList)) {
495             return;
496         }
497         Map<Long, StoryTestEntity> allStoryMap = new HashMap<>();
498         parentStoryList.forEach(s-> {
499             allStoryMap.put(s.getStoryId(), s);
500         });
501         List<StoryTeamUserTestEntity> teamUserEntities = storyTeamUserTestManager.queryByCtimeIsCurrMonth(); // storyTeamUserManager.queryByStoryIdSet(parentStoryIdSet);
502         // 检查项目名称、需求来源
503         List<StoryTestEntity> twoLackList = new ArrayList<>();
504         List<StoryTestEntity> demanderLackList = new ArrayList<>();
505         List<StoryTestEntity> projectLackList = new ArrayList<>();
506         for (StoryTeamUserTestEntity teamUser : teamUserEntities) {
507             if (StringUtils.isEmpty(teamUser.getDemander()) && StringUtils.isEmpty(teamUser.getProject())) { // 都缺少
508                 StoryTestEntity storyTest = allStoryMap.get(teamUser.getStoryId());
509                 if (storyTest != null) {
510                     twoLackList.add(storyTest);
511                 }
512             }
513             else if (StringUtils.isEmpty(teamUser.getDemander())) { // 检查需求来源
514                 StoryTestEntity storyTest = allStoryMap.get(teamUser.getStoryId());
515                 if (storyTest != null) {
516                     demanderLackList.add(storyTest);
517                 }
518             }
519             else if (StringUtils.isEmpty(teamUser.getProject())) { // 检查项目名称
520                 StoryTestEntity storyTest = allStoryMap.get(teamUser.getStoryId());
521                 if (storyTest != null) {
522                     projectLackList.add(storyTest);
523                 }
524             }
525         }
526         // teamUserMap
527         Map<Long, StoryTeamUserTestEntity> teamUserMap = new HashMap<>();
528         teamUserEntities.forEach(st->teamUserMap.put(st.getStoryId(), st));
529 
530         List<OrganizationTestEntity> allOrgs = organizationTestManager.queryAll();
531         allOrgs.forEach(o -> testOrgIdNameMap.put(o.getId(), o.getTitle()));
532         boolean allowSend = false;
533         // 组装告警信息
534         // 汇总消息:需求数据
535         StringBuilder builder = new StringBuilder();
536         String title = "## TAPD 测试团队待补充信息需求提醒";
537         builder.append(title).append(NEWLINE);
538         builder.append("> 注:(用户)为需求创建人。").append(NEWLINE);
539         int rowNum = 1;
540         Map<String, String> demanderMap = new HashMap<>();
541         Map<String, String> projectMap = new HashMap<>();
542         if (! CollectionUtils.isEmpty(twoLackList)) {
543             allowSend = true;
544             builder.append("**").append(rowNum++).append("、待补充【需求来源】和【项目名称】需求").append("**").append(NEWLINE);
545             for (StoryTestEntity s : twoLackList) {
546                 if (demanderMap.get(s.getStoryName()) != null) continue;
547                 StoryTeamUserTestEntity teamUserEntity = teamUserMap.get(s.getStoryId());
548                 String teamName = teamUserEntity!=null? testOrgIdNameMap.get(teamUserEntity.getTeamId()):"无小组";
549                 String creator =  teamUserEntity!=null? teamUserEntity.getCreator():"无";
550                 builder.append("* [").append(s.getStoryName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
551                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
552                 demanderMap.put(s.getStoryName(), s.getStoryName());
553             }
554         }
555         if (! CollectionUtils.isEmpty(demanderLackList)) {
556             allowSend = true;
557             builder.append("**").append(rowNum++).append("、待补充【需求来源】需求").append("**").append(NEWLINE);
558             for (StoryTestEntity s : demanderLackList) {
559                 if (demanderMap.get(s.getStoryName()) != null) continue;
560                 StoryTeamUserTestEntity teamUserEntity = teamUserMap.get(s.getStoryId());
561                 String teamName = teamUserEntity!=null? testOrgIdNameMap.get(teamUserEntity.getTeamId()):"无小组";
562                 String creator =  teamUserEntity!=null? teamUserEntity.getCreator():"无";
563                 builder.append("* [").append(s.getStoryName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
564                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
565                 demanderMap.put(s.getStoryName(), s.getStoryName());
566             }
567         }
568         if (! CollectionUtils.isEmpty(projectLackList)) {
569             allowSend = true;
570             builder.append("**").append(rowNum++).append("、待补充【项目名称】需求").append("**").append(NEWLINE);
571             for (StoryTestEntity s : projectLackList) {
572                 if (projectMap.get(s.getStoryName()) != null) continue;
573                 StoryTeamUserTestEntity teamUserEntity = teamUserMap.get(s.getStoryId());
574                 String teamName = teamUserEntity!=null? testOrgIdNameMap.get(teamUserEntity.getTeamId()):"无小组";
575                 String creator =  teamUserEntity!=null? teamUserEntity.getCreator():"无";
576                 builder.append("* [").append(s.getStoryName()).append("](https://www.tapd.cn/21483581/prong/stories/view/")
577                         .append(s.getParentStoryId()==0L?s.getStoryId() : s.getParentStoryId()).append(")(").append(creator).append(")").append(NEWLINE);
578                 projectMap.put(s.getStoryName(), s.getStoryName());
579             }
580         }
581         if (allowSend) {
582             DingMsgUtils.sendMarkdown(title, builder.toString(), apolloConfigBusiness.getAvatarDataMarketExceptionDingDingUrl());
583         }
584     }
585 
586     /**
587      * 产研整体效能趋势 数据计算和同步
588      * queryWeeks: YearAndMonth.toString格式
589      */
590     @Override
591     public String syncDataMarket2DB(Integer queryYear, Integer queryMonth, String currDay, Boolean syncAll) {
592         try {
593             // 默认不同步全部
594             if (syncAll == null) {
595                 syncAll = false;
596             }
597             String queryWeeks = null;
598             // 如果不需要同步全部,说明是定时任务过来
599             if (syncAll) {
600                 queryYear = null;
601                 queryMonth = null;
602                 // 删除全部数据
603                 dataMarketDBSyncManager.clearAll();
604             } else {
605                 // 计算年、月
606                 LocalDate localDate = LocalDate.now();
607                 if (queryYear == null) {
608                     queryYear = localDate.getYear();
609                 }
610                 if (queryMonth == null) {
611                     queryMonth = localDate.getMonthValue();
612                 }
613 
614                 // 删除一个月的月数据
615 //               dataMarketDBSyncManager.clearMonthData(queryYear, queryMonth);
616 
617                 // 如果传递的日期为空,使用当前日期的周
618                 if (StringUtils.isEmpty(currDay)) {
619                     int monthValue = localDate.getMonthValue();
620                     String months = ""+monthValue;
621                     if (monthValue<10) {
622                         months = "0"+monthValue;
623                     }
624                     currDay = localDate.getYear()+"-"+months+"-"+localDate.getDayOfMonth();
625                 }
626                 String[] dateWeekInterval = DateUtils.getDateWeekInterval(currDay);
627                 queryWeeks = new YearMonth(dateWeekInterval[0], dateWeekInterval[1]).toString();
628                 // 删除一个月的周段数据
629 //              dataMarketDBSyncManager.clearWeekData(dateWeekInterval[0]);
630             }
631 
632             List<OrganizationEntity> organizationEntities = organizationManager.queryAll();
633             List<Long> smallTeamIds = Lists.newArrayList();
634             List<OrganizationEntity> smallTeams = organizationEntities.stream().filter(
635                     k->!k.getCtime().equals(0l)
636             ).collect(toList());
637             smallTeams.stream().forEach(small -> smallTeamIds.add(small.getId()));
638             // 月度数据
639             dataMarketDBSyncManager.syncDataMarket2DBForMonths(smallTeamIds, queryYear, queryMonth);
640             // 周段数据
641             dataMarketDBSyncManager.syncDataMarket2DBForWeeks(smallTeamIds, queryWeeks);
642             return "OK";
643         } catch (Exception e) {
644             return "ERROR: " + e.getMessage();
645         }
646     }
647 
648 
649 }

 

  1. // 测试java.lang.Math  
  2. // 必须继承TestCase  
  3. public class Junit3TestCase extends TestCase {  
  4.     public Junit3TestCase() {  
  5.         super();  
  6.     }  
  7.     
  8.         // 传入测试用例名称  
  9.     public Junit3TestCase(String name) {  
  10.         super(name);  
  11.     }  
  12.    
  13.         // 在每个Test运行之前运行  
  14.     @Override  
  15.     protected void setUp() throws Exception {  
  16.         System.out.println("Set up");  
  17.     }  
  18.         // 测试方法。  
  19.         // 方法名称必须以test开头,没有参数,无返回值,是公开的,可以抛出异常  
  20.         // 也即类似public void testXXX() throws Exception {}  
  21.     public void testMathPow() {  
  22.         System.out.println("Test Math.pow");  
  23.         Assert.assertEquals(4.0, Math.pow(2.0, 2.0));  
  24.     }  
  25.    
  26.     public void testMathMin() {  
  27.         System.out.println("Test Math.min");  
  28.         Assert.assertEquals(2.0, Math.min(2.0, 4.0));  
  29.     }  
  30.    
  31.         // 在每个Test运行之后运行  
  32.     @Override  
  33.     protected void tearDown() throws Exception {  
  34.         System.out.println("Tear down");  
  35.     }  
  36. }  

 

 

如果采用默认的TestSuite,则测试方法必须是public void testXXX() [throws Exception] {}的形式,并且不能存在依赖关系,因为测试方法的调用顺序是不可预知的。
上例执行后,控制台会输出

  1. Set up  
  2. Test Math.pow  
  3. Tear down  
  4. Set up  
  5. Test Math.min  
  6. Tear down  

 

 

从中,可以猜测到,对于每个测试方法,调用的形式是:

  1. testCase.setUp();  
  2. testCase.testXXX();  
  3. testCase.tearDown();     

 

 
运行测试方法

在Eclipse中,可以直接在类名或测试方法上右击,在弹出的右击菜单中选择Run As -> JUnit Test。
在Mvn中,可以直接通过mvn test命令运行测试用例。
也可以通过Java方式调用,创建一个TestCase实例,然后重载runTest()方法,在其方法内调用测试方法(可以多个)。

  1. TestCase test = new Junit3TestCase("mathPow") {  
  2.         // 重载  
  3.     protected void runTest() throws Throwable {  
  4.         testMathPow();  
  5.     };  
  6. };  
  7. test.run();  

 

 

更加便捷地,可以在创建TestCase实例时直接传入测试方法名称,JUnit会自动调用此测试方法,如

  1. TestCase test = new Junit3TestCase("testMathPow");  
  2. test.run();  

 

 
Junit TestSuite

TestSuite是测试用例套件,能够运行过个测试方法。如果不指定TestSuite,会创建一个默认的TestSuite。默认TestSuite会扫描当前内中的所有测试方法,然后运行。
如果不想采用默认的TestSuite,则可以自定义TestSuite。在TestCase中,可以通过静态方法suite()返回自定义的suite。

  1. import junit.framework.Assert;  
  2. import junit.framework.Test;  
  3. import junit.framework.TestCase;  
  4. import junit.framework.TestSuite;  
  5.    
  6. public class Junit3TestCase extends TestCase {  
  7.         //...  
  8.     public static Test suite() {  
  9.         System.out.println("create suite");  
  10.         TestSuite suite = new TestSuite();  
  11.         suite.addTest(new Junit3TestCase("testMathPow"));  
  12.         return suite;  
  13.     }  
  14. }  

 

 

允许上述方法,控制台输出

写道
create suite
Set up
Test Math.pow
Tear down

 

 

并且只运行了testMathPow测试方法,而没有运行testMathMin测试方法。通过显式指定测试方法,可以控制测试执行的顺序。

也可以通过Java的方式创建TestSuite,然后调用TestCase,如

 

  1. // 先创建TestSuite,再添加测试方法  
  2. TestSuite testSuite = new TestSuite();  
  3. testSuite.addTest(new Junit3TestCase("testMathPow"));  
  4.    
  5. // 或者 传入Class,TestSuite会扫描其中的测试方法。  
  6. TestSuite testSuite = new TestSuite(Junit3TestCase.class,Junit3TestCase2.class,Junit3TestCase3.class);  
  7.    
  8. // 运行testSuite  
  9. TestResult testResult = new TestResult();  
  10. testSuite.run(testResult);  

 

 

 

testResult中保存了很多测试数据,包括运行测试方法数目(runCount)等。

JUnit4

与JUnit3不同,JUnit4通过注解的方式来识别测试方法。目前支持的主要注解有:

  • @BeforeClass 全局只会执行一次,而且是第一个运行
  • @Before 在测试方法运行之前运行
  • @Test 测试方法
  • @After 在测试方法运行之后允许
  • @AfterClass 全局只会执行一次,而且是最后一个运行
  • @Ignore 忽略此方法

下面举一个样例:

  1. import org.junit.After;  
  2. import org.junit.AfterClass;  
  3. import org.junit.Assert;  
  4. import org.junit.Before;  
  5. import org.junit.BeforeClass;  
  6. import org.junit.Ignore;  
  7. import org.junit.Test;  
  8.    
  9. public class Junit4TestCase {  
  10.    
  11.     @BeforeClass  
  12.     public static void setUpBeforeClass() {  
  13.         System.out.println("Set up before class");  
  14.     }  
  15.    
  16.     @Before  
  17.     public void setUp() throws Exception {  
  18.         System.out.println("Set up");  
  19.     }  
  20.    
  21.     @Test  
  22.     public void testMathPow() {  
  23.         System.out.println("Test Math.pow");  
  24.         Assert.assertEquals(4.0, Math.pow(2.0, 2.0), 0.0);  
  25.     }  
  26.    
  27.     @Test  
  28.     public void testMathMin() {  
  29.         System.out.println("Test Math.min");  
  30.         Assert.assertEquals(2.0, Math.min(2.0, 4.0), 0.0);  
  31.     }  
  32.    
  33.         // 期望此方法抛出NullPointerException异常  
  34.     @Test(expected = NullPointerException.class)  
  35.     public void testException() {  
  36.         System.out.println("Test exception");  
  37.         Object obj = null;  
  38.         obj.toString();  
  39.     }  
  40.    
  41.         // 忽略此测试方法  
  42.     @Ignore  
  43.     @Test  
  44.     public void testMathMax() {  
  45.           Assert.fail("没有实现");  
  46.     }  
  47.         // 使用“假设”来忽略测试方法  
  48.     @Test  
  49.     public void testAssume(){  
  50.         System.out.println("Test assume");  
  51.                 // 当假设失败时,则会停止运行,但这并不会意味测试方法失败。  
  52.         Assume.assumeTrue(false);  
  53.         Assert.fail("没有实现");  
  54.     }  
  55.    
  56.     @After  
  57.     public void tearDown() throws Exception {  
  58.         System.out.println("Tear down");  
  59.     }  
  60.    
  61.     @AfterClass  
  62.     public static void tearDownAfterClass() {  
  63.         System.out.println("Tear down After class");  
  64.     }  
  65.    
  66. }  

 

 

如果细心的话,会发现Junit3的package是junit.framework,而Junit4是org.junit
执行此用例后,控制台会输出

写道
Set up before class
Set up
Test Math.pow
Tear down
Set up
Test Math.min
Tear down
Set up
Test exception
Tear down
Set up
Test assume
Tear down
Tear down After class

 

 

可以看到,执行次序是@BeforeClass -> @Before -> @Test -> @After -> @Before -> @Test -> @After -> @AfterClass@Ignore会被忽略。

运行测试方法

与Junit3类似,可以在Eclipse中运行,也可以通过mvn test命令运行。

Assert

Junit3和Junit4都提供了一个Assert类(虽然package不同,但是大致差不多)。Assert类中定义了很多静态方法来进行断言。列表如下:

  • assertTrue(String message, boolean condition) 要求condition == true
  • assertFalse(String message, boolean condition) 要求condition == false
  • fail(String message) 必然失败,同样要求代码不可达
  • assertEquals(String message, XXX expected,XXX actual) 要求expected.equals(actual)
  • assertArrayEquals(String message, XXX[] expecteds,XXX [] actuals) 要求expected.equalsArray(actual)
  • assertNotNull(String message, Object object) 要求object!=null
  • assertNull(String message, Object object) 要求object==null
  • assertSame(String message, Object expected, Object actual) 要求expected == actual
  • assertNotSame(String message, Object unexpected,Object actual) 要求expected != actual
  • assertThat(String reason, T actual, Matcher matcher) 要求matcher.matches(actual) == true

Mock/Stub

Mock和Stub是两种测试代码功能的方法。Mock测重于对功能的模拟。Stub测重于对功能的测试重现。比如对于List接口,Mock会直接对List进行模拟,而Stub会新建一个实现了List的TestList,在其中编写测试的代码。
强烈建议优先选择Mock方式,因为Mock方式下,模拟代码与测试代码放在一起,易读性好,而且扩展性、灵活性都比Stub好。
比较流行的Mock有:

其中EasyMock和Mockito对于Java接口使用接口代理的方式来模拟,对于Java类使用继承的方式来模拟(也即会创建一个新的Class类)。Mockito支持spy方式,可以对实例进行模拟。但它们都不能对静态方法和final类进行模拟,powermock通过修改字节码来支持了此功能。

EasyMock

IBM上有几篇介绍EasyMock使用方法和原理的文章:EasyMock 使用方法与原理剖析使用 EasyMock 更轻松地进行测试
EasyMock把测试过程分为三步:录制、运行测试代码、验证期望。
录制过程大概就是:期望method(params)执行times次(默认一次),返回result(可选),抛出exception异常(可选)。
验证期望过程将会检查方法的调用次数。
一个简单的样例是:

 

  1. @Test  
  2. public void testListInEasyMock() {  
  3.     List list = EasyMock.createMock(List.class);  
  4.     // 录制过程  
  5.    
  6.     // 期望方法list.set(0,1)执行2次,返回null,不抛出异常  
  7.     expect1: EasyMock.expect(list.set(0, 1)).andReturn(null).times(2);  
  8.     // 期望方法list.set(0,1)执行1次,返回null,不抛出异常  
  9.     expect2: EasyMock.expect(list.set(0, 1)).andReturn(1);  
  10.    
  11.     // 执行测试代码  
  12.     EasyMock.replay(list);  
  13.         // 执行list.set(0,1),匹配expect1期望,会返回null  
  14.     Assert.assertNull(list.set(0, 1));  
  15.         // 执行list.set(0,1),匹配expect1(因为expect1期望执行此方法2次),会返回null  
  16.     Assert.assertNull(list.set(0, 1));  
  17.         // 执行list.set(0,1),匹配expect2,会返回1  
  18.     Assert.assertEquals(1, list.set(0, 1));  
  19.    
  20.     // 验证期望  
  21.     EasyMock.verify(list);  
  22. }  

 

 

 

EasyMock还支持严格的检查,要求执行的方法次序与期望的完全一致。

Mockito

Mockito是Google Code上的一个开源项目,Api相对于EasyMock更好友好。与EasyMock不同的是,Mockito没有录制过程,只需要在“运行测试代码”之前对接口进行Stub,也即设置方法的返回值或抛出的异常,然后直接运行测试代码,运行期间调用Mock的方法,会返回预先设置的返回值或抛出异常,最后再对测试代码进行验证。可以查看此文章了解两者的不同。
官方提供了很多样例,基本上包括了所有功能,可以去看看
这里从官方样例中摘录几个典型的:

  • 验证调用行为
    1. import static org.mockito.Mockito.*;  
    2.    
    3. //创建Mock  
    4. List mockedList = mock(List.class);  
    5.    
    6. //使用Mock对象  
    7. mockedList.add("one");  
    8. mockedList.clear();  
    9.    
    10. //验证行为  
    11. verify(mockedList).add("one");  
    12. verify(mockedList).clear();  
     
     
  • 对Mock对象进行Stub
    1. //也可以Mock具体的类,而不仅仅是接口  
    2. LinkedList mockedList = mock(LinkedList.class);  
    3.    
    4. //Stub  
    5. when(mockedList.get(0)).thenReturn("first"); // 设置返回值  
    6. when(mockedList.get(1)).thenThrow(new RuntimeException()); // 抛出异常  
    7.    
    8. //第一个会打印 "first"  
    9. System.out.println(mockedList.get(0));  
    10.    
    11. //接下来会抛出runtime异常  
    12. System.out.println(mockedList.get(1));  
    13.    
    14. //接下来会打印"null",这是因为没有stub get(999)  
    15. System.out.println(mockedList.get(999));  
    16.     
    17. // 可以选择性地验证行为,比如只关心是否调用过get(0),而不关心是否调用过get(1)  
    18. verify(mockedList).get(0);  
     
     

代码覆盖率

比较流行的工具是Emma和Jacoco,Ecliplse插件有eclemma。eclemma2.0之前采用的是Emma,之后采用的是Jacoco。这里主要介绍一下Jacoco。Eclmama由于是Eclipse插件,所以非常易用,就不多做介绍了。

Jacoco

Jacoco可以嵌入到Ant、Maven中,也可以使用Java Agent技术监控任意Java程序,也可以使用Java Api来定制功能。
Jacoco会监控JVM中的调用,生成监控结果(默认保存在jacoco.exec文件中),然后分析此结果,配合源代码生成覆盖率报告。需要注意的是:监控和分析这两步,必须使用相同的Class文件,否则由于Class不同,而无法定位到具体的方法,导致覆盖率均为0%。

Java Agent嵌入

首先,需要下载jacocoagent.jar文件,然后在Java程序启动参数后面加上 -javaagent:[yourpath/]jacocoagent.jar=[option1]=[value1],[option2]=[value2],具体的options可以在此页面找到。默认会在JVM关闭时(注意不能是kill -9),输出监控结果到jacoco.exec文件中,也可以通过socket来实时地输出监控报告(可以在Example代码中找到简单实现)。

Java Report

可以使用Ant、Mvn或Eclipse来分析jacoco.exec文件,也可以通过API来分析。

    1. public void createReport() throws Exception {  
    2.             // 读取监控结果  
    3.     final FileInputStream fis = new FileInputStream(new File("jacoco.exec"));  
    4.     final ExecutionDataReader executionDataReader = new ExecutionDataReader(fis);  
    5.             // 执行数据信息  
    6.     ExecutionDataStore executionDataStore = new ExecutionDataStore();  
    7.             // 会话信息  
    8.     SessionInfoStore sessionInfoStore = new SessionInfoStore();  
    9.    
    10.     executionDataReader.setExecutionDataVisitor(executionDataStore);  
    11.     executionDataReader.setSessionInfoVisitor(sessionInfoStore);  
    12.    
    13.     while (executionDataReader.read()) {  
    14.     }  
    15.    
    16.     fis.close();  
    17.                
    18.             // 分析结构  
    19.             final CoverageBuilder coverageBuilder = new CoverageBuilder();  
    20.     final Analyzer analyzer = new Analyzer(executionDataStore, coverageBuilder);  
    21.    
    22.             // 传入监控时的Class文件目录,注意必须与监控时的一样  
    23.     File classesDirectory = new File("classes");  
    24.     analyzer.analyzeAll(classesDirectory);  
    25.    
    26.     IBundleCoverage bundleCoverage = coverageBuilder.getBundle("Title");  
    27.             // 输出报告  
    28.         File reportDirectory = new File("report"); // 报告所在的目录  
    29.     final HTMLFormatter htmlFormatter = new HTMLFormatter();  // HTML格式  
    30.     final IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(reportDirectory));  
    31.             // 必须先调用visitInfo  
    32.     visitor.visitInfo(sessionInfoStore.getInfos(), executionDataStore.getContents());  
    33.     File sourceDirectory = new File("src"); // 源代码目录  
    34.             // 遍历所有的源代码  
    35.             // 如果不执行此过程,则在报告中只能看到方法名,但是无法查看具体的覆盖(因为没有源代码页面)  
    36.     visitor.visitBundle(bundleCoverage, new DirectorySourceFileLocator(sourceDirectory, "utf-8", 4));  
    37.             // 执行完毕  
    38.     visitor.visitEnd();  
    39. }  
posted @ 2017-08-26 23:49  宋文涛  阅读(474)  评论(0编辑  收藏  举报