当前位置: 代码迷 >> 驱动开发 >> OOCamp-测试驱动开发
  详细解决方案

OOCamp-测试驱动开发

热度:74   发布时间:2016-04-28 10:42:32.0
OOCamp--测试驱动开发
   现在有类似这样一个需求:需要提供一个简单类库,以供其他开发者调用。现在进行Tasking,最简单的需求,这个类中应该拥有一个value记录长度值,也应该有一个单位unit来记录相应的单位,对于一个length对象来说,用户只关心我拿到这个对象后怎么用,比如,我两个对象可以比较是否相等,是否可以相加,对于其length的value和unit来说,也许用户并不关心他们的行为(至少现在是这样的),所以完全没必要为也不应该其提供相应的getter/setter 方法。现在我们来实现两个Length对象比较是否相等的行为。

  下面的测试用例我们很容易想到:
  1. 1m = 1m
  2. 1m = 100cm
  3. 1m != 2m
  4. 1m = 1000mm

   接着我们就需要开始写测试代码了:
import org.junit.Test;import static org.junit.Assert.*;import static org.hamcrest.CoreMatchers.*;public class LengthTest {    @Test    public void should_1_m_equals_1_m(){        Length length1 = new Length(1,"m");        Length length2 = new Length(1,"m");        assertThat(length1.equals(length2),is(true));    }}

这时候编译是不通过的,因为我们就没有Length类,不过IDE能够很快的帮我们完成这件事儿,创建号Length类以后呢,跑测试,失败了。看来我们需要重写equals方法,为了让测试通过,我们可以先最简单的实现equals方法,代码如下:
public class Length {    private int value;    private String unit;    public Length(int value, String unit) {        this.value = value;        this.unit = unit;    }        @Override    public boolean equals(Object obj){        Length anotherLength = (Length) obj;        return (this.unit.equals(anotherLength.unit)&&this.value == anotherLength.value);    }}

好,接下来我们继续第二个测试用例:
  @Test    public void should_1m_equals_100_cm(){        Length length1 = new Length(1,"m");        Length length2 = new Length(100,"cm");        assertThat(length1.equals(length2),is(true));            }

跑测试,失败。继续改equals方法:
    @Override    public boolean equals(Object obj){        Length anotherLength = (Length) obj;        if(anotherLength.unit.equals("cm")){            return this.value * 100 == anotherLength.value;        }        return (this.unit.equals(anotherLength.unit)&&this.value == anotherLength.value);    }

运行,成功,然后依次类推,将余下的测试按照刚才的模式写完:
package com.lee.oocamp.blog;import org.junit.Test;import static org.junit.Assert.*;import static org.hamcrest.CoreMatchers.*;public class LengthTest {    @Test    public void should_1_m_equals_1_m(){        Length length1 = new Length(1,"m");        Length length2 = new Length(1,"m");        assertThat(length1.equals(length2),is(true));    }        @Test    public void should_1_m_equals_100_cm(){        Length length1 = new Length(1,"m");        Length length2 = new Length(100,"cm");        assertThat(length1.equals(length2),is(true));            }    @Test    public void should_1_m_not_equal_2_m(){        Length length1 = new Length(1,"m");        Length length2 = new Length(2,"m");        assertThat(length1.equals(length2),is(false));            }    @Test    public void should_1_m_equals_1000_mm(){        Length length1 = new Length(1,"m");        Length length2 = new Length(1000,"mm");        assertThat(length1.equals(length2),is(true));            }}

Length类中的equals方法代码:
    @Override    public boolean equals(Object obj){        Length anotherLength = (Length) obj;        if(anotherLength.unit.equals("cm")){            return this.value * 100 == anotherLength.value;        }else if(anotherLength.unit.equals("mm")){            return this.value * 1000 == anotherLength.value;        }else{            return this.value*1 == anotherLength.value;        }    }

运行单元测试,全部通过。兴奋之余,似乎少了些什么?是的!1m=100cm正确,但是验证100cm=1m了么?1000mm = 1m 似乎也没有验证?继续添加测试用例:

@Test    public void should_1000_mm_equals_1_m(){        Length length1 = new Length(1000,"mm");        Length length2 = new Length(1,"m");        assertThat(length1.equals(length2),is(true));    }        @Test    public void should_100_cm_equals_1_m(){        Length length1 = new Length(100,"cm");        Length length2 = new Length(1,"m");        assertThat(length1.equals(length2),is(true));    }    

