代码质量(单元测试+代码审查)

发布时间:2021-08-05 09:04:17



代码质量
1. 单元测试2. 代码审查


1. 单元测试

单元测试的目的:尽早在尽量小的范围内暴露错误
错误率恒定定律,一定量的代码,必然会产生一定量的BUG
a) 刚写完一个方法就发现BUG,修改只要几分钟;方法提供给其他人使用后,再发现BUG,加上双方修改,review,再联调,预计耗时可能需要半天。
b) 提交测试之后,由测试发现,需要定位原因提交BUG,双方修改BUG,review再联调,再打包测试,然后重新测试.可能需要耗费一整天
c) 而发布到线上之后再发现问题,就不只是耗费时间成本的问题,可能造成的是资损和用户流失


单元测试是对代码进行各个分支的review和深度分析过程,是"白盒测试"过程,可以有效的在很短的时间内,发现一些关键BUG
再紧的项目都要有设计、编码、测试和发布这些环节,如果说项目紧不写单测,看起来编码阶段省了一些时间,但必然会在测试和线上花掉成倍甚至更多的时间来修复。


集成测试再全面也需要单元测试
任何东西都是有死角的,例如清洗一个机箱。如果用集成测试的概念,总有一些死角是洗不到的。这些就需要单元测试来覆盖。


把代码拷贝testcase里面,然后在testcase里面调用拷贝出来的代码
理由: 后续代码改动无法监控,case永远为true,没有意义
代码里面起spring-boot容器,把服务拉起来后,从controller层做http调用
理由: 这是集成测试,不是单元测试!


测试程序类的类名通常是固定格式的,为XXXTest 形式,其中XXX 就是 被测试程序的类名。
测试方法的名称是有固定格式的,通常为“testXXX”,其中XXX 就是被测试方法的名称。虽然在JUnit 4 版本中并没有严格规定,但是最好采用同样规范。
输出测试错误信息:中文只有在测试不通过的时候才输出
assertEquals(“输出结果和期望值不同”,“Hello World”, s);
判断两个参数的值是否相等
assertEquals
测试结果true 和false
boolean b=new UserServiceImpl().login(“Tom”, “456123”);
assertTrue(b);
测试结果是否为null assertNotNull
assertNull(userDAO);
判断两个参数是否引用同一个对象
assertSame|assertNotSame 测试单例模式


JUnit 4 版本中新增了一个方法,那就是“assertThat”方法,使用该方法可以完 成上面所讲的所有方法的功能。
public static void assertThat( [value], [matcher statement] ); 其中value 表示想要测试的变量值。matcher statement 是使用Hamcrest 匹配符来表 达的对前面变量所期望的值的声明
http://hamcrest.org/ Matchers that can be combined to create flexible expressions of intent


assertThat()常用的方法还有:
a)
assertThat( n, allOf( greaterThan(1), lessThan(15) ) ); n满足allof()里的所有条件
assertThat( n, anyOf( greaterThan(16), lessThan(8) ) );n满足anyOf()里的任意条件
assertThat( n, anything() ); n是任意值(任意值都可以通过测试)
assertThat( str, is( “ellis” ) ); str是is()里的内容
assertThat( str, not( “ellis” ) ); str不是not()里的内容
b)
assertThat( str, containsString( “ellis” ) ); str包含containsString()里的内容
assertThat( str, endsWith(“ellis” ) ); str以endsWith()里的内容结尾
assertThat( str, startsWith( “ellis” ) ); str以startsWith()里的内容开始
assertThat( n, equalTo( nExpected ) ); n与equalTo()里的内容相等
assertThat( str, equalToIgnoringCase( “ellis” ) ); str忽略大小写后与equalToIgnoringCase()里的内容相等
assertThat( str, equalToIgnoringWhiteSpace( “ellis” ) );str忽略空格后与equalToIgnoringWhiteSpace()里的内容相等
c)
assertThat( d, closeTo( 3.0, 0.3 ) );d接*于3.0,误差不超过0.3
assertThat( d, greaterThan(3.0) );d大于3.0
assertThat( d, lessThan (10.0) );d小于10.0
assertThat( d, greaterThanOrEqualTo (5.0) );d大于或等于5.0
assertThat( d, lessThanOrEqualTo (16.0) );d小于或等于16.0
d)
assertThat( map, hasEntry( “ellis”, “ellis” ) );map里有一个名为ellis的key,其值为ellis
assertThat( iterable, hasItem ( “ellis” ) );iterable(例如List)里包含值ellis
assertThat( map, hasKey ( “ellis” ) );map有一个名为ellis的key
assertThat( map, hasValue ( “ellis” ) );map里包含一个值ellis


