从一次编码练习中学到的

 

昨天原本打算花半个小时左右练习写一个小程序,“求一个数的立方根”,结果花费了23个小时的时间,记录一下。

 

包含两个部分:首先简单介绍一下这个练习的过程;其次,总结一下学到的东西。

 

 

(一)练习的过程

 

这是第一个版本,先从简单的来,只求解恰好是一个整数的立方根(perfect cube root),从0开始遍历查找:

 

 

 

 

稍微测试一下,没有什么问题。然后,实现从“整数”到“浮点数”的跳跃,当然,也可以理解为类似于需求变更,“求解一个浮点数的近似立方根”,于是有了第二个版本:

 

 

测试了几个数字,没有问题,然后再多做几次测试,就没有那么自信了。

 

# test 1:

# x = 125

# counting =  4999

# the cube root of x is: 4.999

# Ques1: 计算不够准确的问题,为什么不是5?当然,如果需求就是“求解得到一个数的近似立方根,误差在正负0.1之间”的话,这段代码倒是也符合需求的,如果是真实项目的话,应该要去做KYMKnow Your Mission)了。

#

# test 2:

# x = 12345

# counting =  12345000

# can't find the cube root of x. 

# Ques2: 为什么找不到答案呢?是数太大了?还是其他原因?

#于是尝试一个小一点的数,仍然找不到解

# test 3:

# x = -987

# counting =  987001

# can't find the cube root of x.

#但是尝试一个再大一点的数,是可以找到的,说明该问题与大小无关。

# test 4:

# x = -1000

# counting =  10000

# the cube root of x is: -10.0

 

以上两个问题取其重,下一步决定解决Ques2.初步判断是遍历查找的步长太大导致的,将step0.001改为0.0001,于是有了Version2.1版本:

 

 

再运行上述查找不到的两个测试,已经能够找到结果了。

# test 1:

# x = -987

# counting =  99562

# the cube root of x is: -9.95619999999

#

# test 2:

# x = 12345

# counting =  231116

# the cube root of x is: 23.1116

#继续跑几个新的测试,又发现问题了:

# test 3:

# x = 111111111111

# ^Z

# [1]+  Stopped

# Ques3: 性能问题,看来需要改进算法了(updatesolution

#

# 先搁置这里,再运行几个其他的测试

# test 4 : x < 1 or x < epsilon

# x = 0.999

# counting =  9652

# the cube root of x is: 0.9652

#

# x = 0.001

# counting =  0

# the cube root of x is: 0.0

#

# x = 0.005

# counting =  0

# the cube root of x is: 0.0

#

# x = 0.11

# counting =  1100

# can't find the cube root of x.

#

# x = 0.12

# counting =  1200

# can't find the cube root of x.

#

# x = 0.09

# counting =  0

# the cube root of x is: 0.0

#

# x = 0.2

# counting =  2001

# can't find the cube root of x.

#

# x = 0.9

# counting =  9001

# can't find the cube root of x.

#

# x = 0.99

# counting =  9620

# the cube root of x is: 0.962

#

# x = 0.91

# counting =  9101

# can't find the cube root of x.

# Ques4: x小于1时,问题的pattern什么 x在误差本身范围内时,不会进入循环查找,这与Ques1很类似,计算准确度不够;当x小于1时,继续在0x之间查找时是查不到的,因为此时答案ansx要大。

 

先解决Ques4“当x小于1时”的问题,迭代为Version3.0.

 

运行测试,发现小于1的数也可以求解了,不过仍然不够准确,这个与Ques1是同样的问题:

# x = 1.0

# counting =  9655

# the cube root of x is: 0.9655

 

所以现在,还剩下2个问题:

Ques1:计算不够准确的问题                      -- Version4.0

Ques3:性能的问题(改进算法,使用二分法查找)  -- Version5.0

 

之后又补充了几个测试,发现一些问题:

Ques5对输入数据的有效值没有做判断 -- Version6.0(输入超过范围的数据以及非数字捕获异常)

Ques6:发现使用二分法查找后,虽然性能改善了,但是准确性却下降了,比如之前125的求解还是5呢,现在变成4.99996811523了。

# test 1:

# x = 12345678

# the cube root of x is: 231.120418163

# counting =  41

# Ques6Although the problem of large number is solved (performance), accuracy is decreased:

# test 2:

# x = 125

# the cube root of x is: 4.99996811523

# counting =  25

 

同时,现在代码已经有些冗长了,需要进行代码优化Ques7

 

