ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
不会太深入这个话题(许多书和网站都讨论),有一个称作 TDD(测试驱动开发)的技术,主张一个好的实践是在方法实现之前编写测试。该过程分为以下几个步骤: * 编写一个测试 * 编译器运行的最小化必须代码添加到类的定义中,代码不必在这个阶段工作。编译器只要产生一个输出就够了。 * 运行测试,将很可能失败因为模型方法还没有真的被实现。 * 修改代码以提供最小需求使测试可以通过 * 运行测试并通过 * 代码重构;如果修改引起了一个 bug ,一个测试将会报告故障 然后重复这个过程直到所有应用需要的功能都被实现。编写最少的代码来通过测试确保应用不会假装比严格需求做更多的事情。这个概念是在两个缩写词中重新获得,KISS(keep it simple,stupid),和YAGNI(you ain't gonna need it,你不会需要它)。 虽然描述的每个步骤非常小,但它取决于现实的需要跟程序员的个人态度。 在下面的例子中,这个技术会被在第一个步骤采用,并具有一定程度的自由,不然读者繁琐。它将被在一个简单的验证框架基础上实现。所需要的是一个非常简单的对象,执行一个验证并在验证OK的时候返回true 反之返回false;后者的情况下,一个错误信息将被生成。 所以我们开始第一个测试类。 TestERegValidator 继承 TestCase。通过测试类在以它们所要测试的类名称来命名。第一个验证类接受一个文本值和一个正则表达式模式来执行验证。 ~~~ class TestERegValidator extends haxe.unit.TestCase { public function testValidateFalse() { var v = new ERegValidator(“test”, “x”, null); assertFalse(v.validate()); } } ~~~ 记住,正则表达式在 Flash 9 版本之前没有出现。 在测试中,一个 ERegValidator 类的实例被初始化。第一个测试会检查一个字符串值是否匹配模式 x;这通过 assertFalse 来描述,如果传递的参数为 true 则会失败。测试类和第一个测试准备好了,但是明显的,这段代码本身不能被编译,因为类 ERegValidator 还不存在。 ~~~ class ERegValidator { public function new(value : String, pattern : String, opt : String) {} public function validate() : Bool { return true; } } ~~~ 现在代码准好被编译了;但是一个最后的事情漏掉了,还需要一个入口类 和一个 main 方法来运行测试并查看结果。 ~~~ class Main { static function main() { var runner = new haxe.unit.TestRunner(); runner.add(new TestERegValidator()); runner.run(); } } ~~~ 要让这段代码在所有的三个平台工作,下面的 .hxml 文件用来编译。在编译单元测试时打开 -debug 开关尤为重要,因为在失败的情况下更容易发现和纠正错误。 ~~~ # Neko -neko main.n -main Main -debug --next # Flash9 -swf main.swf -swf-version 9 -main Main -debug --next # Javascript -js main.js -main Main -debug ~~~ 编译结束会在每个平台产生同样的结果。 ~~~ Class: TestERegValidator F * TestERegValidator::testValidateFalse() ERR: Main.hx:12(TestERegValidator.testValidate) - expected false but was true // here are omitted some lines of debugging information // that vary with the different platforms FAILED 1 tests, 1 failed, 0 success ~~~ 这是预期的结果;代码编译但是并没有工作,因为验证函数总是返回 true ,而断言期待一个 false 参数。因此,只有测试需要的代码被引入: ~~~ class ERegValidator { public function new(value:String, pattern:String, ?opt:String) {} public function validate() : Bool { return false; } } ~~~ 现在执行代码会报告: ~~~ Class: TestERegValidator . OK 1 tests, 0 failed, 1 success ~~~ 一个新的失败的测试被引入: ~~~ public function testValidateTrue() { var v = new ERegValidator(“test”, “t”, null); assertTrue(v.validate()); } ~~~ 这次测试运行两个,其中一个会失败。是时候调整代码来使两个测试验证可能会通过。 ~~~ class ERegValidator { private var value : String; private var pattern : String; private var opt : String; public function new(value : String, pattern : String, ?opt : String) { this.value = value; this.pattern = pattern; this.opt = if(opt == null) “” else opt; } public function validate() : Bool { var er = new EReg(pattern, opt); return er.match(value); } } ~~~ 构造器参数现在保存在实例,可以有效的用于验证。现在,失败的情况下,一个错误需要提供来通知用户,一些故障阻止了验证过程成功完成。新的测试被添加: ~~~ public function testEmptyError() { var v = new ERegValidator(“test”, “t”); v.validate(); assertEquals(v.error, null); } ~~~ 需要类中一个新的变量 error,在成功验证的情况下,它必须有一个 null 值。 ~~~ public var error(default, null) : String; ~~~ 然后开始另一个测试。 创建测试和实现代码的过程会被重复很多次直到所有所需功能都被实现。所有这些迭代的结果是类ERegValidator的完成;一个新的类 EmailValidator 也被引入,通过它的对应的测试类 TestEmailValidator。 ~~~ class TestEmailValidator extends haxe.unit.TestCase { public function testConventional() { var v = new EmailValidator(“john@example.com”); assertTrue(v.validate()); } public function testDirty() { var v = new EmailValidator(“john @example.com”); // spaces are not allowed assertFalse(v.validate()); } public function testIncomplete() { var v = new EmailValidator(“john”); assertFalse(v.validate()); } public function testDoubleDotted() { var v = new EmailValidator(“john@example..com”); assertFalse(v.validate()); } } class TestERegValidator extends haxe.unit.TestCase { public function testValidateTrue() { var v = new ERegValidator(“test”, “t”); assertTrue(v.validate()); } public function testValidateFalse() { var v = new ERegValidator(“test”, “x”); assertFalse(v.validate()); } public function testEmptyError() { var v = new ERegValidator(“test”, “t”); v.validate(); assertTrue(v.error == null); } public function testNotEmptyError() { var v = new ERegValidator(“test”, “x”); v.validate(); assertTrue(v.error != null); } public function testErrorContent() { var value = “test”; var pattern = “x”; var v = new ERegValidator(value, pattern); v.validate(); assertTrue(v.error.indexOf(value) > = 0); assertTrue(v.error.indexOf(pattern) > = 0); } } class EmailValidator extends ERegValidator { public function new (email : String) { super(email, “^([^@\\s]+)@((?:[-a-z0-9]+\\.)+[a-z]{2,})$”, “i”); } } class ERegValidator { public var error(default, null) : String; private var value : String; private var pattern : String; private var opt : String; public function new(value : String, pattern : String, ?opt : String) { this.value = value; this.pattern = pattern; this.opt = if(opt == null) “” else opt; } public function validate() : Bool { var er = new EReg(pattern, opt); if(er.match(value)) return true; else { error = “’”+value+”’ does not match the expression /”+pattern+”/”; return false; } } } ~~~ 框架可以被继承,和添加新的数值范围,信用卡号码,电话号码等等的验证类。一个公共的接口 Validator 可以被引入来使这些类交换,可能改变 error 变量类型从 String 为 List<String>是一个好主意。这个方式一个 独立的 Validator 可以通知更多的错误信息。一个一般的 ValidatorGroup 类可以引入来一次性执行许多链式验证。