另外,还有如下这些常用注解,使测试起来更加方便:


    @Ignore: 被忽略的测试方法@Before: 每一个测试方法之前运行@After: 每一个测试方法之后运行@BeforeClass: 所有测试开始之前运行@AfterClass: 所有测试结束之后运行

测试程序是否发生异常 @Test(expected=java.lang.ArithmeticException.class)
测试程序运行时间 @Test(expected=java.lang.ArithmeticException.class,timeout=100)
测试方法的初始化和销毁
“@Before”注解的方法将在每一个测试方法之前执行,
“@After”注解的方 法将在每一个测试方法之后执行。
测试类的初始化和销毁
“@BeforeClass”注解标明的方法就是一个测试类初始化方法,当执行该测试类时 将首先执行该方法。
“@AfterClass”注解标明的方法就是测试类的销毁方法,当执行完 测试类中的所有测试方法后,将执行该方法。


Alibaba Java Code Guidelines, Sonar, ErrorProne, Jacoo
powermockito是改字节码
mockito是代理 spy可以部分mock,spy方法需要使用doReturn方法才不会调用实际方法。
父类对象继承的属性可以用反射和子类对象来创建


目前针对服务端单测的实现方式
可采取
Easymock
PowerMock
Mockito
样例:


@Service
public class DemoService{
@Autowired
private DemoDao demoDao;

public boolean getString(int type){
int result = demoDao.getStringByType(type);
if(result == 1){
return true;
}else {
return false;
}
}
}

public class DemoServiceTest{

@InjectMocks
private DemoService demoService;

@Mock
private DemoDao demoDao;

@Test(dependsOnMethods = "getStringMock" )
private void testGetString(){
Assert.assertEquals(demoService.getString(1),true);
Assert.assertEquals(demoService.getString(2),false);
}

private int getStringMock(){
when(demoDao.getStringByType(1)).thenReturn(1);
when(demoDao.getStringByType(2)).thenReturn(2);
}
}

直接new XXX(),然后调用里面方法做测试验证
样例:


public class DemoUtils{

public static boolean convert(String str) throws Exception{
if ("true".equals(str)){
return true;
}else if("false".equals(str)){
return false;
}else {
throw new Exception("convert fail");
}
}
}

public class DemoUtilsTest{
@Test
public void testConvert_true(){
DemoUtils demoUtils = new DemoUtils();
Assert.assertEquals(demoUtils.convert("true"),true);
}

@Test
public void testConvert_false(){
DemoUtils demoUtils = new DemoUtils();
Assert.assertEquals(demoUtils.convert("false"),false);
}

@Test
public void testConvert_exception(){
DemoUtils demoUtils = new DemoUtils();
try{
demoUtils.convert("exception");
Assert.assertTrue(false); // 异常用例走不到这里,若走到这里,则失败
}catch (Exception e){
Assert.assertEquals(e.getMessage(),"convert fail");
}
}
}

不可采取


错误示例1 ##


public class PatternUtilsTest {
@Test
public void test1() {
String content = "12.00 ";
System.out.println(PatternUtils.group(content, "style="color:red;">(.*?)<\/td><\/tr>", 1));
}
}

总结:
没有assert
没有调用项目代码,只是一段调试代码,调试自己的正则而已,对代码没有一点监控作用


