代码改变世界

TDD实验1

2008-12-04 11:44  敏捷的水  阅读(2245)  评论(13编辑  收藏  举报

用户故事:我们要找一个女朋友,这个女朋友要能够管理财务。

1. 先创建一个工程,引入NUnit,我们这里用这个做单元测试。

image

2. 设定,调试时启动Nunit

image

3. 写一个测试类

首先我们需要一个有一个能管财务女朋友。

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using NUnit.Framework;

namespace FindGrilFriendTest

{

    [TestFixture]

    public class TestGrilFriend

    {

        [Test]

        public void IsFinanceAbility()

        {

            GrilFriend myGril = new GrilFriend();

            Assert.IsNotNull(myGril);

        }

    }

}

4.现在启动Debug

image

 5. 我们发现这个都无法编译通过,因为没有GrilFriend类

image

6. 我们利用重构工具,生成GrilFriend类

image

 namespace FindGrilFriendTest

{

    public class GrilFriend

    {

        public GrilFriend()

        {

            throw new NotImplementedException();

        }

    }

    [TestFixture]

    public class TestGrilFriend

    {

        [Test]

        public void IsFinanceAbility()

        {

            GrilFriend myGril = new GrilFriend();

            Assert.IsNotNull(myGril);

        }

    }

}

我们把这个类提到一个单独的文件叫GrilFriend.cs, 运行测试

image

7. 为了让测试通过,我们去掉构造函数里的那句

using System;

namespace FindGrilFriendTest

{

    public class GrilFriend

    {

        public GrilFriend()

        {

        }

    }

}

测试通过,看到绿色,好爽

image

 

8. 先在我们测试grilFriend是否有财务能力。

using System;

using NUnit.Framework;

namespace FindGrilFriendTest

{

 

   [TestFixture]

    public class TestGrilFriend

    {

        [Test]

        public void IsFinanceAbility()

        {

            GrilFriend myGril = new GrilFriend();

            Assert.IsNotNull(myGril);

            bool strongGrilFriend=myGril.IsFinanceAbility();

            if (strongGrilFriend)

            {

                Console.Out.WriteLine("Yes,she is suit for my grilfriend");

            }

        }

    }

}

编译失败,因为我们的GrilFriend类里没有IsFinanceAbility方法。同样重构
image
 

using System;

namespace FindGrilFriendTest

{

    public class GrilFriend

    {

        public GrilFriend()

        {

        }

        internal bool IsFinanceAbility()

        {

            throw new NotImplementedException();

        }

    }

}

编译明显失败。修改为

    internal bool IsFinanceAbility()

        {

            return true;

        }

    }

测试Pass,绿色,爽。

9. 现在我们的这个girlFriend不喜欢财务,所以我们现在加一个功能,就是这个girlFriend找个女佣来算帐。

internal bool IsFinanceAbility()
       {

            Servant myServant = new Servant();

            if (myServant.CouldCalc())

            {

                return true;

            }

            else

            {

                return false;

            }

        }

10. 编译没法通过,因为这个Servant还没有创建,我们可以立即创建一个Servant类,加上CouldCalc方法,但问题是,这些都让我们自己加的话,我们要累死了,所以我们想让Servant这个类让另一个开发人员Bill来写,Bill说:“好,但是我用一天的时间”,哎,难道我们需要等一天?这时候我们想到了接口,于是我们修改代码

using System;

namespace FindGrilFriendTest

{

    public class GrilFriend

    {

        public IServant myServant;

        public GrilFriend()

        {

        }

        internal bool IsFinanceAbility()

        {

            if (myServant.CouldCalc())

            {

                return true;

            }

            else

            {

                return false;

            }

        }

    }

}
 

using System;

using System.Collections.Generic;

using System.Text;

namespace FindGrilFriendTest

{

    public interface IServant

    {

        bool CouldCalc();

    }

}

这时候,我们的girlFriend需要测试这个仆人会不会计算,但我们如何如何判断,上面的方法无法判断,仆人不做任何工作,只要Return true,就骗过girlFriend了。现在我们改写这个测试方法为,如果我传一个数,他能把这个数乘以2,我们就认为他可以.

