Refactoring PowerBuilder Applications

The migration march to PB 12.NET will have many shops revisiting legacy applications. In my previous articles ("Refactoring Is Not an ‘R' Word, [PBDJ, Vol. 16, issue 12] and "Refactoring Classic PowerBuilder Applications Using TDD and pbUnit" [PBDJ, Vol. 17, issue 1) you read why refactoring code before migration helps ensure smooth migration and enterprise integration. You were introduced to Test Driven Development methodology and saw how you can use it to ensure successful refactoring. I introduced you to pbUnit, an open source tool and framework that you can use for both refactoring and developing new code in PB Classic applications, and guided you through installing pbUnit and mastering the basic algorithm for refactoring PB legacy code. In this article I'll show you a technique to use for rolling multiple test cases into a single test suite and how to approach writing test cases that exercise database-centric code.

Merging Common Code into a Test Suite

At the conclusion of my last article I guided you to write three test cases that characterized the behavior of the calculator application under three different sets of input values: two positives, a positive and a negative, and two zero values. After writing these three tests, a discerning programmer would have noticed that two sections of code were common to and repeated in each of the three tests: the initial setup code and the concluding tear down code. No doubt an astute object-oriented programmer would wonder: Could I have written these code segments one time? Could they have been automatically invoked at the proper time for every test in the series? For those who are just reading along, here's one test case with the common code in bold face:

//(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

Granted this code is trivial, but in most production quality applications you will have many more steps to build, and to set the state for the complex memory structures you need to support the method under test; ditto for the code you need to clean up after a test run. Wouldn't it be useful to write the code once and have it automatically invoked?

For this purpose, the pbUnit TestCase ancestor provides two "life cycle" events: SetUp and TearDown. pbUnit invokes these events automatically before/after every test event call in a test case. You write code to build your memory structures in the SetUp event and write code to destroy them in the TearDown event. pbUnit hands each test event its own isolated and individual memory structure, so there is no chance of having a "dirty" test structure. Figures 1-4 show what the test case code will look like after refactoring the test.

Figure 1

Figure 2

Figure 3

Figure 4: Test Case with SetUp and TearDown

Test Suite: Executing Multiple Test Cases in a Single Run

After a while the number of TestCase objects in your collection will grow. You will find that you need to run several test cases as a sequence to cover a particular area of code. You might find yourself wishing that you could group multiple tests together and automatically run them as a sequence. pbUnit provides a way for you to group TestCases together using a TestSuite class. Internally, pbUnit gathers all tests into a virtual TestSuite and runs them from there, calling SetUp and TearDown from each test to guarantee isolation. You can formalize the TestSuite and use it to your advantage. To construct a TestSuite, inherit from the TestSuite class in the constructor event for each test you wish to invoke the Initialize method, passing the testcase class name as a string parameter. Save the test suite class, giving it a name that starts with the word "Suite" so you can recognize it in the GUI. For the sake of clarity, organization and ease of use, you might create a library just for your suites. To run the entire suite as a sequence, select the suite from your test list and run it (see Figures 5, 6 and 7).

Figure 5

Figure 6: TestSuite constructor code

Figure 7: Running a TestSuite

To Mock or Not to Mock: Testing DB-Centric Code

TDD purists claim that tests that exercise database access code are not really unit tests, since they depend on an external entity. A purist would take the approach of building a mock data object that returns the data that would have been returned from a call to the database and in the test replacing the call to the database with a call to the mock object. Pragmatists, however, recognize that testing data access code can be valuable because exercising classes that interact directly with the database can guarantee these classes execute the right SQL statements, assemble the right objects, and so on. Although these tests depend on external entities, they exercise classes that are the basic building blocks of a big application. One important caveat is that you must ensure the database is in a known state before you run your tests. Another limiting factor is that for unit tests to be useful, they must run fast; database access can be relatively slow. In the Java world there is a robust framework called dbUnit that deals with the issues of database access testing. Currently there is no such framework for PowerBuilder. See Chapter 16 of jUnit in Action for a complete discussion of database testing with jUnit.

If you are going to test data access code using pbUnit, you must be aware that since each test case runs in isolation from all others, a connection established globally using SQLCA will not be available to your test code. One approach would be to establish a connection inside each test case using the SetUp and TearDown events. However, you can intuitively predict that this will negatively impact test performance. A better way to configure a connection for multiple tests is establish it in the Constructor event of the TestSuite class, before the calls to initialize your tests. You can even use the SQLCA global variable. This will make a single connection available to all the tests in the suite. One important note: you cannot code the Disconnect after the initialize calls in the Constructor event. The constructor is invoked and completed before the tests are actually run. A Disconnect in the constructor will also be invoked, closing the connection before the tests are run. Therefore you must code your Disconnect in the TestSuite Destructor event (see Figure 8).

Figure 8: TestSuite with Database Connection

Conclusion

I hope the articles in this series have exposed you to the advantages and possibilities of improving your code quality via unit testing. I also hope they have whetted your appetite for including pbUnit or some other community or vendor-based framework in your tool arsenal. Tools like pbUnit can only grow and become accepted through community participation in their development. If you like pbUnit and can extend its functionality, please consider sharing your extensions. Of late the PowerBuilder community has been quiet, relying more on vendor-provided solutions than community-evolved ones. It is my hope that as we move more toward .NET integration, this trend will change and the community will once again become more active.

Recommended Additional Reading

These books, although not PowerBuilder centric will provide you with valuable insights into TDD and working with legacy code

Kent Beck, Test-Driven Development by Example published by Addison-Wesley

Share

About the Author

Yakov Werde is a passionate Certified Sybase Developer and Instructor, Sybase MVP and member of TeamSybase who has been guiding new and experienced PowerBuilder developers to develop the skills they need to succeed on their projects for 15 years. Yakov has been in the forefront of PowerBuilder .NET migration, having worked with, written about and built projects with PowerBuilder .NET since it's earliest release. Yakov's current passions include PowerBuilder .NET with WPF and mobile and cloud computing.

His company eLearnIT LLC helps corporations extend the life of their PowerBuilder applications by providing migration assistance, developer training and project consulting.

Comments and Discussions

I am new to PowerBuilder even though I was exposed to it many years ago, We are working on a legacy project using PowerBuilder and we are thinking to introduce PBUnit to the project to kick start unit testing and got stuck with datawindows. The issue was we are not able to use datawindows to do getters/setters on the datawindows' control elements without opening it visually. Could you please advise if we can use datawindows in PBUnit at all. If yes could you please advise on how (not open it visually as we don't need it in our testing).