正确写法:
调用项目中使用到这段正则的方法(mock或者直接new都可以)
assert正则匹配后的结果


错误示例 ##


public class MysqlAdaptServiceTest {
@Test(dataProvider = "telSheet")
public void testConvertTelSheetToVoiceRecord(TelSheet telSheet) {
VoiceCallRecord voiceCallRecord = service.convertTelSheetToVoiceRecord(telSheet);
assertEquals(voiceCallRecord.getTime(), telSheet.getCallStart());
assertEquals(voiceCallRecord.getDialtype(), telSheet.getCallType() == 1 ? "主叫" :
telSheet.getCallType() == 2 ? "被叫" : telSheet.getCallType() == 3 ? "呼叫转移" : "未知");
assertEquals(voiceCallRecord.getDurationsec(), telSheet.getCallSeconds());
assertEquals(voiceCallRecord.getLocation(), telSheet.getCallAddress());
assertEquals(voiceCallRecord.getLocationtype(), telSheet.getTelType());
assertEquals(voiceCallRecord.getPeernumber(), telSheet.getOtherNumber());
assertEquals(voiceCallRecord.getCreatetime(), telSheet.getCreateTime());
assertEquals(voiceCallRecord.getLastmodifytime(), telSheet.getLastModifyTime());
}
}

总结:
代码不要对预期数据(expect)做任何处理


assertEquals(voiceCallRecord.getDialtype(), telSheet.getCallType() == 1 ? “主叫” :
telSheet.getCallType() == 2 ? “被叫” : telSheet.getCallType() == 3 ? “呼叫转移” : “未知”);
这个assert中,把代码处理逻辑搬到testcase中,试问,假如这个地方失败了,是代码里面的转换错了呢?还是预期结果的转换处理错了呢?
所以,不要对预期结果做任何的改动
正确写法:


在预期结果里面,增加 callTypeName字段,分别构造4个case,覆盖"主叫",“被叫”,“呼叫转移”,"未知"的场景
assert的时候,直接取预期结果和转化后的做对比


错误示例2 ##


public class PhoneQueryServiceTest {
@SuppressWarnings("unchecked")
@DataProvider
public Object[][] voiceCallRecord() throws IOException {
List voiceCallRecordList = objectMapper.readValue(ClassLoader.getSystemResource("PhoneQueryService/voice-call-record.json"), new TypeReference>() {
});
List> mapList = objectMapper.readValue(ClassLoader.getSystemResource("PhoneQueryService/voice-call-record.json"), List.class);
Object[][] data = new Object[voiceCallRecordList.size()][1];
for (int i = 0, size = voiceCallRecordList.size(); i < size; i++) {
voiceCallRecordList.get(i).setPhonenumberid(UUID.fromString((String) mapList.get(i).get("Phonenumberid")));
data[i][0] = voiceCallRecordList.get(i);
}
return data;
}

@Test(dataProvider = "voiceCallRecord")
public void testGetVoiceCallRecords(VoiceCallRecord voiceCallRecord) throws DataCarrierException {
List voiceCallRecordListExcepted = Collections.singletonList(voiceCallRecord);
when(teleDataDao.getCallRecords(eq(TENANT_ID), eq(voiceCallRecord.getPhonenumberid()), any(), any()))
.thenReturn(voiceCallRecordListExcepted);
List voiceCallRecordListActual =
service.getVoiceCallRecords(TENANT_ID, voiceCallRecord.getPhonenumberid().toString(), new Date(), new Date());
assertEquals(voiceCallRecordListActual, voiceCallRecordListExcepted);
}

public List getVoiceCallRecords(String tenantId, String phoneid, Date startdate, Date
enddate) throws DataCarrierException {
List callRecords;
try {
if (!mysqlAdaptService.needGetFromMysql(tenantId, phoneid)) {
UUID phoneNumberId = UUID.fromString(phoneid);
callRecords = teleDataDao.getCallRecords(tenantId, phoneNumberId, startdate, enddate);
if (callRecords == null || callRecords.size() == 0) {
throw new DataCarrierException(DataCarrierExceptionCode.EMPTY_QUERY_RESULT, "找不到对应的记录");
}
} else {
TelLine line = telLineMapper.selectByPrimaryKey(Long.valueOf(phoneid));
List telSheets = telSheetMapper.selectByLineId(getIndex(line.getPeopleID()), phoneid,
startdate, enddate);
callRecords = telSheets
.stream()
.peek(dataCarrierEncryptor::decryptAfterRetrieve)
.map(mysqlAdaptService::convertTelSheetToVoiceRecord)
.collect(Collectors.toList());

if (callRecords == null || callRecords.size() == 0) {
throw new DataCarrierException(DataCarrierExceptionCode.EMPTY_QUERY_RESULT, "找不到对应的记录");
}
}

callRecords.stream()
.peek(voiceCallRecord ->{
voiceCallRecord.setLocation(convertLocation(voiceCallRecord.getLocation()));//通话地点:将区号转为地名
voiceCallRecord.setDialtype(convertDetailType(voiceCallRecord.getDialtype()));//通话类型
voiceCallRecord.setTime(convertTime(voiceCallRecord.getTime()));//毫秒处理
}).collect(Collectors.toList());
} catch (InvalidQueryException e) {
LOGGER.error("getVoiceCallRecords meet invalid query exception: ", e);
throw new DataCarrierException(DataCarrierExceptionCode.INVALID_QUERY_EXCEPTION, e);
} catch (Exception e) {
LOGGER.error("getVoiceCallRecords meet exception: ", e);
throw new DataCarrierException(DataCarrierExceptionCode.GET_PEOPLE_BY_USER_ID_FAIL, e);
}
return callRecords;
}
}

