`
airu
  • 浏览: 270800 次
  • 性别: Icon_minigender_1
  • 来自: 云南
社区版块
存档分类
最新评论

TDD 学习笔记(三)

 
阅读更多
接上一篇。这里到了比较关键的时候了。
Kent跨大步了,但是如何找到合适的步伐,还是需要不断从小步尝试。
现在看看目前的to-do lists吧。
  • 5美元 + 5美元 = 10 美元
  • 5美元 + 10法郎 = 10 美元 假设美元对法兰的汇率是 1:2


这里要注意的是两件事,首先,加法的引入,然后,汇率实现。

我们需要快速在脑子里浮现出一个场景。我们去银行兑换货币,银行提供汇率……

这里,先实现一个简单的加法测试

@Test public void  testSimpleAddition(){
    Money sum = Money.dollar(5).plus(Money.dollar(5));
    assertEquals(Money.dollar(10),sum);
}


很简单,现在来实现已经是轻车熟路了吧。

Money
public Money plus(Money addend){
    new Money(amount + addend.amount,currency);
} 

这里算是一眼就看出来的,所以没必要返回一个 Money.dollar(10)了。测试,OK。
接下来要考虑的,就是如何比较不同的货币,也就是货币转换。首先要有银行,然后呢不同的货币如何表示在一个类中?这里需要在抽象一层。
作者在书中使用了表达式, 5美元+10法郎 这就是一个表达式。我们通过银行,来把这个表达式兑换成美元,或者法郎(根据汇率)。


@Test public void  testSimpleAddition(){
    Moeny five = Money.dollar(5);
    Expression sum = five.plus(five);
    Bank bank = new Bank();
    Moeny result = bank.reduce(sum,"USD");
    assertEquals(Money.dollar(10),sum);  
}


这就是我们脑子中浮现的一个样子。好了,接下来就一个一个实现。
首先修改plus方法。
Money 类
public Expression plus(Money addend){
    new Money(amount+addend.amount,currency);
}


Expression 接口
public interface Expression{

}


Bank  类
public class Bank{
public Money reduce(Expression expression,String to){
    return new Money.dollar(10);//just fake implementation
}
}

测试一下,OK。没什么问题。但是还有fake implementation。我们仔细分析下,发现其实Expression接口与Money还是有区别的,所以,在plus的时候,应该返回一个真正的实现了Expression 的类。这里就叫做Sum吧。这样,Sum应该又有一个加数和一个被加数。
测试上:
@Test public void testSum(){
   Moeny five = Money.dollar(5);
   Expression result = five.plus(five);
   Sum sum = (Sum)result;
   assertEquals(sum.augend,five);
   assertEquals(sum.addend,five);  
}

Sum
public class Sum implements Expression{
private Money augend;
private Money addend;
public Sum(Money augend, Money addend){
    this.augend = augend;
    this.addend = addend;
}
}


Money 类中的plus可以返回一个真正的Expression类了。
public Expression plus(Money addend){
   return new Sum(this,addend);
}


这是,我们的问题终于回到这个fake implementation,让我们再写个测试,来暴露问题。
@Test public void  testReduceSum(){
    Expression sum = Money.dollar(3).plus(Money.dollar(4));
    Bank bank = new Bank();
    Moeny result = bank.reduce(sum,"USD");
    assertEquals(Money.dollar(7),sum);  
}


public class Bank{
public Money reduce(Expression expression,String to){
    Sum sum = (Sum)expression;
    return new Money(sum.augend.amount+sum.addend.amount,to);   
}
}

这下是通过测试了,但是我们看到了又臭又长的一串串 sum.augend.amount 之类的。
这样的东西出现,预示着我们应该使用Move method重构。于是把reduce方法移到Sum中。
public class Sum implements Expression{
private Money augend;
private Money addend;
public Sum(Money augend, Money addend){
    this.augend = augend;
    this.addend = addend;
}
public Money reduce(String to){
    return new Money(augend.amount + addend.amount,to);
}
}

于是Bank的reduce可以这样写

public class Bank{
public Money reduce(Expression expression,String to){
    Sum sum = (Sum)expression;
    return sum.reduce(to);
}
}

这下简洁多了。既然多了个函数,我们为此写个测试吧。

@Test public void testReduceMoney(){
    Money money = Money.dollar(1);
    Bank bank = new Bank();
    Money m = bank.reduce(money,"USD");
    assertEquals(Money.dollar(1),m);
}

这个测试很有意思,既然Money是一种表达式,那么就直接放到bank.reduce里检验。
结果问题出来了。造型错误。我们还得修改bank.reduce
public class Bank{
public Money reduce(Expression expression,String to){
    if(expression instanceof Money) return (Money)expression;
    Sum sum = (Sum)expression;
    return sum.reduce(to);
}
}

这下代码越来越难看了。这完全背离了我们的意图。不过通过这里,我们很快就能发现,Money和Sum如果都有一致的方法,就很好了。于是Expression接口多了reduce方法

public interface Expression{
    public Money reduce(String to);
}


Money需要添加此方法。(Money类实现了Expression接口)。

public Money reduce(String to){
    return this;
}


现在,Bank的reduce方法终于可以这样写了
public Money reduce(Expression expression, String to){
    return expression.reduce(to);
}

清晰很多了。测试通过,接下来是to-do lists的第二项未实现的。那就是;
5美元 + 10法郎 = 10 美元。

在写测试之前,我们一般要考虑一下如何实现,这里,我们的银行应该提供一个汇率。
这样,我们就可以转换不同币种了。
我们可以这样写测试:

@Test public void testRecuceMoneyDifferentCurrency(){
    Bank bank = new Bank();
    bank.addRate("CHF","USD",2)
    Money result = bank.reduce(Money.franc(10),"USD");
    assertEquals(Money.dollar(5),result);
}


这是对一个基本的汇率转换的测试。我们暂时还没有一个清晰的方法来实现汇率。于是就使用triangulation吧。先做一个stub实现。具体分析代码,最后实现转换是在Money中的reduce方法。
Money 类
public Money reduce(String to){
    int rate = currency.equals("CHF")&&to.equals("USD")?2:1;
    return new Money(amount/rate,to);
}

显然这是为了通过测试。
新增一个测试 :
@Test public void testBankRate(){
    Bank bank = new Bank();
    bank.addRate("USD","CHF",2); // reverse the rate
    Money result = bank.reduce(Money.dollar(10),"CHF");
    assertEquals(Money.franc(5),result);
}

显然,我们的银行没有起到作用。我们可以应该修改接口Expression中的reduce方法

public interface Expression{
    public Money reduce(Bank bank, String to);
}

于是接着改Money

public Money reduce(Bank bank, String to){
    int rate = (currency.equals("CHF"))&&(to.equals("USD"))?2:1;
    return new Money(amount/rate,to);

}

当然Sum的也要改。这就省略。
rate应该由Bank提供。
所以Extract Method到Bank类。

Bank
public int rate(){
    return (currency.equals("CHF"))&&(to.equals("USD"))?2:1;
}


Money
public Money reduce(Bank bank, String to){
    int rate = bank.rate();
    return new Money(amount/rate, to);
}


测试,还是不能通过。因为那个判断其实是个fake implementation,所以需要真正考虑如何实现了。这里,脑海里第一浮现的就是Collection类。作者使用Hashtable来存放一个key/value的键值对。这样每个转换都对应一个汇率。
修改Bank类的rate方法,添加参数。
Bank类。
private Hashtable rates;
public void addRate(String from ,String to, int rate){
    rates.put(new Pair(from,to),new Ineger(rate));
}
public int rate(String from ,String to){
    Pair key = new Pair(from,to);
    return ((Integer)rates.get(key)).intValue();
}


这里有引入一个新类,作为key的封装。我们有需要测试了。

@Test public void testkeyEquality(){
    assertEquals(new Pair("USD","CHF"),new Pair("USD","CHF"));
    assertFalse((new Pair("USD","CHF")).equals(null));
}


如果你还记得前面的货币比较的话,这里应该容易理解,我们马上知道该如何比较Pair

public class Pair{
private String from;
private String to;
public Pair(Stirng from, String to){
    this.from = from;
    this.to = to;
}

@Override
public boolean equals(Object obj){
    if(obj == null) return false;
    if(obj == this) return true;
    if(obj instanceof Pair){
        Pair newpair = (Pair)obj;
        return (from.equals(obj.from) && (to.equals(obj.to));
    }else return false;
}

@Override
public int hashCode(){
    return 0;
}


这里的euqals方法其实做过了。因为我们的测试没有放映到我需要这么做。
对于hashCode方法呢,暂时返回0,使之退化成线性查找,我们这里但求通过测试。
经过这番折腾,测试终于通过了。我们这里又想到一个测试:

@Test public void testIdentityRate(){
    assertEquals(1,(new Bank()).rate("USD","USD"));
}

这类测试属于边界测试吧。往往起到意想不到的作用。果然 ,我们又一次倒下了。
对于这类一般性问题,我们在rate里面没有考虑全面。
public int rate(String from ,String to){
    if(from.equals(to)) return 1;
    Pair key = new Pair(from,to);
    return ((Integer)rates.get(key)).intValue();
}

加上这个,就算完成rate的测试了吧。因为我们马上就要实现 5美元+10法郎=10美元的测试了。

@Test public void testMixedAddition(){
    Money five = Money.dollar(5);
    Expression sum = five.plus(Money.franc(10));
    Bank bank = new Bank();
    bank.addRate("CHF","USD",2);
    Money result = bank.reduce(sum,"USD");
    assertEquals(Money.dollar(10),result);
}

这个测试失败了。我们应该得到10美元,但是,我们得到的是15美元。问题出在Sum类。
public Money reduce(String to){
    return new Money(augend.amount + addend.amount,to);
}

一下就明白了,不同的表达式需要经过reduce,才能相加。
public Money reduce(Bank bank,String to){
    return new Money(augend.reduce(bank,to)+ addend.reduce(bank,to),to);
}

好了,现在工作了。但是我们看到,Sum中的加数与被加数同时也应该是表达式。于是
可以再测试通过的情况下,重构,把Sum变为:

public class Sum implements Expression{
private Expression augend;
private Expression addend;
public Sum(Expression augend, Expression addend){
    this.augend = augend;
    this.addend = addend;
}
public Money reduce(Bank bank,String to){
    int amount = augend.reduce(bank,to).amount +
                 addend.reduce(bank,to).amount;
    return new Money(amount,to);
}
}

既然都是面向接口了,注意Money中的plus也要改改。times也该改改
public Expression plus(Expression addend){
   return new Sum(this,addend);
}
public Expression times(int multiplier){   
    return new Money(amount*multiplier,currency);
}


然后相应的测试代码也改改:
@Test public void testMixedAddition(){
    Expression five = Money.dollar(5);
    Expression sum = five.plus(Money.franc(10));
    Bank bank = new Bank();
    bank.addRate("CHF","USD",2);
    Money result = bank.reduce(sum,"USD");
    assertEquals(Money.dollar(10),result);
}

改完以后,测试发现,Expression接口么有plus方法,这就很明显了,Expression应该有plus和times接口。加上。

Money的实现未变。
Sum的plus实现呢?暂且未知。
public Expression plus(Expression addend){
    return null;
}

于是测试通过了。我们需要一个新的测试Sum的plus,来发现我们的伪实现。
@Test public void testSumPlus(){
    Expression five = Money.dollar(5);
    Expression sum = five.plus(Money.franc(10));
    Bank bank = new Bank();
    bank.addRate("CHF","USD",2);
    Money result = bank.reduce(sum.plus(Money.franc(20),"USD");
    assertEquals(Money.dollar(20),result);
}


现在实现Sum的plus方法,其实很简单。
public Expression plus(Expression expression){
    return new Sum(this,expression);
}

测试通过。就这么简单。现在我们可能会在to-do lists 中加入,

(5美元+10法郎)*3 = 30 美元

测试代码:
@Test public void testSumPlus(){
    Expression fiveBuck = Money.dollar(5);
    Expression tenFranc= Money.franc(10);
    Bank bank = new Bank();
    bank.addRate("CHF","USD",2);
    Money result = bank.reduce(new Sum(fiveBuck,tenFranc).times(2),"USD");
    assertEquals(Money.dollar(20),result);
}




我们的times方法,同样需要对Expression方法有效。
Sum中的实现
public Expression times(int multiplier){
    return new Sum(augend.times(multiplier),addend.times(multiplier));
} 

Ok ,测试通过。写到这里,基本就告一个段落了,但是这里远远没有完。
只是我们的to-do lists空了,我们还可以添加,继续重构代码。
0
0
分享到:
评论

相关推荐

    编程学习笔记

    《编程学习笔记》 在编程领域,学习是一个永无止境的过程。这份“编程学习笔记”正是作者在学习过程中对已掌握知识的整理与总结,旨在帮助读者更好地理解和应用编程概念。笔记通常以简洁明了的方式呈现关键点,是...

    java校招学习笔记

    "java校招学习笔记"显然是针对应届毕业生或求职者准备的,旨在帮助他们掌握Java的基础知识和校招面试中常见的技术问题。这份笔记可能包含了从基础概念到进阶主题的全面概述,以提高求职者的竞争力。 首先,Java的...

    Study__TDD:个人TDD学习资料库

    埃迪的TDD学习笔记 :police_car_light: 警告不要合并任何PR。 :memo: 内容所有摘要都放在“问题”选项卡中。 当前,列出了以下内容: NHN FE개발랩摘要-Finsihed 견고한JS소프트웨어만들기-进行中测试Vue.js应用程序...

    达内微软NET学习笔记

    《达内微软NET学习笔记》是一份详实的.NET软件工程师培训资料,涵盖了.NET框架的基础知识、核心概念以及实际开发中的应用技巧。这份笔记源于作者在加拿大达内科技的培训经历,旨在与学习者共享珍贵的学习心得,由于...

    rationalrose2002学习笔记.rar_Rational Rose_rational _rose

    《Rational Rose 2002 学习笔记》是一份深入探讨软件建模工具Rational Rose 2002的教程资料。这份文档详细介绍了Rational Rose在系统分析、设计与实现中的应用,旨在帮助用户掌握这款强大的统一建模语言(UML)工具...

    java学习笔记4

    Java学习笔记第四部分主要涵盖了Java编程语言的深入学习内容,可能是继基础语法、面向对象编程之后的高级主题。"良葛格"作为编著者,可能以易懂且实用的方式阐述了这些概念。以下是根据标题和描述可能包含的知识点:...

    C#学习笔记精华,详细。

    本篇“C#学习笔记精华”旨在为初学者和进阶者提供一个全面且深入的学习指南,帮助读者掌握C#的核心概念和实用技巧。 1. **基础语法**:C#的基础包括变量、数据类型、常量、运算符、流程控制(如if语句、switch语句...

    C#学习笔记-C# Study Notes

    【C#学习笔记-C# Study Notes】 这是一份详尽的C#学习资源,旨在帮助初学者深入理解和掌握C#编程语言。这份笔记不仅包含了语言基础,还涵盖了从实际问题出发的学习过程,强调理解而非机械记忆,使得学习更加生动且...

    spring培训学习笔记

    ### Spring培训学习笔记知识点梳理 #### 一、Spring框架概览 - **启动时间与背景**:Spring项目始于2003年2月,最初的基础代码来源于书籍《Expert One-On-One J2EE Design and Development》。 - **核心特性**: -...

    python自动化测试开发框架学习笔记

    在"python自动化测试开发框架学习笔记"中,我们将深入探讨Python测试领域的核心概念和常用工具,特别是Allure报告框架。 Python作为一门流行的编程语言,因其简洁易读的语法和丰富的库支持,在自动化测试领域有着...

    Junit良葛格学习笔记

    "Junit良葛格学习笔记"很可能包含了一系列关于如何有效利用JUnit进行测试的教程和示例。下面将详细阐述JUnit的核心概念、功能以及在实际开发中的应用。 1. **JUnit简介**: JUnit是由Ernst Leiss和Kent Beck开发的...

    .NET 快速重构 - 学习笔记

    .NET 快速重构学习笔记是针对开发者提升代码质量和可维护性的重要指南。重构是一个系统性的过程,旨在改善软件设计,优化代码结构,而不改变其外在行为。在.NET开发环境中,重构是不可或缺的技能,它可以帮助开发者...

    面向对象学习笔记········

    本学习笔记将深入探讨面向对象的基本概念、原则以及常见设计模式。 1. **基本概念** - **对象**:对象是类的实例,具有属性(数据成员)和方法(函数成员)。对象是现实世界中的实体在程序中的抽象。 - **类**:...

    ssh_web开发测试程序学习笔记

    这个“ssh_web开发测试程序学习笔记”涵盖了使用这三个框架进行Web开发和测试的基本概念、步骤和最佳实践。 Struts2是MVC(Model-View-Controller)设计模式的一个实现,它提供了强大的表单处理、国际化、异常处理...

    UML学习笔记 建模语言

    ### UML学习笔记:建模语言详解 #### 统一建模语言(UML)概述 统一建模语言(UML, Unified Modeling Language)是一种被广泛应用于软件开发领域的标准化建模语言,它为软件密集型系统的可视化建模提供了一种统一的...

    appfuse 学习笔记

    AppFuse 是一个开源项目,旨在加速和简化J2EE应用程序的开发流程。由Matt Raible设计,它作为一...通过深入阅读和实践AppFuse的学习笔记,开发者可以逐步掌握其核心功能,并将其应用到实际项目中,提升开发质量和效率。

    python后端开发学习笔记,知识体系,技术栈 python Django mysql性能优化 redis 面向对象.zip

    这份学习笔记聚焦于Python后端开发的知识体系和技术栈,涵盖了Python基础、Django框架、MySQL数据库性能优化、Redis缓存以及面向对象编程等核心内容。 首先,Python是一种高级编程语言,以其简洁清晰的语法和丰富的...

    notes:学习笔记

    【标题】:“学习笔记” 【描述】:“学习笔记”通常是指个人在学习过程中记录的知识点、理解、思考和问题解答等内容的集合。这样的笔记可能涵盖了各种主题,包括但不限于编程语言、操作系统、网络技术、数据库管理...

Global site tag (gtag.js) - Google Analytics