重构 PowerBuilder 应用程序
将多个测试用例合并到一个测试套件中
向 PB 12.NET 迁移的大潮将促使许多公司重新审视遗留应用程序。在我之前的文章(“重构不是一个‘R’字” [PBDJ, Vol. 16, issue 12] 和“使用 TDD 和 pbUnit 重构经典 PowerBuilder 应用程序” [PBDJ, Vol. 17, issue 1)中,您已经了解了为什么在迁移之前重构代码有助于确保顺利迁移和企业集成。您已了解了测试驱动开发 (TDD) 方法论,并看到了如何使用它来确保成功的重构。我向您介绍了 pbUnit,一个开源工具和框架,可用于 PB Classic 应用程序中的重构和新代码开发,并指导您安装 pbUnit 并掌握重构 PB 遗留代码的基本算法。在本文中,我将向您展示一种技术,用于将多个测试用例合并到一个测试套件中,以及如何编写测试用例来测试数据库相关的代码。
将通用代码合并到测试套件中
在我上一篇文章的结尾,我指导您编写了三个测试用例,它们在三种不同的输入值集下表征了计算器应用程序的行为:两个正数、一个正数和一个负数、以及两个零值。在编写完这三个测试后,一个敏锐的程序员会注意到,在这三个测试中的每一个中,都有两段代码是通用的且重复的:初始设置代码和最后的清理代码。无疑,一个精明的面向对象程序员会想:我能否只写一次这些代码段?能否在每次测试运行时自动调用它们?对于那些刚开始阅读的读者,这里有一个包含通用代码(加粗显示)的测试用例。
//(1) Setting up to do the test w_calctester lw_calctester // create the window in memory so you can get at its members lw_calctester = Create w_calctester //(2) Set up specifics for this test and execute it lw_calctester.sle_years.text ='5' //provide positive inputs lw_calctester.sle_amount.text = '50000' //provide positive inputslw_calctester.cb_calc.triggerevent(clicked!) //execute the method //(3) Compare the results of the run against expected results this.assert( 'Positive Value Incorrectly Calculated', & lw_calctester.sle_interest_paid.text ='$250,000.00' ) //(4) Tear down the setup to clean up memory after the test. destroy lw_calctester
虽然这些代码很简单,但在大多数生产级别的应用程序中,您将需要更多的步骤来构建和设置状态,以支持被测试方法的复杂内存结构;同样,在测试运行后您也需要清理代码。难道不能只编写一次代码并让它自动调用吗?
为此,pbUnit 的 TestCase
基类提供了两个“生命周期”事件:SetUp 和 TearDown。pbUnit
会在每次调用测试用例中的测试事件之前/之后自动调用这些事件。您可以在 SetUp 事件中编写代码来构建内存结构,并在 TearDown
事件中编写代码来销毁它们。pbUnit 为每个测试事件提供独立的内存结构,因此不会出现“脏”测试结构的情况。图 1-4 展示了重构测试后测试用例代码的外观。
测试套件:一次执行多个测试用例
一段时间后,您集合中的 TestCase
对象数量会增长。您会发现需要按顺序运行多个测试用例来覆盖特定的代码区域。您可能会希望能够将多个测试分组,并自动按顺序运行它们。pbUnit
提供了一种方法,您可以使用 TestSuite
类将 TestCases 分组。在内部,pbUnit
将所有测试收集到一个虚拟的 TestSuite 中,并从中运行它们,调用每个测试的 SetUp 和 TearDown
来确保隔离。您可以正式化 TestSuite
并利用它。要构造一个 TestSuite
,在每个您希望调用的测试的构造函数事件中继承 TestSuite
类,并将测试用例类名作为字符串参数传递给 Initialize 方法。保存测试套件类,并给它一个以“Suite”开头的名称,以便在 GUI 中识别它。为了清晰、组织和易用性,您可以创建一个仅用于存放套件的库。要按顺序运行整个套件,请从您的测试列表中选择套件并运行它(参见图 5、6 和 7)。
模拟还是不模拟:测试数据库密集型代码
TDD 的纯粹主义者声称,测试数据库访问代码的测试并不是真正的单元测试,因为它们依赖于外部实体。纯粹主义者会采取构建模拟数据对象的方法,该对象返回从数据库调用中获得的数据,并在测试中将对数据库的调用替换为对模拟对象的调用。然而,务实主义者认识到,测试数据访问代码是有价值的,因为测试直接与数据库交互的类可以确保这些类执行正确的 SQL 语句,组装正确的对象等等。尽管这些测试依赖于外部实体,但它们测试的是大应用程序的基本构建块类。一个重要的警告是,您必须确保在运行测试之前数据库处于已知状态。另一个限制因素是,为了使单元测试有用,它们必须运行得快;数据库访问可能相对较慢。在 Java 世界中,有一个名为 dbUnit 的强大框架,它解决了数据库访问测试的问题。目前 PowerBuilder 没有这样的框架。请参阅 jUnit in Action 的第 16 章,了解关于使用 jUnit 进行数据库测试的完整讨论。
如果您打算使用 pbUnit 来测试数据访问代码,您必须意识到,由于每个测试用例都独立于其他测试用例运行,因此全局使用 SQLCA
建立的连接将**不**对您的测试代码可用。一种方法是在每个测试用例中使用 SetUp
和 TearDown
事件建立连接。但是,您可以直观地预见到这将对测试性能产生负面影响。一种更好的为多个测试配置连接的方法是在 TestSuite
类的构造函数事件中建立连接,在调用初始化您的测试之前。您甚至可以使用 SQLCA
全局变量。这将使一个连接可用于套件中的所有测试。一个重要的注意事项:您不能在构造函数事件的 Initialize 调用之后编写 Disconnect 代码。构造函数会在测试实际运行之前被调用并完成。在构造函数中调用 Disconnect 也会导致连接在测试运行之前关闭。因此,您必须在 TestSuite 的 Destructor 事件中编写 Disconnect 代码(参见图 8)。
结论
我希望本系列文章向您展示了通过单元测试提高代码质量的优势和可能性。我也希望它们能激发您将 pbUnit 或其他社区或供应商提供的框架添加到您的工具库中。像 pbUnit 这样的工具只有通过社区的参与才能不断发展和被接受。如果您喜欢 pbUnit 并且能够扩展其功能,请考虑分享您的扩展。最近,PowerBuilder 社区一直比较沉寂,更多地依赖供应商提供的解决方案,而不是社区演进的解决方案。我希望随着我们更多地转向 .NET 集成,这种趋势将改变,社区将再次变得活跃起来。
推荐附加阅读
这些书籍虽然不是 PowerBuilder 专属,但将为您提供有关 TDD 和处理遗留代码的宝贵见解
- Kent Beck, Test-Driven Development by Example (Addison-Wesley 出版)
- Michael Feathers, Working Effectively with Legacy Code (Prentice Hall 出版)
- Martin Fowler, Refactoring: Improving the Design of Existing Code (Addison-Wesley 出版)
- Tahchiev, Leme, Massol, and Gregory, JUnit in Action (Manning Publications Company 出版)