总结:
voiceCallRecordListExcepted和voiceCallRecordListActual 共享同一个内存空间,所以assertEquals(voiceCallRecordListActual, voiceCallRecordListExcepted);恒定是true,就是一段没有用的assert


代码中有很多convert,还有各类的分支,都没有覆盖
正确的写法


几个convert拆开,单独写单测,如:


public class PhoneQueryService{
@Autowired
private CpToCityReader cpToCityReader;
public String convertLocation(String originLocation) {
Map cpToCityLists = cpToCityReader.getCpToCityMap();
if (StringUtils.isNotBlank(originLocation)) {
Pattern patternWithZero = Pattern.compile("0\d{2,3}");
Pattern patternWithoutZero = Pattern.compile("\d{3}");
Matcher matcher = patternWithZero.matcher(originLocation);
if (matcher.find()) {
originLocation = matcher.group();
return cpToCityLists.getOrDefault(originLocation, originLocation);
} else {
matcher = patternWithoutZero.matcher(originLocation);
if (matcher.find()) {
originLocation = "0"+matcher.group();
return cpToCityLists.getOrDefault(originLocation, originLocation);
}
}
}
return originLocation;
}
}


public class PhoneQueryServiceTest {
@Test
public void testConvertLocation()throws DataCarrierException {
ConcurrentHashMap map = new ConcurrentHashMap<>();
map.put("0571","杭州");
when(cpToCityReader.getCpToCityMap()).thenReturn(map);
Assert.assertEquals(service.convertLocation("0571"),"杭州");
Assert.assertEquals(service.convertLocation("571"),"杭州");
Assert.assertEquals(service.convertLocation("[571]"),"杭州");
Assert.assertEquals(service.convertLocation("[571]杭州"),"杭州");
Assert.assertEquals(service.convertLocation("[0571]"),"杭州");
Assert.assertEquals(service.convertLocation("杭州"),"杭州");
}
}

server方法覆盖
a) 数据驱动文件包含请求参数和预期结果
b) 预期结果不作变更直接和方法调用的结果做对比


public class XXXModel{
List request;
List expect;
/** getter and setter */
}

