标题写的是设计,其实这里应该是写测试。刚才开始很难写出测试来,因为传统编程思维的影响。如果是以前,这段代码也许很快就能写出个能用的版本。但现在我需要提高的我代码质量,不能永远停留在那个脚步。
根据前面的需求,首先想到的是,我如何判断source中的文件比dest中的新。噢,我们只需比较最后的修改时间就行了。
于是我写了关于修改时间的简单测试
FileModifyTime source(std::time(NULL)); FileModifyTime dest(std::time(NULL)-1000); CPPUNIT_ASSERT(source.isNewerThan(dest)); CPPUNIT_ASSERT(!source.isNewerThan(source)); CPPUNIT_ASSERT(!dest.isNewerThan(source));
接着,我就写实现代码了。
class FileModifyTime { public: FileModifyTime(time_t modifyTime) : itsModifyTime(modifyTime){} bool isNewerThan(const FileModifyTime& fi){ return itsModifyTime > fi.itsModifyTime;} private: time_t itsModifyTime; };
运行测试,通过了。太简单了,离我实际应用太远了。
对于第一次尝试TDD开发,我真不知道如何来写测试。考虑到测试一般而言,是给定一系列输入,然后查看输出是否与期望的相符合。在这个小程序中,我的输入是什么呢?我的输出又是什么呢?于是进入了漫长的思考中...
考虑这个程序的核心作用在source中找出已经改动的文件或者文件夹,那么测试的输入应该是各种文件信息的输入,输出是应该是是否需要更新的bool值,于是测试代码大概是这样:
Folder source("sourceFolder"); Folder dest("destFolder"); SynchronousSystem SS(&source,&dest); CPPUNIT_ASSERT(SS.isNeedUpdate("File1")); CPPUNIT_ASSERT(SS.isNeedUpdate("File2") == false);
这一步可能跨度有点大,因为我需要写2个类。
先写个空类能保持编译通过。
class Folder { public: Folder(const std::string& path){} };
class Folder; class SynchronousSystem { public: SynchronousSystem(Folder* source,Folder* dest){} bool isNeedUpdate(const std::string& filename) { return true; } };
编译通过,但测试不通过。引文这里的我直接返回了true。为了使其通过测试,我修改了isNeedUpdate函数
bool isNeedUpdate(const std::string& filename) { if(filename == "File1") return true; else return false; }
测试通过了,但isNeedUpdate的作用应该是判断文件修改时间,这个需要从source和dest中读取文件信息,于是继续编写测试代码:
Folder source("sourceFolder"); Folder dest("destFolder"); CPPUNIT_ASSERT(source.getFileModifyTime("File1").isNewerThan(dest.getFileModifyTime("File1")); CPPUNIT_ASSERT(source.getFileModifyTime("File2").isNewerThan(dest.getFileModifyTime("File2") == false);
于是在Folder中需要增加成员函数getFileModifyTime
FileModifyTime getFileModifyTime(const std::string& filename) { return FileModifyTime(std::time(NULL)); }
为了使测试通过,我们需要在Folder中做一些工作。
class Folder { public: Folder(const std::string& path):itsPath(path){} FileModifyTime getFileModifyTime(const std::string& filename) { if (filename == "File1" && itsPath == "destFolder") return FileModifyTime(std::time(NULL)-1000); if (filename == "File2" && itsPath == "sourceFolder") return FileModifyTime(std::time(NULL)-1000); return FileModifyTime(std::time(NULL)); } private: std::string itsPath; };
这样所有测试都通过了,再回过头来看SynchronousSystem,它的isNeedUpdate函数应该调用Folder看查询文件的修改时间,类SynchronousSystem修改为
class SynchronousSystem { public: SynchronousSystem(Folder* source,Folder* dest):itsSourceFolder(source),itsDestFolder(dest){} bool isNeedUpdate(const std::string& filename) { return itsSourceFolder->getFileModifyTime(filename).isNewerThan(itsDestFolder->getFileModifyTime(filename)); } private: Folder* itsSourceFolder; Folder* itsDestFolder; };
一切很好,所有测试都通过了。
这样看来,一个功能1差不多完成了,考虑功能2的情况,如果dest中的文件不存在,那么isNeedUpdate中应该返回true。测试代码:
Folder source("sourceFolder"); Folder dest("destFolder"); SynchronousSystem SS(&source,&dest); CPPUNIT_ASSERT(SS.isNeedUpdate("FileNotExist"));
测试不通过,我们更新isNeedUpdate函数
bool isNeedUpdate(const std::string& filename) { if(!itsDestFolder->isExist(filename)) return true; return itsSourceFolder->getFileModifyTime(filename).isNewerThan(itsDestFolder->getFileModifyTime(filename)); }
类Folder中增加
bool isExist(const std::string& filename) { if(filename == "FileNotExist") return false; else return true; }
测试通过了。