using System;

using System.Collections.Generic;

using System.Text;

 

namespace FindGrilFriendTest

{

    public interface IServant

    {

        long CouldCalc(int i);

    }

}

这个时候接口定义完了,Bill说:“我将来开发的这个类就提供这个功能,我现在腐败去了”。
 
这个时候,我们又想到,是呀,GirlFriend也不能这么测她的财务能力,我们重构测试代码如下

using System;

using NUnit.Framework;

namespace FindGrilFriendTest

{

   [TestFixture]

    public class TestGrilFriend

    {

        [Test]

        public void IsFinanceAbility()

        {

            GrilFriend myGril = new GrilFriend();

            Assert.IsNotNull(myGril);

            Random r = new Random(1000);

            int i = r.Next();

            long strongGrilFriend=myGril.IsFinanceAbility(i);

            if (strongGrilFriend==2*i)

            {

                Console.Out.WriteLine("Yes,she is suit for my grilfriend");

            }

        }

    }

}

Test失败,修改grilFriend类

using System;
 

namespace FindGrilFriendTest

{

    public class GrilFriend

    {

        public IServant myServant;

        public GrilFriend()

        {

 

        }

        internal long IsFinanceAbility(int i)

        {

            return myServant.CouldCalc(i);

        }

    }

}

测试,还是失败

image

看错误信息,是有对象没有实例化,我们Debug一下,发现 return myServant.CouldCalc(i)出错; myServant没有实例化,是因为Bill还没有开发具体类,但是现在把Bill叫回来,还是继续等待,看来都不是好方法,这个时候,我们有的人可能觉得我自己先写一个类来模拟一下,可是如果这样,当Bill完成后,我们还的需要改代码,这个时候,一个东西迅速出现在脑海里,那就是Mock

现在我们修改测试类

using System;

using NUnit.Framework;

using Rhino.Mocks;

namespace FindGrilFriendTest

{

   [TestFixture]

    public class TestGrilFriend

    {

        [Test]

        public void IsFinanceAbility()

        {

            GrilFriend myGril = new GrilFriend();

            Assert.IsNotNull(myGril);

            Random r = new Random(1000);

            int i = r.Next();

            MockRepository mockRepository = new MockRepository();

            IServant mockServant = mockRepository.CreateMock<IServant>();

            Expect.Call(mockServant.CouldCalc(i)).Return(2 * i);

            mockRepository.ReplayAll();

            myGril.myServant = mockServant;

            long strongGrilFriend=myGril.IsFinanceAbility(i);

            mockRepository.VerifyAll();

            if (strongGrilFriend==2*i)

            {

                Console.Out.WriteLine("Yes,she is suit for my grilfriend");

            }

 

        }

    }

}

修改grilFriend类,这里把internal改为public

using System;

 

namespace FindGrilFriendTest

{

    public class GrilFriend

    {

        public IServant myServant;

        public GrilFriend()

        {

 

        }

        public long IsFinanceAbility(int i)

        {

            if (myServant != null)

            {

                return myServant.CouldCalc(i);

            }

            else

            {

                return 0;

            }

        }

    }

}

 

测试,哇,通过了。绿色,好爽.

image

至此,我们完成了我们的功能,找到一个适合自己的女朋友。Bill这个时候还在腐败呢。

 

总结:

这是我自己用TDD的方式第一次体验,当然我对TDD还不怎么了解,现在感觉是看到绿色很爽,写完代码有了测试代码了,而且过程中多次修正自己的设计。不好的地方是时间大大超过预期。如果哪位仁兄能够提供一些更好的TDD guideline,再次非常感谢。

 

问题:这里我们看到,找servant应该是girlFriend类自己的事,现在却需要我们传进去,可是内部如果实例化,我们又没法Mock,因为Mock只能写在测试代码里。这让我想到了IOC, 但是还没实验,下节我试试IOC能否解决。

 

本文写于:2008-12-03 23:50 王德水