public class PhoneQueryServiceTest {
@DataProvider(name = "voiceCallRecord")
public Iterator voiceCallRecord() throws IOException {
List objectList = new ArrayList<>();
List xxxModelList = objectMapper.readValue(ClassLoader.getSystemResource("xxx.json"), new TypeReference>() {
});
for (XXXModel xxxModel : xxxModelList){
objectList.add(new Object[]{xxxModel});
}
return objectList.iterator();
}

@Test(dataProvider = "voiceCallRecord")
public void testGetVoiceCallRecords(XXXModel xxxModel) throws DataCarrierException {
List xxxxRequest = xxxModel.getRequest();
List xxxxExpect = xxxModel.getExpect();
when(teleDataDao.getCallRecords(eq(TENANT_ID), eq(voiceCallRecord.getPhonenumberid()), any(), any()))
.thenReturn(xxxxRequest);
List voiceCallRecordListActual =
service.getVoiceCallRecords(TENANT_ID, voiceCallRecord.getPhonenumberid().toString(), new Date(), new Date());
assertEquals(voiceCallRecordListActual, xxxxExpect);
}
}

2. 代码审查

所有人都要经过代码审查。并且很正规的:这种事情应该成为任何重要的软件开发工作中一个基本制度。并不单指产品程序??所有东西。它不需要很多的工作,但它的效果是巨大的。
从代码审查里能得到什么?
1.防止bug混入,他不是最重要的一点
2.代码审查的最大的功用是纯社会性的。如果你在编程,而且知道将会有同事检查你的代码,你编程态度就完全不一样了。你写出的代码将更加整洁,有更好的注释,更好的程序结构??因为你知道,那个你很在意的人将会查看你的程序。没有代码审查,你知道人们最终还是会看你的程序。但这种事情不是立即发生的事,它不会给你带来同等的紧迫感,它不会给你相同的个人评判的那种感受。
3.还有一个非常重要的好处。代码审查能传播知识。在很多的开发团队里,经常每一个人负责一个核心模块,每个人都只关注他自己的那个模块。除非是同事的模块影响了自己的程序,他们从不相互交流。这种情况的后果是,每个模块只有一个人熟悉里面的代码。如果这个人休假或??但愿不是??辞职了,其他人则束手无策。通过代码审查,至少会有两个人熟悉这些程序??作者,以及审查者。审查者并不能像程序的作者一样对程序十分了解??但他会熟悉程序的设计和架构,这是极其重要的。
4.最重要的一个原则:代码审查用意是在代码提交前找到其中的问题??你要发现是它的正确。在代码审查中最常犯的错误??几乎每个新手都会犯的错误??是,审查者根据自己的编程*惯来评判别人的代码。
5.第二个误区就是人们感觉一定要说点什么(才算是做了代码审查)。代码审查的第二个易犯的毛病是,人们觉得有压力,感觉非要说点什么才好。你知道作者用了大量的时间和精力来实现这些程序??不该说点什么吗?不,你不需要。只说一句“哇,不错呀”,任何时候都不会不合适。如果你总是力图找出一点什么东西来批评,你这样做的结果只会损害自己的威望。当你不厌其烦的找出一些东西来,只是为了说些什么,被审查人就会知道,你说这些话只是为了填补寂静。你的评论将不再被人重视
6.第三个误区就是速度。。你不能匆匆忙忙的进行一次代码审查??但你也要能迅速的完成。