运行,测试失败。为什么呢?因为我们只对this.value 做了从m向其他单位的转换,却并没有做从mm或cm向其他单位的转换。继续修改我们的实现代码,我们要让测试全部通过!

这时,我们想,我们既要由m向mm转换,又要由mm向m转换,为什么不在生成对象的时候就全部实现统一的转换呢?顺着这个思路我们可以继续往下走,由于有单元测试做保证,所以我们可以随意修改我们的实现。但是记着,改动不要太大,时刻记着运行单元测试,小步快跑是测试驱动开发的秘笈。

修改后Length代码如下:
public class Length {    private int value;    private String unit;    public Length(int value, String unit) {        this.value = getValue(unit,value);        this.unit = unit;    }        private int getValue(String unit, int value) {        int result = 0;        if(unit.equals("m")){            result = value * 1000;        }else if(unit.equals("cm")){            result = value * 10;        }else if(unit.equals("mm")){            result = value * 1;        }        return result;    }    @Override    public boolean equals(Object obj){        Length anotherLength = (Length) obj;       return this.value == anotherLength.value;    }}

运行测试用例,全部通过!说明我们的测试是可行的,实现也是正确的。但是不和谐的因素出现了,在getValue中有太多的if-else 了!一个有良好设计风格的程序员肯定会想方设法的去消灭这些if-else。 好,我们继续重构(别忘了,我们有充分的单元测试做保证,因为我们的代码是由测试驱动出来的,只要测试通过了,代码就没问题,所以不要担心会把功能重构丢了。)getValue中的unit其实可以用枚举变量来代替,重构后代码清单如下:
Length类:public class Length {    private int value;    private Length() {             }    public static Length createLength(int value, UNIT unit) {        Length length = new Length();        length.value = unit.getTheValue(value);                return length;    }    @Override    public boolean equals(Object obj){        boolean result = false;        Length anotherLength = (Length) obj;        result = this.value == anotherLength.value;        return result;    }}

枚举UNIT:
public enum UNIT {    M(1000),CM(10),MM(1);    int radio;        UNIT(int radio){        this.radio = radio;    }        public int getTheValue(int value) {        int result = value * this.radio;        return result;    }}

当然,由于构造器设置为了私有的,Length由简单对象工程来生成,我们也需要修改我们相应的单元测试用例。修改完成后我们发现测试类中也存在大量重复性代码,是时候对测试类进行重构了,重构后代码如下:
import org.junit.Test;import com.lee.oocamp.Length;import static org.junit.Assert.*;import static org.hamcrest.CoreMatchers.*;public class LengthTest {    @Test    public void should_1_m_equals_1_m(){        compareTwoLengthObj(1,UNIT.M,1,UNIT.M,true);    }        @Test    public void should_1_m_not_equal_2m(){        compareTwoLengthObj(1,UNIT.M,2,UNIT.M,false);    }        @Test    public void should_1_m_not_equal_1cm(){        compareTwoLengthObj(1,UNIT.M,1,UNIT.CM,false);    }    @Test    public void should_1_m_equals_100cm(){        compareTwoLengthObj(1, UNIT.M, 100, UNIT.CM ,true);    }        @Test    public void should_1_m_equals_1000mm(){        compareTwoLengthObj(1, UNIT.M, 1000, UNIT.MM ,true);    }        @Test    public void should_100_cm_equals_1m(){        compareTwoLengthObj(100, UNIT.CM, 1, UNIT.M ,true);    }        @Test    public void should_1000_mm_equals_1m(){        compareTwoLengthObj(1000, UNIT.MM, 1, UNIT.M ,true);    }    @Test    public void should_2000_mm_not_equals_1m(){        compareTwoLengthObj(2000, UNIT.MM, 1, UNIT.M ,false);    }        private void compareTwoLengthObj(int valueOfLength1,UNIT unitOfLength1,int valueOfLength2,UNIT unitOfLength2,boolean expect) {        Length length1 = Length.createLength(valueOfLength1,unitOfLength1);        Length length2 = Length.createLength(valueOfLength2,unitOfLength2);        assertThat(length1.equals(length2),is(expect));    }    }

即使又新加了几个测试用例,是不是看着也更清爽了呢?

end。
  相关解决方案