Using FoxUnit for Test-Driven Development

 

In the summer of 2004, Vision Data Solutions (http://www.visionds.com) announced an open-source project called FoxUnit (http://www.foxunit.org), based on the Kent Beck book, "Test Driven Development by Example".

 

As an open-source project, it's supported not only by Vision Data Solutions developers, but also by other developers using it with full source code. It requires VFP 8 or newer. The Vision Data Solutions team is also receiving and incorporating input from other developers as to how to make FoxUnit more useful for everyone. The documentation, however, is a bit scarce, so it can be tricky to get started.

 

Test-driven development and FoxUnit

 

FoxUnit deals with exclusively with unit tests. Other types of testing such as Integration and acceptance require more automated tools. Test driven development however stresses the importance of unit testing, the purpose to test each class completely so you can feel confident it performs its purpose properly. This is supposed to improve code readability and testability. Unit testing also improves the overall quality of the code, eliminating problems early in the development cycle, saving time and effort down the road. There are a few basic tenets of TDD unit testing:

 

  1. You must write unit tests. (big surprise there!)
  2. Don't write any code without writing a test for it first. This ensures you know what's supposed to happen before you write the code.
  3. Automate tests so they can repeat frequently.
  4. Use mock objects (fake objects) to implement just enough functionality to test objects that interact with each other.

 

FoxUnit is designed to help you accomplish #3, which is the most difficult part. TDD is ideally suited for mid-level components because each class you test has to be able to stand on its own. This means your code should already be using classes (either .PRG- or .VCX-based) to handle the dirty work, letting the user interface simply make calls to these classes. If your visual classes (such as combo boxes and forms) have their own custom methods, you can write tests for them, but realize you must do more work for integration testing (when you make sure they all work together).

 

Setting up FoxUnit in your development environment

 

FoxUnit runs within your current development environment, taking up less than 1MB. You can place it in its own folder (and add it to your path) or unzip it right into the main project folder. In your current project folder, create a new folder called Tests. This is where your test cases will be stored. Run FXU.PRG, and the list of current tests will appear (figure 1). If this is the first time you've run FoxUnit, you are prompted to specify or create a new folder for your tests.

 

My list of tests You can filter the list by entering classes or names in the text boxes.

 

FoxUnit is a top-level form that shows on the Windows taskbar. It's non-modal so you can continue to do other work while it loads. When it isn't visible, FoxUnit adds a menu bar to the system menu that reactivates it when you need it.

 

Creating your first test

 

Click on the New Class button to create a new test class. You can base the test class on templates included with FoxUnit or your own. After you use a custom template, FoxUnit adds it to the list of available templates. FoxUnit provides a Standard and Minimal test case (see below for how to create your own templates).

 

The Standard test class includes comments and examples of how you might use the default methods (the minimal one only includes the method definitions.) There are two built-in methods to the template: Setup and TearDown. Each new test is added as a new method to the test case. Add common code you use for each test -- such as setting up paths or instantiating objects -- into the Setup method. Add cleanup code to the TearDown method. FoxUnit calls these two methods each time a test runs. For example, I'll test a custom class called cCustomerClass. This class is responsible for creating and managing customers. Since the entire test class uses this object for its test purposes, I've added a property before any methods that points to this object:

 

DEFINE CLASS CustomerTestClass as FxuTestCase OF FxuTestCase.prg

oObject = .NULL.

 

Now I set the oObject property to the cCustomerClass in the Setup method:

 

Function Setup

oObject = CREATEOBJECT("cCustomerClass")

 

Now each time a test runs, it fires this method, creating the object.

 

Verifying values

 

There are three other methods hidden in the test class: AssertEquals, AssertTrue, and AssertNotNull. Use these methods to validate your results. Let's start with AssertNotNull.

 

  1. Click on the Add Test button. FoxUnit adds a method named NewTestMethod to the test class and shows it to you.
  2. Change the name to describe what you're testing, such as WasObjectCreated.
  3. Add code to the method to verify its functionality. Use AssertNotNull to get a notification when an error occurs:

 

Function WasObjectCreated

 

THIS.AssertNotNull("Object was not created",THIS.oObject)

 

Close the test class program. Your test is now displayed as one of the available tests. Highlight the test in FoxUnit and click on the Selected button to run the test. If the test doesn't run successfully, it will appear in red (figure 2).

 

An Unsuccessful run -- The error/exception message provides full details of where the code failed to run.

Why did it fail?

 

Because I didn't tell it where to find the cCustomerClass object. Here's the fix:

 

Function Setup

SET CLASSLIB TO ccustclass

oObject = CREATEOBJECT("cCustomerClass")

 

When a test runs successfully, it appears in green. Now you can add further test methods to test the class.

 

If there are errors in the Setup or TearDown code, they'll show up in the FoxUnit Errors dialog. If you want FoxPro to run this code outside the tester (so you can debug it immediately), right-click and choose Options. There's an option to exclude these two methods.

 

Some other test examples

 

So what should your customer class do? One thing it should do is open the Customer table. So, create a test for it.

 

Let's say the method you call to open a table is OpenFile, and it returns true or false depending on whether it was successful. My test method, in this case, might look like this:

 

THIS.AssertTrue("Table was not opened",THIS.oObject.OpenFile() )

 

Each test case doesn't have to be a single line. An initial test method might define some default variables to work with or retrieve data from data sources:

 

Function AddCustomer

lcCust = "AKSEL Solutions"

lcID = THIS.oObject.GetCustID( )

THIS.AssertTrue("Customer could not be added",THIS.oObject.Add(lcID, lcCust))

 

AssertEquals can be helpful for evaluating non-logical and NULL-based expressions. If the Customer Update method is supposed to return 1 if it's successful, the following code tests the result:

 

THIS.AssertEquals("Customer was not updated", ;

1, ;

THIS.oObject.Update())

 

There's another method called MessageOut(). This adds messages to the FoxUnit Message tab. This is great for providing feedback on the test when you don't want to call the Assert methods.

 

Function GetCustomer

IF THIS.oObject.GetCustomer("AKSEL")

THIS.messageOut( "Customer name is "+THIS.oObject.CustName)

ENDIF

 

If the GetCustomer test was successful the message should appear on the Message tab.

 

Running everything at once!

 

The first three buttons on the toolbar run the tests. You can choose to run all the listed tests, only the ones for the currently highlighted class, or just the selected one. Each test is logged and run on its. When you select a failed test, the details of the failure appear in the Failures and Errors section.

 

FoxUnit also keeps track of the tests after you exit Visual FoxPro so when you come back to your development environment later, you can see which classes you need to fix before running them again. So if you have a suite of 100 tests, run them all, go for lunch and then come back!

 

Creating Your Own FoxUnit template

 

A FoxUnit template is a .TXT file that looks like a PRG class, with one exception: It uses VFP's TextMerge function to insert the appropriate class names. Open the fxutestcasetemplate.txt file and look at the first few lines:

 

DEFINE CLASS <<testclass>> as FxuTestCase OF FxuTestCase.prg

 

#IF .f.

LOCAL THIS AS <<testclass>> OF <<testclass>>.PRG

#ENDIF

 

Testclass is the name the user specifies when creating the new test. Copy your existing test class with a .TXT extension and change the DEFINE CLASS statement to match this one. You now have your own reusable FoxUnit template.

 

Managing test classes

 

Click on the Load Class button to load a test you previously removed or created in another directory. If you no longer require a particular test, click on Remove Selected. This removes it from the list of tests, but it doesn't delete the file.

 

FoxUnit stores the test classes as .PRG classes in the Tests folder. You can modify these files outside FoxUnit if you want. Click on the Reload Selected button to refresh FoxUnit's list of available test methods.

 

Vision Data Solutions has a webinar with Jim Erwin available on test-driven development with FoxUnit. It identifies several keys to success:

 

         Take small steps when building tests.

         Keep it simple.

         Don't tolerate broken tests.

         Use descriptive names.

         Refactor your code.

         Rely on your tests, not your comments.

         Don't be afraid to start over.

         Only write for the requirements.

 

Although setting up the initial tests may take some time, FoxUnit represents a huge improvement in creating, managing, and running tests. A big thanks to the Vision Data Solutions team for bringing this tool to the FoxPro community. Well done!