`
feikiss
  • 浏览: 100028 次
  • 性别: Icon_minigender_1
  • 来自: 西安
社区版块
存档分类
最新评论

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。
1
0
分享到:
评论

相关推荐

    WebAudioAPIError(解决方案).md

    项目中常见的问题,记录一下解决方案

    avnet(安富利)网站详情页数据样例

    avnet(安富利)网站详情页数据样例

    1-全国各地区建筑业-二级专业承包建筑业企业利润总额2005-2012年-社科数据.zip

    该数据集涵盖了2005至2012年间全国各地区二级专业承包建筑业企业的利润总额。这些数据不仅包括了原始数据,还提供了线性插值和ARIMA填补的版本,以便于研究者能够根据不同的需求选择合适的数据形式进行分析。数据集中包含了行政区划代码、地区名称、是否属于长江经济带、经纬度信息、年份以及利润总额等关键指标。这些指标为评估企业的经营效益和盈利水平提供了重要依据,同时也反映了建筑业在不同地区的发展态势。数据来源为国家统计局,确保了数据的权威性和准确性。通过这些数据,研究者可以深入分析建筑业的经济贡献及其在宏观经济中的作用,为政策制定和行业规划提供数据支持。

    CentOS6.4X64安装Oracle11g中文2.05MB最新版本

    本文档主要讲述的是CentOS6.4 X64安装Oracle11g;在CentOS安装oracle11g比安装oracle10g简单很多,oracle可以不设置比如OS内核参数、防火墙、环境变量等,所以实施时推荐安装oracle11g。感兴趣的朋友可以过来看看

    发动机零部件质量信息反馈及处理表.docx

    发动机零部件质量信息反馈及处理表.docx

    1-全国省市县土地利用类型面板数据2009-2021年-社科数据.zip

    全国省市县土地利用类型面板数据2009-2021年是一项详尽的数据集,它基于土地利用方式和地域差异,对土地资源单元进行细致划分,反映了土地的用途、性质和分布规律。该数据集涵盖了全国各省、地级市、县的土地利用类型,包括耕地、园地、林地、交通运输用地、水域及沙地等多种土地类型。时间范围上,省级和地级市的土地利用类型面板数据覆盖2009至2021年;县级土地利用类型面板数据则从2019年开始至2021年。数据指标丰富,包括行政单位、年份以及各类土地利用的具体分类,如水田、水浇地、旱地、果园、茶园等,以及城镇村及工矿用地、交通运输用地、水域及水利设施用地等。这些数据为政府决策、规划编制以及土地资源管理提供了坚实的数据基础,有助于全面了解土地资源的利用状况,并为未来的规划和管理提供支持。

    MediaError(解决方案).md

    项目中常见的问题,记录一下解决方案

    前端跳槽突围课:React18底层源码深入剖析(完结21章)

    好课分享——前端跳槽突围课:React18底层源码深入剖析(完结21章)

    1111java后端1111Controller

    1111java后端1111Controller

    嵌入式系统开发-STM32单片机-电子春联-代码设计

    嵌入式系统开发-STM32单片机-电子春联-代码设计

    潜在失效模式及后果分析(FMEA)应用流程.docx

    潜在失效模式及后果分析(FMEA)应用流程.docx

    使用Python和Matplotlib创建动态3D圣诞树动画

    内容概要:本文详细介绍了如何使用Python和Matplotlib库创建一个动态的3D圣诞树动画。通过代码示例,展示了几何形状的创建方法,如圣诞树的形状、装饰品和星星的位置计算,以及如何通过动画更新函数实现闪烁效果。 适合人群:具有一定Python编程基础的开发者,尤其是对Matplotlib库和数据可视化感兴趣的读者。 使用场景及目标:① 学习Matplotlib库的基本用法,包括3D绘图和动画制作;② 掌握几何形状的数学建模方法,如圆锥和球体;③ 实践动画效果的实现技巧,提升编程技能。 阅读建议:本教程以具体代码示例为主,理论与实践相结合。建议读者在阅读过程中亲自编写和运行代码,逐步理解每一步骤的实现细节。

    开发一个带有 PCIe Endpoint 设备的驱动程序并实现热插拔功能.docx

    开发一个带有 PCIe Endpoint 设备的驱动程序并实现热插拔功能

    ASP+ACCESS课程教学网站信息交流与发布系统(源代码+论文+外文翻译)(源代码+论文+说明文档).zip

    【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。

    消防气压给水设备和稳压泵安装 分项工程质量验收记录表.docx

    消防气压给水设备和稳压泵安装 分项工程质量验收记录表.docx

    Cytoscape-3-10-0-windows-64bit.exe

    Cytoscape-3-10-0-windows-64bit.exe

    ASP物资管理系统设计与实现(源代码+论文)(源代码+论文+说明文档).zip

    【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。

    [net毕业设计]asp.net学生成绩管理系统(源代码+论文).zip

    【项目资源】:包含前端、后端、移动开发、操作系统、人工智能、物联网、信息化管理、数据库、硬件开发、大数据、课程资源、音视频、网站开发等各种技术项目的源码。包括STM32、ESP8266、PHP、QT、Linux、iOS、C++、Java、python、web、C#、EDA、proteus、RTOS等项目的源码。【项目质量】:所有源码都经过严格测试,可以直接运行。功能在确认正常工作后才上传。【适用人群】:适用于希望学习不同技术领域的小白或进阶学习者。可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。【附加价值】:项目具有较高的学习借鉴价值,也可直接拿来修改复刻。对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。【沟通交流】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。鼓励下载和使用,并欢迎大家互相学习,共同进步。

    电力场景输电线电缆缺陷检测数据集VOC+YOLO格式1167张8类别.zip

    文件太大放服务器下载,请务必先到资源详情查看然后下载 样本图参考:blog.csdn.net/2403_88102872/article/details/143977852 数据集格式:Pascal VOC格式+YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):1167 标注数量(xml文件个数):1167 标注数量(txt文件个数):1167 标注类别数:8 标注类别名称:["ddan_boc_tt","ddan_ct","ddan_ct_tua","ddan_ct_vatla","ddan_tran_tt","ddan_tt_mon","ddan_tt_tua","ddan_tt_vatla"]

Global site tag (gtag.js) - Google Analytics