相关文档

  • 单片机课设中期报告_本科毕业设计中期检查报告.doc
  • win10+Anaconda+tensorflow+opencv+pycharm
  • C++ SetTimer
  • 心理学专业大学分数线文科生学心理学难吗
  • 属狗金牛座的人性格解析是怎么样的
  • 【天坑】springboot打包成war,部署到tomcat,访问404
  • 秋游漓江的高中生秋游作文
  • 辩论赛场上的十八个忌讳
  • 有关幼儿园校本研修专题的总结
  • 盛世嫡妃喜欢女主的有哪些人
  • 周公解梦梦见抱小孩好不好
  • 怎么让霉菌彻底排出来 霉菌可以自己用棉签弄出来吗
  • chage命令详解
  • 虚假广告的负面影响高考英语作文
  • 教师学习心得体会汇总5篇
  • 软件测试管理
  • 白菜炝锅面怎么做好吃
  • 编写一个算法,其功能时给一维数组a输入任意6个整数,假设为5,7,4,8,9,1,然后建立一个如图3_4所示的方阵,并打印出来(屏幕输出)
  • tensorflow读取本地MNITS_data失败的原因
  • 春天怕上火 教你这几个常用招式去火的方法很好用
  • 在计算预付款起扣点时要不要扣除暂定金额
  • 台式电脑老是重启咋回事
  • 乌班图docker容器日志清理_ubuntu 存储清理
  • iOS中集成ijkplayer视频直播框架(转载非常详细)
  • 小金鱼历险
  • 【fork/exec /proc/self/exe: no such file or directory】namespace里面mount /proc 后,退出后要重新mount
  • 想不出祝福语?六一儿童节祝福语短信大全
  • Java 冒泡排序(原理以及代码案例)
  • 对医生表扬信范文
  • 名门暗战剧情介绍大结局1-30集分集剧情
  • 猜你喜欢

  • 单位试用员工转正申请书怎么写
  • 开关电流市场前景预测及投资规划分析报告(目录)
  • 2010年四川省成都市七中外地生招生考试数学试卷
  • 黄体酮联合寿胎丸治疗早期先兆流产的临床分析
  • 大连新科科技有限公司企业信用报告-天眼查
  • 压胶服装项目可行性分析报告范本参考
  • 2015-2016学年甘肃省敦煌市郭家堡中学七年级(上)期末英语试卷(解析版)
  • 最新人教版英语八下Unit 2《I’ll help to clean up the city park》课件1
  • 脆皮茄子怎么炸才脆
  • 有你同行真好
  • 二年级上册语文《从森林里带走什么》教案
  • c语言指针详解_每日干货丨C语言指针详解
  • 产后多久来月经正常 产后第一次月经的注意问题
  • 怎样才能将微博里的好友添加到微信中去
  • 2019-2020年九年级数学下学期第三次月考试题
  • 北京源深节能技术有限责任公司宁夏分公司企业信用报告-天眼查
  • 唯美受欢迎的唐诗作品
  • 【优质文档】幼儿园小班健康教案:感冒了-实用word文档 (2页)
  • 2016建筑工程最新规范标准目录
  • 03、创业计划书模版(仅供参考汇编
  • 自强不息成语毛笔书法作品图片
  • GPRS、GSM遥测终端机
  • 计算机软件技术基础4-1 操作系统
  • 2018—2019学年度七年级英语教学工作计划
  • 中国汽车产业竞争力现状分析
  • 人事人员岗位晋升标准考核表
  • 冀教版五年级下册语文试题-第五单元提升练*冀教版含答案
  • 盐城奥祥工艺包装有限公司企业信用报告-天眼查
  • 面部精油
  • 个人工作计划20XX年新学期教师工作计划范文
  • 净土财富(武汉)投资管理有限公司临沧分公司企业信用报告-天眼查
  • 2010年新liyi_7_游戏开发技术(2-综合举例)
  • 2018-2019届高考英语全国卷一二轮复*备考:高考英语专题复*--阅读理解技巧-学术小金刚系列
  • 关于用内外造句
  • 清河县晨尧绒毛制品有限公司企业信用报告-天眼查
  • 小学议论文作文:蜘蛛为什么不吃死苍蝇转载7
  • 四川省高等教育人才培养质量和教学改革项目课程课件共27页
  • 飞得更高_小学六年级作文800字
  • 2018高中历史 第十二课 文艺复兴巨匠的人文风采同步训练2 岳麓版必修3
  • 【精品模板】063TGp_medical_digital_v2医学化工类PPT模板
  • 浅谈化工电气连接部位发热消除方法
  • 倾听小溪的心声作文推荐2篇
  • 电脑版