另外,使用二分法查找后,原来while循环的终止条件之一“ans < termination”是否还需要?这个也不是很确定(Ques8

 

目前我只写到了Version6.0,也就是说Ques6~8还未解决:

 

对于剩下的Ques6~8的一些思考:

Ques6:性能与准确性之间的平衡问题,实际项目中,应该与用户确认具体的使用场景,然后来决定。我倒是想到个折中的办法,通过测试找到一个比较大的数字,绝对值比这个数字小的输入,使用遍历查找法,保证准确性;超过此数字时,性能优先,就使用二分法;

Ques7:代码优化,这个得需要几个迭代的优化了,一直在实现功能,还没有关注代码的简洁性,使用TDD开发的方式应该会比较好的一直保证代码的简洁吧,这个我还不熟;

Ques8:确认循环可以终止:在使用遍历查找法时ans< termination是作为循环终止的条件加上的,那么使用二分法后是否还需要呢?如果不需要,会不会出现这种情况导致循环无法终止呢:寻找平均数出现了乒乓效应?如果需要,那么,应该为增加的这个判断条件增加什么测试呢?很显然,这个规律或者证明的思路我还没有找到,做什么样的测试,在这里起到很关键的作用。

 

截止目前,我虽然做了很多测试,并且通过这些测试识别出很多问题,然后不断地在改进代码,可是这些测试都是比较随意在做的,并不系统。

 

我然后尝试,基于Version6.0的代码画了一个因果图,基于因果图可以很轻松地得出一些应该要覆盖到的测试场景,确保代码中的每一个基本的原子逻辑关系都得到了检验,这样的测试还未来得及补充,先作为Ques9记录一下吧:补充要覆盖的原子逻辑的测试场景

 

 

(二)学到的东西

 

练习虽小,还是学到不少东西,罗列一下:

 

  1. 完成初始的代码并不难,难的是后续不断优化代码、调试代码的过程;记得之前看过的一句话:“For every story you write, you need to put three into your backlog.”后两个是为了修正第一个story的。其实,不论编码还是测试的工作,都不会是一次性就能完成很好的,需要不断地修正,这是一个迭代的、不断地学习的过程。

  2. 不论是test first development,还是test after development测试在驱动代码优化方面起着重要的作用。如这个小练习所示,很多驱动代码改进的Questions都是由于运行了相应的测试发现的。随着测试的深入、代码的优化,我们对这段代码的质量也有了新的认识。

  3. 能够给测试人员提出具体而中肯的测试建议Developer,一定自身做了很多的测试。假设,现在有测试人员要测试我编写的这个小程序,我一定非常希望这个tester可以帮我特别关注Ques6~9,同时告知我已经测试过的内容。这对测试人员开展基于风险的测试、提高测试效率非常重要。而相反,假如我只停留在Version2.0,自己只做了几个简单的测试,一切正常,并且自认为质量还可以,那么当测试人员问及我的测试建议时,我只能说,“你随便测测吧”,或者“测试是你的事儿,就交给你了”,根本给不出什么有价值的建议。

  4. 了解任务完成的质量状况,远比了解任务完成的进度更重要:想一想,你身边的Developer,他们的代码是写到哪一个version就截止了呢,Version2.0还是Version6.0?他们是否做了足够的测试了呢?每一天开站会时:当开发人员说“我昨天完成了这个task”时,指的又是什么含义呢?仅仅指的是这个task已经实现了?还是这个task已经实现、并且经过了XX程度的反复测试、然后目前的质量状况是XX的样子、还需要进行哪些的测试?

  5. 暴露问题的能力很重要,让项目进程可视化的同时,让质量也可视化。每个重视质量的软件项目都需要这样的人才:由于他们的存在,与质量相关的高风险的问题总会被及时地指出来。当其他人在实践看板,看到一张张卡片有序不紊地从左向右flow,大家都在关注还有多少任务没有完成时,你可以指出来,隐藏在看板深处的一条隐形的质量红线,它正反方向流动,在某一个环节,尽管表面上任务已经完成,卡片已经移走,可是质量状况多么令人堪忧:已经测试了什么、还有哪些需要测试、已经发现了什么问题,还有哪些令人担忧的风险。。。当所有人都以为整个项目进展顺利的时候,你会站出来,让大家注意到原来我们的质量债务已经堆积如山了。

  6. 探索和学习的能力很重要。无论开发还是测试工作,都面临大量的未知,需要去探寻问题是什么、可能的解决方案是什么、可能存在的问题是什么、有哪些潜在的风险。解决未知领域的问题,从来都不是靠Plan Based Method,你以为事先设计好story的验收条件,后续只要按此开展测试,确保每个AC Pass就可以了?你都不晓得前方会遇到哪些问题,不知道探索了这些问题后又会出现什么新的问题,基于验收条件是没有办法基于前一步的测试反馈去调整后面的测试方向的,它充其量只能起到一个“保底”的作用:确保最基本的简单场景可以运行。

  7. 黑盒与白盒相结合、测试分析与测试执行相结合,不要拘泥于测试的形式和流程。如果是测试人员完全从需求、从黑盒的角度来测试,会设计什么样的tests呢?边界值、正负数、整数和小数是比较容易cover到的,那么是否会想到x<epsilonx<1的情况呢?是否会测试到“找不到解”的情况呢?可能就引人而异了,也许这个时候看看代码会得到更多的思路。

  8. 一条普遍真理,无需多说:测试无法穷尽,必须基于风险

  9. 另外,关于学习这件事,我所倡导的是PRE学习法,即Practice-Reflection-ExplicationPractice大家都在做,每天有大量的编码或测试活动,可是每次是否从这些实践活动中学习到新的东西了呢?这就是RE的能力了。

  10. 最后,细心的读者一定看出来了,不是只有测试人员才需要学习testingskills开发人员也需要学习测试技能和测试思维,包括探索的技能、测试分析的技能、建模的技能等等,对了,具备一些基本的正确的测试价值观,这很重要。

Comment Box is loading comments...