文/陈刚 at 2006年4月13日 from
www.ChenGang.com.cn
一、前言
今天我把文章的名称改了一下,想把它写成关于TDD实践的一系列文章。前一篇是设计,这一篇开始是开发。
TDD我是闻名已久,在过去实际开发中也经常用junit来写单元测试,但真正的TDD却从来没有尝试过。不过单元测试写得久了,发现TDD确实有它诱人的地方,也许它真的可以带我领略一个新的编程世界。于是,又再次翻开BOB的那本〈敏捷**开发〉(去年只看了一半),现在就学用结合吧。
在BOB那本书中,初始那个保龄球的TDD实践例子,不错!很详细。不过,我觉着写得过于冗长了,我在昨晚是一目十行看完的。BOB无非想通过这个例子告诉我们几件关于TDD的重要观念:(1)先写测试,再让测试通过。(2)从测试来考虑问题,也就是TDD的本意--面向测试编程。(3)测试应该自顶向下(4)测试为重构提供了正确性的验证,测试和重构是TDD中两条不可缺少的腿。
我认为TDD至少有如下好处:
(1)看问题的角度发生了变化,从测试角度看问题,让你的程序灵活性更高。一个类,测试代码是一个使用者,程序中的其他类是第二个使用者,所以这个类,就必须要灵活到适应两个使用者。而灵活带来的是解偶和可扩展的好处。
(2)即刻得到了单元测试代码。单元测试代码对于软件来说,就象安全帽对于建筑工人的重要性一样。而且单元测试代码就是最好的文档。
不过我的一些不同的观点: TDD不应该抛开最初的概要设计。
我周未喜欢去户外玩,在户外可变性很大,但我喜欢有一个计划,而在实际户外时再根据情况灵活应变改变计划。我认为TDD也类似,TDD是一种很讲究实际的编程方法,但初始的概要设计还是要有的,它至少让我们对开发有了一个大致的路线图。然后,我们再在TDD中根据实际情况再调整,但设计的路线图让我们不至于迷路。
上面一堆啰嗦话讲完了,让我们开始开发吧。
二、开发PhoneManager类
1、先写PhoneManager的测试类。
在这里我面临一个选择,是先写界面呢?还是先写底层API?我想,先写界面很难写测试代码,而且界面在设计时已经定下来了,基本不会变了。所以权衡了一下,还是先写底层API吧,根据设计图上的界面和各种UML图,API写起来有法可依。
于是我按照BOB大叔“ 自顶而下”的教导,先选择了PhoneManager这个管理着Phone和PhoneGroup的类。
另注:“自顶而下”的教导是完全正确的,因为TDD中编程结构不确定很大。如果为了便于写测试代码而选择“从下向上”,你会发现写到“上”的时候,忽然需要调整结构,“下”面就要做大调整,这时白费工就做大发了。就象梳头一样,从下往上梳你会死很惨。再用一句俗语来比喻:“毛主席得个感冒,全国人民都得痛哭三天”^_^
从功能上说addPhone应该是比较早用到的方法,所以先对它写测试代码。如下图:
说明:
(1)TDD就是这样:正式的类还没写,测试的代码就写好了,我们下一步做的就是要使它个测试正确通过
(2)很明显PhoneManager在系统应该只有一个实例,所以它应该用单例模式。
(3)一个Phone应该对应一个手机号码,所以在构造函数里就把手机号传入了。
2、让测试通过。
界面里一大堆的错误,我们一一把它修正了。还好Eclipse的操作很方法,一点红叉就能给你创建缺少的类、方法什么。 在这里我体验到先写测试的一个好处,用Eclipse的快速修正要比一步步的创建类少打很键盘^_^。
在创建正式类的过程中,我发现了上图程序以及设计中的几个错误:
(1)13818886666超过int范围,所以应该定义它为long型
(2)第32句错了。应该是 assertEquals(1,mgr.getPhones().size());
OK,我创建好类,并且快速编写了正式的代码,然后运行TestPhoneManager,通过! 时间不超过十分钟,我感觉比先写代码后写测试要更快一些。不过这里的程序还是比较简单,也许这些不能做为TDD优秀的完全证据,以后再慢慢的深入体会吧。
下面给出所有代码:
3、测试addPhone的特殊情况
(1)加入null,应该加不进去。
运行测试发现失败。mgr.getPhone().size()返回的是2,这是受到了上一个测试加入的new Phone(13888816666L)的影响,因为mgr是单例,所以setUp虽然在每个方法前都会运行一次,但得到的实例的都是同一样。
有人可能觉得那就改一下测试代码assertEquals(2,mrg.getPohnes().size())好了,这样不就通过了吗。但我们要谨记 写单元测试的两个原则:独立性和细粒度。testAddNullPhone绝对不要受到testAddPhone里数据的影响。我觉得最好的解决办法是,给PhoneManager加一个clear方法,已清空其内的Phone对象,测试代码唯持不变,在tearDown方法中执行mgr.clear();。
对了mgr.getPhone().size()返回的是2,一个是testAddPhone加入的,另一个是则是因为Set允许加入null值,所以我们得在mgr.addPhone方法里加入一个null判断。
另外,在PhoneManager中加入
运行测试,"生命的绿色"出现^_^,通过!
写这些测试代码很快,修正也相当快,不过写文章太慢了。我的TDD的基本过程就是这样子的了,等我把代码写完了再一起帖出来吧,wait............
OK,两个多小时后,完成了三个类和相应的测试代码。其中在PhoneGroup和Phone之间互设时,为了防止死循环,做了相对较较复杂的判断。不过在写这里的时候,我发现以前的ITreeNode类的抽象类AbstractTreeNode中关于互设的部分可能有错误,所以我在这里没有让PhoneGroup和Phone再去实现ITreeNode接口,必竟这里的树结点也就两层,相对简单一些。
然后我又把每个测试类的main方法删除了,因为Eclipse有自己的runner,JUnit自带的text runner用不上。也把setUp.super()删除了,这是一个空实现,要和不要都一样,不如删除更干净。为了方便,我的测试代码是放在同一目录(src/java)下的,我还要把它移动不同目录(scr/test)的同一包下。另外,概要设计图不用忙着更新,留到最后吧。就象一张地图有些地方标错了,没必要马上重新弄出一张新的,等旅程全部结束后,再把全面更正后的地图给后继者用就行了。
最后总结:这是我第一次TDD,感觉确实很爽。我自己的体会,“先写测试再实现的”和“先实现再写测试”相比有这样几个好处:(1)编程速度更快(2)思路更清晰(3)每向前走一步都很有安全感(因为测试代码在后面支持着我)(4)测试代码更全面(以前已经先实现了后,经常不想写测试代码,偷点懒用调试或输出点Log,人工判断一下就了事)。
好了,给出全部代码如下,仅供参考。先列出测试代码,从测试代码可以看到了解API实现的功能,最后再给出各类的实现代码。
import junit.framework.TestCase;
public class TestPhoneGroup extends TestCase{
private PhoneGroupgroup;
protected void setUp() throws Exception{
group = new PhoneGroup( " 桂林 " );
}
public void testGetName(){
assertEquals( " 桂林 " ,group.getName());
}
public void testAddPhone_GetPhones(){
Phonep1 = new Phone( 13888813331L );
Phonep2 = new Phone( 13888813332L );
Phonep2_same = new Phone( 13888813332L );
assertEquals( true ,group.addPhone(p1));
assertEquals( false ,group.addPhone(p1));
assertEquals( true ,group.addPhone(p2));
assertEquals( false ,group.addPhone(p2_same));
assertEquals( 2 ,group.getPhones().size());
assertEquals(group,p1.getPhoneGroup());
assertEquals(group,p2.getPhoneGroup());
}
public void testRemovePhone(){
Phonep1 = new Phone( 13888813331L );
assertEquals( true ,group.removePhone(p1.getNumber()));
assertEquals( 0 ,group.getPhones().size());
assertEquals(PhoneGroup.NONE,p1.getPhoneGroup());
assertEquals( false ,group.removePhone(p1.getNumber()));
}
}
一、前言
今天我把文章的名称改了一下,想把它写成关于TDD实践的一系列文章。前一篇是设计,这一篇开始是开发。
TDD我是闻名已久,在过去实际开发中也经常用junit来写单元测试,但真正的TDD却从来没有尝试过。不过单元测试写得久了,发现TDD确实有它诱人的地方,也许它真的可以带我领略一个新的编程世界。于是,又再次翻开BOB的那本〈敏捷**开发〉(去年只看了一半),现在就学用结合吧。
在BOB那本书中,初始那个保龄球的TDD实践例子,不错!很详细。不过,我觉着写得过于冗长了,我在昨晚是一目十行看完的。BOB无非想通过这个例子告诉我们几件关于TDD的重要观念:(1)先写测试,再让测试通过。(2)从测试来考虑问题,也就是TDD的本意--面向测试编程。(3)测试应该自顶向下(4)测试为重构提供了正确性的验证,测试和重构是TDD中两条不可缺少的腿。
我认为TDD至少有如下好处:
(1)看问题的角度发生了变化,从测试角度看问题,让你的程序灵活性更高。一个类,测试代码是一个使用者,程序中的其他类是第二个使用者,所以这个类,就必须要灵活到适应两个使用者。而灵活带来的是解偶和可扩展的好处。
(2)即刻得到了单元测试代码。单元测试代码对于软件来说,就象安全帽对于建筑工人的重要性一样。而且单元测试代码就是最好的文档。
不过我的一些不同的观点: TDD不应该抛开最初的概要设计。
我周未喜欢去户外玩,在户外可变性很大,但我喜欢有一个计划,而在实际户外时再根据情况灵活应变改变计划。我认为TDD也类似,TDD是一种很讲究实际的编程方法,但初始的概要设计还是要有的,它至少让我们对开发有了一个大致的路线图。然后,我们再在TDD中根据实际情况再调整,但设计的路线图让我们不至于迷路。
上面一堆啰嗦话讲完了,让我们开始开发吧。
二、开发PhoneManager类
1、先写PhoneManager的测试类。
在这里我面临一个选择,是先写界面呢?还是先写底层API?我想,先写界面很难写测试代码,而且界面在设计时已经定下来了,基本不会变了。所以权衡了一下,还是先写底层API吧,根据设计图上的界面和各种UML图,API写起来有法可依。
于是我按照BOB大叔“ 自顶而下”的教导,先选择了PhoneManager这个管理着Phone和PhoneGroup的类。
另注:“自顶而下”的教导是完全正确的,因为TDD中编程结构不确定很大。如果为了便于写测试代码而选择“从下向上”,你会发现写到“上”的时候,忽然需要调整结构,“下”面就要做大调整,这时白费工就做大发了。就象梳头一样,从下往上梳你会死很惨。再用一句俗语来比喻:“毛主席得个感冒,全国人民都得痛哭三天”^_^
从功能上说addPhone应该是比较早用到的方法,所以先对它写测试代码。如下图:
说明:
(1)TDD就是这样:正式的类还没写,测试的代码就写好了,我们下一步做的就是要使它个测试正确通过
(2)很明显PhoneManager在系统应该只有一个实例,所以它应该用单例模式。
(3)一个Phone应该对应一个手机号码,所以在构造函数里就把手机号传入了。
2、让测试通过。
界面里一大堆的错误,我们一一把它修正了。还好Eclipse的操作很方法,一点红叉就能给你创建缺少的类、方法什么。 在这里我体验到先写测试的一个好处,用Eclipse的快速修正要比一步步的创建类少打很键盘^_^。
在创建正式类的过程中,我发现了上图程序以及设计中的几个错误:
(1)13818886666超过int范围,所以应该定义它为long型
(2)第32句错了。应该是 assertEquals(1,mgr.getPhones().size());
OK,我创建好类,并且快速编写了正式的代码,然后运行TestPhoneManager,通过! 时间不超过十分钟,我感觉比先写代码后写测试要更快一些。不过这里的程序还是比较简单,也许这些不能做为TDD优秀的完全证据,以后再慢慢的深入体会吧。
下面给出所有代码:
import
junit.framework.TestCase;
/**
* @author chengang2006-4-13
*/
public class TestPhoneManager extends TestCase{
private PhoneManagermgr;
public static void main(String[]args){
junit.textui.TestRunner.run(TestPhoneManager. class );
}
@Override
protected void setUp() throws Exception{
super .setUp();
mgr = PhoneManager.getInstance();
}
public void testAddPhone(){
Phonephone = new Phone( 13888816666L );
mgr.addPhone(phone);
assertEquals( 1 ,mgr.getPhones().size());
assertEquals(phone,mgr.getPhone( 13888816666L ));
}
}
/**
* @author chengang2006-4-13
*/
public class TestPhoneManager extends TestCase{
private PhoneManagermgr;
public static void main(String[]args){
junit.textui.TestRunner.run(TestPhoneManager. class );
}
@Override
protected void setUp() throws Exception{
super .setUp();
mgr = PhoneManager.getInstance();
}
public void testAddPhone(){
Phonephone = new Phone( 13888816666L );
mgr.addPhone(phone);
assertEquals( 1 ,mgr.getPhones().size());
assertEquals(phone,mgr.getPhone( 13888816666L ));
}
}
import
java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author chengang2006-4-13
*/
public class PhoneManager{
private static PhoneManagerinstance = new PhoneManager();
private Set < Phone > phones = new HashSet < Phone > ();
private PhoneManager(){}
public static PhoneManagergetInstance(){
return instance;
}
public void addPhone(Phonephone){
phones.add(phone);
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
}
import java.util.HashSet;
import java.util.Set;
/**
* @author chengang2006-4-13
*/
public class PhoneManager{
private static PhoneManagerinstance = new PhoneManager();
private Set < Phone > phones = new HashSet < Phone > ();
private PhoneManager(){}
public static PhoneManagergetInstance(){
return instance;
}
public void addPhone(Phonephone){
phones.add(phone);
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
}
/**
* @author chengang2006-4-13
*/
public class Phone{
private long number;
public Phone( long number){
this .number = number;
}
public long getNumber(){
return number;
}
}
* @author chengang2006-4-13
*/
public class Phone{
private long number;
public Phone( long number){
this .number = number;
}
public long getNumber(){
return number;
}
}
3、测试addPhone的特殊情况
(1)加入null,应该加不进去。
public
void
testAddNullPhone(){
mgr.addPhone( null );
assertEquals( 0 ,mgr.getPhones().size());
}
mgr.addPhone( null );
assertEquals( 0 ,mgr.getPhones().size());
}
运行测试发现失败。mgr.getPhone().size()返回的是2,这是受到了上一个测试加入的new Phone(13888816666L)的影响,因为mgr是单例,所以setUp虽然在每个方法前都会运行一次,但得到的实例的都是同一样。
有人可能觉得那就改一下测试代码assertEquals(2,mrg.getPohnes().size())好了,这样不就通过了吗。但我们要谨记 写单元测试的两个原则:独立性和细粒度。testAddNullPhone绝对不要受到testAddPhone里数据的影响。我觉得最好的解决办法是,给PhoneManager加一个clear方法,已清空其内的Phone对象,测试代码唯持不变,在tearDown方法中执行mgr.clear();。
对了mgr.getPhone().size()返回的是2,一个是testAddPhone加入的,另一个是则是因为Set允许加入null值,所以我们得在mgr.addPhone方法里加入一个null判断。
import
junit.framework.TestCase;
/**
* @author chengang2006-4-13
*/
public class TestPhoneManager extends TestCase{
private PhoneManagermgr;
public static void main(String[]args){
junit.textui.TestRunner.run(TestPhoneManager. class );
}
@Override
protected void setUp() throws Exception{
super .setUp();
mgr = PhoneManager.getInstance();
}
@Override
protectedvoidtearDown()throws Exception{
super .tearDown();
mgr.clear();
}
public void testAddPhone(){
Phonephone = new Phone( 13888816666L );
mgr.addPhone(phone);
assertEquals( 1 ,mgr.getPhone( 13888816666L ));
}
publicvoid testAddNullPhone(){
mgr.addPhone(null );
assertEquals(0 ,mgr.getPhones().size());
}
}
/**
* @author chengang2006-4-13
*/
public class TestPhoneManager extends TestCase{
private PhoneManagermgr;
public static void main(String[]args){
junit.textui.TestRunner.run(TestPhoneManager. class );
}
@Override
protected void setUp() throws Exception{
super .setUp();
mgr = PhoneManager.getInstance();
}
@Override
protectedvoidtearDown()throws Exception{
super .tearDown();
mgr.clear();
}
public void testAddPhone(){
Phonephone = new Phone( 13888816666L );
mgr.addPhone(phone);
assertEquals( 1 ,mgr.getPhone( 13888816666L ));
}
publicvoid testAddNullPhone(){
mgr.addPhone(null );
assertEquals(0 ,mgr.getPhones().size());
}
}
另外,在PhoneManager中加入
public
class
PhoneManager{
private static PhoneManagerinstance = new PhoneManager();
private Set < Phone > phones = new HashSet < Phone > ();
private PhoneManager(){}
public static PhoneManagergetInstance(){
return instance;
}
public void addPhone(Phonephone){
if(phone==null )
return ;
phones.add(phone);
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
publicvoid clear(){
phones.clear();
}
}
private static PhoneManagerinstance = new PhoneManager();
private Set < Phone > phones = new HashSet < Phone > ();
private PhoneManager(){}
public static PhoneManagergetInstance(){
return instance;
}
public void addPhone(Phonephone){
if(phone==null )
return ;
phones.add(phone);
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
publicvoid clear(){
phones.clear();
}
}
运行测试,"生命的绿色"出现^_^,通过!
写这些测试代码很快,修正也相当快,不过写文章太慢了。我的TDD的基本过程就是这样子的了,等我把代码写完了再一起帖出来吧,wait............
OK,两个多小时后,完成了三个类和相应的测试代码。其中在PhoneGroup和Phone之间互设时,为了防止死循环,做了相对较较复杂的判断。不过在写这里的时候,我发现以前的ITreeNode类的抽象类AbstractTreeNode中关于互设的部分可能有错误,所以我在这里没有让PhoneGroup和Phone再去实现ITreeNode接口,必竟这里的树结点也就两层,相对简单一些。
然后我又把每个测试类的main方法删除了,因为Eclipse有自己的runner,JUnit自带的text runner用不上。也把setUp.super()删除了,这是一个空实现,要和不要都一样,不如删除更干净。为了方便,我的测试代码是放在同一目录(src/java)下的,我还要把它移动不同目录(scr/test)的同一包下。另外,概要设计图不用忙着更新,留到最后吧。就象一张地图有些地方标错了,没必要马上重新弄出一张新的,等旅程全部结束后,再把全面更正后的地图给后继者用就行了。
最后总结:这是我第一次TDD,感觉确实很爽。我自己的体会,“先写测试再实现的”和“先实现再写测试”相比有这样几个好处:(1)编程速度更快(2)思路更清晰(3)每向前走一步都很有安全感(因为测试代码在后面支持着我)(4)测试代码更全面(以前已经先实现了后,经常不想写测试代码,偷点懒用调试或输出点Log,人工判断一下就了事)。
好了,给出全部代码如下,仅供参考。先列出测试代码,从测试代码可以看到了解API实现的功能,最后再给出各类的实现代码。
import
junit.framework.Test;
import junit.framework.TestSuite;
public class AllTests{
public static Testsuite(){
TestSuitesuite = new TestSuite( " Testforcom.wxxr.management.admin.console.smstest " );
// $JUnit-BEGIN$
suite.addTestSuite(TestPhoneManager. class );
suite.addTestSuite(TestPhone. class );
suite.addTestSuite(TestPhoneGroup. class );
// $JUnit-END$
return suite;
}
}
import junit.framework.TestSuite;
public class AllTests{
public static Testsuite(){
TestSuitesuite = new TestSuite( " Testforcom.wxxr.management.admin.console.smstest " );
// $JUnit-BEGIN$
suite.addTestSuite(TestPhoneManager. class );
suite.addTestSuite(TestPhone. class );
suite.addTestSuite(TestPhoneGroup. class );
// $JUnit-END$
return suite;
}
}
import
junit.framework.TestCase;
public class TestPhone extends TestCase{
private Phonephone;
protected void setUp() throws Exception{
phone = new Phone( 13888816666L );
}
public void testGetNumber(){
assertEquals( 13888816666L ,phone.getNumber());
}
public void testSetPhoneGroup(){
PhoneGroupguilin = new PhoneGroup( " 桂林 " );
PhoneGroupbeijin = new PhoneGroup( " 北京 " );
phone.setPhoneGroup(guilin);
phone.setPhoneGroup(beijin);
assertEquals(beijin,phone.getPhoneGroup());
assertEquals(phone,beijin.getPhones().iterator().next());
assertEquals( false ,guilin.getPhones().iterator().hasNext());
}
}
public class TestPhone extends TestCase{
private Phonephone;
protected void setUp() throws Exception{
phone = new Phone( 13888816666L );
}
public void testGetNumber(){
assertEquals( 13888816666L ,phone.getNumber());
}
public void testSetPhoneGroup(){
PhoneGroupguilin = new PhoneGroup( " 桂林 " );
PhoneGroupbeijin = new PhoneGroup( " 北京 " );
phone.setPhoneGroup(guilin);
phone.setPhoneGroup(beijin);
assertEquals(beijin,phone.getPhoneGroup());
assertEquals(phone,beijin.getPhones().iterator().next());
assertEquals( false ,guilin.getPhones().iterator().hasNext());
}
}
import junit.framework.TestCase;
public class TestPhoneGroup extends TestCase{
private PhoneGroupgroup;
protected void setUp() throws Exception{
group = new PhoneGroup( " 桂林 " );
}
public void testGetName(){
assertEquals( " 桂林 " ,group.getName());
}
public void testAddPhone_GetPhones(){
Phonep1 = new Phone( 13888813331L );
Phonep2 = new Phone( 13888813332L );
Phonep2_same = new Phone( 13888813332L );
assertEquals( true ,group.addPhone(p1));
assertEquals( false ,group.addPhone(p1));
assertEquals( true ,group.addPhone(p2));
assertEquals( false ,group.addPhone(p2_same));
assertEquals( 2 ,group.getPhones().size());
assertEquals(group,p1.getPhoneGroup());
assertEquals(group,p2.getPhoneGroup());
}
public void testRemovePhone(){
Phonep1 = new Phone( 13888813331L );
assertEquals( true ,group.removePhone(p1.getNumber()));
assertEquals( 0 ,group.getPhones().size());
assertEquals(PhoneGroup.NONE,p1.getPhoneGroup());
assertEquals( false ,group.removePhone(p1.getNumber()));
}
}
import
junit.framework.TestCase;
/**
* @author chengang2006-4-13
*/
public class TestPhoneManager extends TestCase{
private PhoneManagermgr;
@Override
protected void setUp() throws Exception{
mgr = PhoneManager.getInstance();
}
@Override
protected void tearDown() throws Exception{
mgr.clear();
}
public void testAddPhone(){
Phonephone = new Phone( 13888816666L );
assertEquals( true ,mgr.addPhone(phone));
assertEquals( 1 ,mgr.getPhone( 13888816666L ));
}
public void testAddPhoneGroup(){
PhoneGroupgroup = new PhoneGroup( " 杭州 " );
assertEquals( true ,mgr.addPhoneGroup(group));
assertEquals( 1 ,mgr.getPhoneGroups().size());
assertEquals(group,mgr.getPhoneGroup( " 杭州 " ));
}
public void testAddNullPhone(){
mgr.addPhone( null );
assertEquals( 0 ,mgr.getPhones().size());
}
public void testAddNullPhoneGroup(){
mgr.addPhoneGroup( null );
assertEquals( 0 ,mgr.getPhoneGroups().size());
}
public void testAddPhoneForSameNumber(){
Phonephone1 = new Phone( 13888816666L );
Phonephone2 = new Phone( 13888816666L );
assertEquals( true ,mgr.addPhone(phone1));
assertEquals( false ,mgr.addPhone(phone2));
assertEquals( 1 ,mgr.getPhones().size());
assertEquals(phone1,mgr.getPhone( 13888816666L ));
}
public void testAddPhoneGroupForSameName(){
PhoneGroupg1 = new PhoneGroup( " 桂林 " );
PhoneGroupg2 = new PhoneGroup( " 桂林 " );
assertEquals( true ,mgr.addPhoneGroup(g1));
assertEquals( false ,mgr.addPhoneGroup(g2));
assertEquals( 1 ,mgr.getPhoneGroups().size());
assertEquals(g1,mgr.getPhoneGroup( " 桂林 " ));
}
public void testRemovePhone(){
Phonephone1 = new Phone( 13888816666L );
mgr.addPhone(phone1);
assertEquals( true ,mgr.removePhone( 13888816666L ));
assertEquals( null ,mgr.getPhone( 13888816666L ));
}
public void testRemovePhoneGroup(){
PhoneGroupg1 = new PhoneGroup( " 桂林 " );
mgr.addPhoneGroup(g1);
assertEquals( true ,mgr.removePhoneGroup( " 桂林 " ));
assertEquals( null ,mgr.getPhoneGroup( " 桂林 " ));
}
public void testRemoveNonePhone(){
assertEquals( false ,mgr.removePhone( 13888816666L ));
}
public void testRemoveNonePhoneGroup(){
assertEquals( false ,mgr.removePhoneGroup( " sss " ));
}
/**
*测试,当新增一个Phone时,同时将其所属PhoneGroup加入到PhoneManager中
*/
public void testAddPhoneThenAddGroup(){
Phonephone1 = new Phone( 13888816661L );
Phonephone2 = new Phone( 13888816662L );
PhoneGroupg1 = new PhoneGroup( " 桂林 " );
PhoneGroupg2 = new PhoneGroup( " 桂林 " );
phone1.setPhoneGroup(g1);
phone2.setPhoneGroup(g2);
mgr.addPhone(phone1);
mgr.addPhone(phone2);
assertEquals( 1 ,mgr.getPhoneGroups().size());
assertEquals(phone1.getPhoneGroup(),mgr.getPhoneGroup( " 桂林 " ));
// Phone默认属于PhoneGroup.NULL组
Phonephone3 = new Phone( 13888816663L );
Phonephone4 = new Phone( 13888816664L );
mgr.addPhone(phone3);
mgr.addPhone(phone4);
assertEquals( 2 ,mgr.getPhoneGroups().size());
assertEquals(PhoneGroup.NONE,mgr.getPhoneGroup(PhoneGroup.NONE.getName()));
}
}
/**
* @author chengang2006-4-13
*/
public class TestPhoneManager extends TestCase{
private PhoneManagermgr;
@Override
protected void setUp() throws Exception{
mgr = PhoneManager.getInstance();
}
@Override
protected void tearDown() throws Exception{
mgr.clear();
}
public void testAddPhone(){
Phonephone = new Phone( 13888816666L );
assertEquals( true ,mgr.addPhone(phone));
assertEquals( 1 ,mgr.getPhone( 13888816666L ));
}
public void testAddPhoneGroup(){
PhoneGroupgroup = new PhoneGroup( " 杭州 " );
assertEquals( true ,mgr.addPhoneGroup(group));
assertEquals( 1 ,mgr.getPhoneGroups().size());
assertEquals(group,mgr.getPhoneGroup( " 杭州 " ));
}
public void testAddNullPhone(){
mgr.addPhone( null );
assertEquals( 0 ,mgr.getPhones().size());
}
public void testAddNullPhoneGroup(){
mgr.addPhoneGroup( null );
assertEquals( 0 ,mgr.getPhoneGroups().size());
}
public void testAddPhoneForSameNumber(){
Phonephone1 = new Phone( 13888816666L );
Phonephone2 = new Phone( 13888816666L );
assertEquals( true ,mgr.addPhone(phone1));
assertEquals( false ,mgr.addPhone(phone2));
assertEquals( 1 ,mgr.getPhones().size());
assertEquals(phone1,mgr.getPhone( 13888816666L ));
}
public void testAddPhoneGroupForSameName(){
PhoneGroupg1 = new PhoneGroup( " 桂林 " );
PhoneGroupg2 = new PhoneGroup( " 桂林 " );
assertEquals( true ,mgr.addPhoneGroup(g1));
assertEquals( false ,mgr.addPhoneGroup(g2));
assertEquals( 1 ,mgr.getPhoneGroups().size());
assertEquals(g1,mgr.getPhoneGroup( " 桂林 " ));
}
public void testRemovePhone(){
Phonephone1 = new Phone( 13888816666L );
mgr.addPhone(phone1);
assertEquals( true ,mgr.removePhone( 13888816666L ));
assertEquals( null ,mgr.getPhone( 13888816666L ));
}
public void testRemovePhoneGroup(){
PhoneGroupg1 = new PhoneGroup( " 桂林 " );
mgr.addPhoneGroup(g1);
assertEquals( true ,mgr.removePhoneGroup( " 桂林 " ));
assertEquals( null ,mgr.getPhoneGroup( " 桂林 " ));
}
public void testRemoveNonePhone(){
assertEquals( false ,mgr.removePhone( 13888816666L ));
}
public void testRemoveNonePhoneGroup(){
assertEquals( false ,mgr.removePhoneGroup( " sss " ));
}
/**
*测试,当新增一个Phone时,同时将其所属PhoneGroup加入到PhoneManager中
*/
public void testAddPhoneThenAddGroup(){
Phonephone1 = new Phone( 13888816661L );
Phonephone2 = new Phone( 13888816662L );
PhoneGroupg1 = new PhoneGroup( " 桂林 " );
PhoneGroupg2 = new PhoneGroup( " 桂林 " );
phone1.setPhoneGroup(g1);
phone2.setPhoneGroup(g2);
mgr.addPhone(phone1);
mgr.addPhone(phone2);
assertEquals( 1 ,mgr.getPhoneGroups().size());
assertEquals(phone1.getPhoneGroup(),mgr.getPhoneGroup( " 桂林 " ));
// Phone默认属于PhoneGroup.NULL组
Phonephone3 = new Phone( 13888816663L );
Phonephone4 = new Phone( 13888816664L );
mgr.addPhone(phone3);
mgr.addPhone(phone4);
assertEquals( 2 ,mgr.getPhoneGroups().size());
assertEquals(PhoneGroup.NONE,mgr.getPhoneGroup(PhoneGroup.NONE.getName()));
}
}
/**
* @author chengang2006-4-13
*/
public class Phone{
private long number;
private PhoneGroupgroup;
public Phone( long number){
this .number = number;
setPhoneGroup(PhoneGroup.NONE);
}
public long getNumber(){
return number;
}
/**
*设置所属PhoneGroup,并加入到PhoneGroup之下
* @param g
*/
public void setPhoneGroup(PhoneGroupg){
if (g == null ) // 过滤null
g = PhoneGroup.NONE;
if (group == g)
return ;
if (group != null )
group.removePhone(number); // 从旧组里删除
group = g;
group.addPhone( this ); // 加入到新组中
}
public PhoneGroupgetPhoneGroup(){
return group;
}
}
* @author chengang2006-4-13
*/
public class Phone{
private long number;
private PhoneGroupgroup;
public Phone( long number){
this .number = number;
setPhoneGroup(PhoneGroup.NONE);
}
public long getNumber(){
return number;
}
/**
*设置所属PhoneGroup,并加入到PhoneGroup之下
* @param g
*/
public void setPhoneGroup(PhoneGroupg){
if (g == null ) // 过滤null
g = PhoneGroup.NONE;
if (group == g)
return ;
if (group != null )
group.removePhone(number); // 从旧组里删除
group = g;
group.addPhone( this ); // 加入到新组中
}
public PhoneGroupgetPhoneGroup(){
return group;
}
}
import
java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class PhoneGroup{
public static PhoneGroupNONE = new PhoneGroup( " NONE " );
private Stringname;
private Set < Phone > phones = new HashSet < Phone > ();
public PhoneGroup(Stringname){
this .name = name;
}
public StringgetName(){
return name;
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
/**
*加入一个Phone,同时将Phone所属组设为自己
* @param phone
* @return
*/
public boolean addPhone(Phonephone){
if (phone == null )
return false ;
if (getPhone(phone.getNumber()) != null ) // 已存在同一号码的Phone
return false ;
if ( this != phone.getPhoneGroup()) // 如果本来就一样,就没必要设置了
phone.setPhoneGroup( this );
phones.add(phone);
return true ;
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
/**
*根据手机号删除一个Phone,同时将此Phone的所属组设为null
* @param number
* @return
*/
public boolean removePhone( long number){
Phonephone = getPhone(number);
boolean success = phones.remove(phone);
if (success)
phone.setPhoneGroup( null );
return success;
}
}
import java.util.HashSet;
import java.util.Set;
public class PhoneGroup{
public static PhoneGroupNONE = new PhoneGroup( " NONE " );
private Stringname;
private Set < Phone > phones = new HashSet < Phone > ();
public PhoneGroup(Stringname){
this .name = name;
}
public StringgetName(){
return name;
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
/**
*加入一个Phone,同时将Phone所属组设为自己
* @param phone
* @return
*/
public boolean addPhone(Phonephone){
if (phone == null )
return false ;
if (getPhone(phone.getNumber()) != null ) // 已存在同一号码的Phone
return false ;
if ( this != phone.getPhoneGroup()) // 如果本来就一样,就没必要设置了
phone.setPhoneGroup( this );
phones.add(phone);
return true ;
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
/**
*根据手机号删除一个Phone,同时将此Phone的所属组设为null
* @param number
* @return
*/
public boolean removePhone( long number){
Phonephone = getPhone(number);
boolean success = phones.remove(phone);
if (success)
phone.setPhoneGroup( null );
return success;
}
}
import
java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* @author chengang2006-4-13
*/
public class PhoneManager{
private static PhoneManagerinstance = new PhoneManager();
private Set < Phone > phones = new HashSet < Phone > ();
private Set < PhoneGroup > groups = new HashSet < PhoneGroup > ();
private PhoneManager(){}
public static PhoneManagergetInstance(){
return instance;
}
public boolean addPhone(Phonephone){
if (phone == null )
return false ;
if (getPhone(phone.getNumber()) != null ) // 已存在同一号码的Phone
return false ;
PhoneGroupg = phone.getPhoneGroup();
addPhoneGroup(g);
return phones.add(phone);
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
public void clear(){
phones.clear();
groups.clear();
}
public boolean removePhone( long number){
Phonephone = getPhone(number);
return phones.remove(phone);
}
public boolean removePhoneGroup(Stringname){
PhoneGroupgroup = getPhoneGroup(name);
return groups.remove(group);
}
/**
*加入一个组</P>
*如果group=null,加入失败并返回false;如果已存在组名相同的组,加失败并返回false;
* @param group
* @return
*/
public boolean addPhoneGroup(PhoneGroupgroup){
if (group == null )
return false ;
if (getPhoneGroup(group.getName()) != null ) // 已存在同一号码的Phone
return false ;
return groups.add(group);
}
public Set < PhoneGroup > getPhoneGroups(){
return Collections.unmodifiableSet(groups);
}
public PhoneGroupgetPhoneGroup(Stringname){
for (PhoneGroupgroup:groups){
if (group.getName() == name){
return group;
}
}
return null ;
}
}
import java.util.HashSet;
import java.util.Set;
/**
* @author chengang2006-4-13
*/
public class PhoneManager{
private static PhoneManagerinstance = new PhoneManager();
private Set < Phone > phones = new HashSet < Phone > ();
private Set < PhoneGroup > groups = new HashSet < PhoneGroup > ();
private PhoneManager(){}
public static PhoneManagergetInstance(){
return instance;
}
public boolean addPhone(Phonephone){
if (phone == null )
return false ;
if (getPhone(phone.getNumber()) != null ) // 已存在同一号码的Phone
return false ;
PhoneGroupg = phone.getPhoneGroup();
addPhoneGroup(g);
return phones.add(phone);
}
public Set < Phone > getPhones(){
return Collections.unmodifiableSet(phones);
}
public PhonegetPhone( long number){
for (Phonephone:phones){
if (phone.getNumber() == number){
return phone;
}
}
return null ;
}
public void clear(){
phones.clear();
groups.clear();
}
public boolean removePhone( long number){
Phonephone = getPhone(number);
return phones.remove(phone);
}
public boolean removePhoneGroup(Stringname){
PhoneGroupgroup = getPhoneGroup(name);
return groups.remove(group);
}
/**
*加入一个组</P>
*如果group=null,加入失败并返回false;如果已存在组名相同的组,加失败并返回false;
* @param group
* @return
*/
public boolean addPhoneGroup(PhoneGroupgroup){
if (group == null )
return false ;
if (getPhoneGroup(group.getName()) != null ) // 已存在同一号码的Phone
return false ;
return groups.add(group);
}
public Set < PhoneGroup > getPhoneGroups(){
return Collections.unmodifiableSet(groups);
}
public PhoneGroupgetPhoneGroup(Stringname){
for (PhoneGroupgroup:groups){
if (group.getName() == name){
return group;
}
}
return null ;
}
}
作者简介
陈刚,广西桂林人,著作有《Eclipse从入门到精通》
您可以通过其博客了解更多信息和文章:http://www.ChenGang.com.cn版权声明:本博客所有文章仅适用于非商业性转载,并请在转载时注明出处及作者的署名