已锁定 主题: 程序员为什么不写单元测试
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2007-07-09
我很多都不用写junit测试的.只有在底层开发/基础类时才写.
|
|
返回顶楼 | |
发表时间:2007-07-09
不错选择.虽然我只对lib写单元测试.
|
|
返回顶楼 | |
发表时间:2007-07-09
经过一次失败的项目,觉得测试的重要性了,学习中....
测试的实施,我觉得可以通过行政手段来促进,搞个测试经理专门负责每个人的测试代码,谁不搞或者谁搞不好那就不好意思了-罚,呵呵! 通过一两个项目就能把测试注入到企业文化中,最重要的是带动! |
|
返回顶楼 | |
发表时间:2007-07-09
lovevirus 写道 经过一次失败的项目,觉得测试的重要性了,学习中....
测试的实施,我觉得可以通过行政手段来促进,搞个测试经理专门负责每个人的测试代码,谁不搞或者谁搞不好那就不好意思了-罚,呵呵! 通过一两个项目就能把测试注入到企业文化中,最重要的是带动! 用行政手段的话,这样会引起反抗的,提出TDD的k.Beck说:“让人反抗TDD的方法就是强迫人写TestCase。” 用培训加鼓励的方法比较好吧,当然还需要有一些有公司中有威望有号召力的人(程序员)来引导。 |
|
返回顶楼 | |
发表时间:2007-07-09
对于我来说:
写TestCase是个非常难的过程 它集成了设计,分析,代码,重构,规范,抽象,推理,经验。 远不是一个还在发愁怎么样用eclipse 插件开发spring 的新手能够理解的。 我在推行ttd时能够上手的人也是有很长的阵痛期的。 |
|
返回顶楼 | |
发表时间:2007-07-09
抛出异常的爱 写道 对于我来说:
写TestCase是个非常难的过程 它集成了设计,分析,代码,重构,规范,抽象,推理,经验。 远不是一个还在发愁怎么样用eclipse 插件开发spring 的新手能够理解的。 我在推行ttd时能够上手的人也是有很长的阵痛期的。 你说的TDD后面隐藏的东西的确是这么多,但是TDD就是引导你去分析,设计,重构。它的名字就告诉你拉,测试驱动开发,由写测试自然而然地做出一些复杂的事情。这个过程应该不叫难,就像人会呼吸一样,呼吸难吗?不难,可是整个过程其中就隐藏了很多很多过程,很多机制。 这里的分析是对需求的理解,在XP中表现为用户故事。而抽象,规范,设计,这些都是包含重构的过程中的。而对TDD来说比较重要的就是对代码坏味的嗅觉,这是个经验问题,也是个水平问题。而TDD就是要让你的代码保持一个简单,清晰的状态,简单清晰的代码意味着什么:结构良好(设计方面),耦合度低(可测试性高,规范性强),简单易读(当然易读是在了解代码的结构的前提),易于维护(易读简单的好处)。 如果让刚刚进入公司的人,比如学生,一上来就用TDD,之后他会觉得这个很自然,就像我国的母语汉语一样,的确是复杂,但是你就是在这个环境里长大的那就自然而然地学会了,也不会有什么别扭的,反而觉得不用TDD会别扭。 |
|
返回顶楼 | |
发表时间:2007-07-09
我写的:用肺呼吸的编程。。。(隐喻)
PS:才发现大约有半年没写代码了。有什么错误请指出 --------------------------- TDD的设计: 写一个代码重要的是有什么样的条件, 什么样的输入、输出就可以了。 对于TDD那么代码的意义不同了, 是个有逻辑的有血有肉的意义的动作, 不然你写不出一个合理的测试。 例子: 代码: 把所有的A表中flage为1的记录取出来, 把Mprice的值加在一起除以总数值放入所有记录的Tprice中 测试驱动: 有 Apple记录 卖了10元 已收到款 标价为0; Bick记录 卖了20元 已收到款 标价为20; Cake记录 志了30元 未收到款 标价为30; 把所有收到款的货物的平均价存入A B C的标价中去 Apple记录 卖了10元 已收到款 标价为15; Bick记录 卖了20元 已收到款 标价为15; Cake记录 志了30元 未收到款 标价为15; TDD的分析: 上面说了有血有肉的测试,但一个逻辑如果只有一步叫什么逻辑? 引申出来一个逻辑一共有几步? 有多少种可能的变化? 有什么样的非法输入? 有什么样的异常异外? 以前这样的事都是由设计师来完成的, 他们吐血的用以前的经验去考虑去猜各种的情况, 但这种神人必竟是少数, 大多数人都是会出错的, 变更也就跟着来了。 测试驱动先想出一个可能作出测试, 完成这个测试之后 再想出一个可能再作出测试2, 完成这个测试之后 如果没考虑到3那也没关系,什么时候想起3 再作出测试3 完成测试3的成本几乎与完成测试2的成本一样多 而要是以前那不是大一点点的多 如果改了一次之后不再改的话也就忍了 但以我的经验,如果改过一次的程序120%还会被改第二回。 那时的的成本是个巨大的数字大约会以天为单位 改第三回时是以周为单位, 上面的代码中变化 代码:flage为0的记录不变 测试驱动:不改动未收到货款的标价 Apple记录 卖了10元 已收到款 标价为15; Bick记录 卖了20元 已收到款 标价为15; Cake记录 志了30元 未收到款 标价为30; 问了一下业务部的人员。。。。变更? 代码:flage为0的记录Mprice = Tprice; 测试驱动:未收到货款的标价等于卖价。 加一条记录D Disk记录 卖了40元 未收到款 标价为30; 结果: Apple记录 卖了10元 已收到款 标价为15; Bick记录 卖了20元 已收到款 标价为15; Cake记录 志了30元 未收到款 标价为30; Disk记录 卖了40元 未收到款 标价为40; TDD的代码: 测试代码应该是比逻辑代码要多, 大约是一个if一个测试 每个循环的出口都要有二个测试 程序的代码 应该把罗辑只考虑你写的测试目的 而不考虑所有的其它问题 如可能异常, 多种可能性, 都不考虑——单单只考虑你要测试的目的。 一个方法的测试代码如果太多了, 那么一定是你把太多的任务放在一个方法中了。 那么就会有重构的必要。 例子: 经过无数次小变化上面的小小逻辑变的成为: 一个由20个if组成的 3个大嵌套循环 (下面内容再考虑一下。我也是TDD的新手) TDD的重构: (见gigx译的:《重构》) TDD的规范: TDD的抽象: TDD的推理: TDD的经验: |
|
返回顶楼 | |
发表时间:2007-07-10
抛出异常的爱 写道 我写的:用肺呼吸的编程。。。(隐喻)
PS:才发现大约有半年没写代码了。有什么错误请指出 --------------------------- TDD的设计: 写一个代码重要的是有什么样的条件, 什么样的输入、输出就可以了。 对于TDD那么代码的意义不同了, 是个有逻辑的有血有肉的意义的动作, 不然你写不出一个合理的测试。 例子: 代码: 把所有的A表中flage为1的记录取出来, 把Mprice的值加在一起除以总数值放入所有记录的Tprice中 测试驱动: 有 Apple记录 卖了10元 已收到款 标价为0; Bick记录 卖了20元 已收到款 标价为20; Cake记录 志了30元 未收到款 标价为30; 把所有收到款的货物的平均价存入A B C的标价中去 Apple记录 卖了10元 已收到款 标价为15; Bick记录 卖了20元 已收到款 标价为15; Cake记录 志了30元 未收到款 标价为15; ...... 不是很能看懂你的伪代码,我以我的理解写了代码,你看看能不能表达你的意思。我写出来没有你说的那么复杂。 ///// 初始需求情况的测试代码表达 /////////////////////////////////////////////////////////////// public void testCFillTprice() { RecordList A = new RecordList(); Record _RecordA = new Record(10,true); Record _RecordB = new Record(20,true); Record _RecordC = new Record(30,false); A.add(_RecordA);A.add(_RecordB);A.add(_RecordC); A.fillTprice(); assertEquals(15, _RecordA.getTprice()); assertEquals(15, _RecordB.getTprice()); assertEquals(15, _RecordC.getTprice()); } class Record{ public Record(int sellingPrice, boolean state){ } public int getTprice(){return 15;} } class RecordList { public void add(Record rec) { } public void fillTprice() { } } 以上代码用了伪实现。然后重构消除重复代码。得到: ///// 重构后 /////////////////////////////////////////////////////////////// public class TestRecord extends TestCase { public void testCFillTprice() { RecordList A = new RecordList(); Record _RecordA = new Record(10,true); Record _RecordB = new Record(20,true); Record _RecordC = new Record(30,false); A.add(_RecordA);A.add(_RecordB);A.add(_RecordC); A.fillTprice(); assertEquals(15.0, _RecordA.getTprice()); assertEquals(15.0, _RecordB.getTprice()); assertEquals(15.0, _RecordC.getTprice()); } } public class RecordList { private List<Record> records = new ArrayList<Record>(); public void add(Record record){ records.add(record); } private int getSelledRecord() { int numOfSelled = 0; for(Record rec : records){ if(rec.isState()) ++numOfSelled; } return numOfSelled; } private double getMprice(){ double mprice = 0; for(Record rec : records){ if(rec.isState()) mprice += rec.getSellingPrice(); } return mprice; } public void fillTprice(){ Record.setTprice(getMprice()/getSelledRecord()); } } public class Record { private double sellingPrice = 0; private boolean state = false; private static double tprice = 0; public Record(double sellingPrice, boolean state) { this.sellingPrice = sellingPrice; this.state = state; } public double getSellingPrice() { return sellingPrice; } public double getTprice() { return tprice; } public static void setTprice(double tprice) { Record.tprice = tprice; } public boolean isState() { return state; } public void setState(boolean state) { this.state = state; } } 测试通过,业务逻辑实现完成。之后如你说的业务规则变动,测试随着更改,然后出现了重复代码,继续重构,于是得到: ///// 业务规则变更 /////////////////////////////////////////////////////////////// public class TestRecord extends TestCase { public void testCFillTprice() { RecordList A = new RecordList(); Record _RecordA = new Record(10,true); Record _RecordB = new Record(20,true); Record _RecordC = new Record(30,false); A.add(_RecordA);A.add(_RecordB);A.add(_RecordC); A.fillTprice(); assertEquals(15.0, _RecordA.getTprice()); assertEquals(15.0, _RecordB.getTprice()); assertEquals(30.0, _RecordC.getTprice()); } } public class RecordList { private List<Record> records = new ArrayList<Record>(); public void add(Record record){ records.add(record); } private int getSelledRecord() { int numOfSelled = 0; for(Record rec : records){ if(rec.isState()) ++numOfSelled; } return numOfSelled; } private double getMprice(){ double mprice = 0; for(Record rec : records){ if(rec.isState()) mprice += rec.getSellingPrice(); } return mprice; } public void fillTprice(){ for(Record rec : records){ rec.setTprice(getMprice()/getSelledRecord()); } } } public class Record { private double sellingPrice = 0; private boolean state = false; private double tprice = 0; public Record(double sellingPrice, boolean state) { this.sellingPrice = sellingPrice; this.state = state; } public double getSellingPrice() { return sellingPrice; } public double getTprice() { return tprice; } public void setTprice(double tprice) { if(state) this.tprice = tprice; else { this.tprice = sellingPrice; } } public boolean isState() { return state; } public void setState(boolean state) { this.state = state; } } 这就是我的理解不知道楼上的怎么看,我写出的代码并没有你说的那么复杂,刚刚十二点看到你回复后想了一下,随便写了点代码,还未彻底重构完,代码还有点味道,一些代码没有测试到。现在先睡觉了明天考试,明天接着讨论。 |
|
返回顶楼 | |
发表时间:2007-07-10
klyuan 写道 wutao8818 写道 测试的环境很难做。
比如和数据库相关的测试。由于有多张关联表,要模拟这个数据环境很麻烦。也许比业务逻辑的代码还要多。 这个我会专题来讲 期待,这也是我觉得程序员们不愿写测试的原因之一。 |
|
返回顶楼 | |
发表时间:2007-07-10
“代码:”
是代码应该完成的事。 本应该是设计师的活。 在TDD中这种详设被溶入了测试的编写之中 “测试驱动:” 是业务的实际意义,这都是非编程人员,提出的需求。 就是由需求直接变代码的过程。 现实中会到例子。 PS:这种例子很简单不用写测试用脑子也能想出代码不过比干巴巴的由一个if组成的逻辑更像是真实的。 第一个测试是这样的: public void testRecordListCanSaveRecord() { RecordList recordList = new RecordList(); recordList.add(new Record()); assertEquals(1, recordList.size()); recordList.add(new Record()); assertEquals(2, recordList.size()); } 用eclipse自动生成两个类。 代码略:只要能通过就行; 这个测试主要目的与方法名一样 为什么只用size这个方法呢? 那就是由于经验了。 (对于一系列的存储我都喜欢用size来) |
|
返回顶楼 | |