Share via


Writing, Testing, and Debugging Tasmanian Traders

Code in the stored procedures in the database, class methods, form methods, menus, and programs was tested and debugged as it was being written.

This topic covers writing, testing, and debugging the Tastrade application in the following areas:

  • Forms
  • Reports
  • Menus and Toolbars
  • Error Handling
  • Testing and Debugging

This topic also provides a synopsis of what happens when the Main Program is initially executed. At the end of the topic, there are additional Comments about the Code.

Forms

Tastrade includes Form classes (stored in .vcx files) and forms (stored in .scx files) based on Form classes. See the section on Designing and Creating the Tasmanian Trader Classes for information about Form classes. The following list includes all the forms (.scx files) in Tasmanian Traders [ParentClass listed in brackets].

Form Name Description
BehindSc.scx [tsBaseForm] Behind the Scenes form.
ViewCode [tsTextForm] Displays the code from Behind the Scenes.
Category [tsMaintForm] Maintenance form for Category.dbf.
ChngPswd [tsBaseForm] Makes it possible for the user to change the password.
CustAdd [tsBaseForm] Makes it possible for the user to enter information for a new customer.
Customer [tsMaintForm] Maintenance form for Customer.dbf.
Employee [tsMaintForm] Maintenance form for Customer.dbf.
GetInv [form] Makes it possible for the user to specify a date range in the Orders report.
GetTitle [form] Makes it possible for the user to filter the employees to be displayed in the ListEmpl report. GetInv and GetTitle are based on the Visual FoxPro base form class so that they can be run in reports launched from Project Manager without having to open libraries.
OrdEntry [OrderEntry] Order Entry form.
OrdHist [OrderEntry] Order History form.
Product [tsMaintForm] Maintenance form for Products.dbf.
Rebuild [tsBaseForm] Makes it possible for the administrative user to reindex and check validity of the database.
Reports [tsBaseForm] Makes it possible for the user to specify a report to run.
Shipper [tsMaintForm] Maintenance form for Shipper.dbf.
Supplier [tsMaintForm] Maintenance form for Supplier.dbf.

Most forms with controls that aren't bound to data (for example, IntroForm and About) are saved as classes in .vcx files and created with the CREATEOBJECT( ) function. Sometimes a form that is data-bound is called directly as a class. For example, the Login class exposes properties to set the data for its controls. The default values of these properties are set to Tasmanian Traders tables and fields. The subclass, LoginPicture, is used directly in the application; there is no .scx file for these forms.

The advantage to running a Form class directly is that you can create the form and show it in two separate operations:

  • Form = CREATEOBJECT("IntroForm")
  • Form.Show

If you do this, you can adjust property settings after creating the object but before displaying it.

The advantage of creating a form in the Form Designer is access to a data environment and the properties, events, and methods associated with the data environment object. It is also more convenient to design and test forms in this way because you can run them directly from the Form Designer or from the Project Manager.

Most of the data-bound forms in the application contain tables in their data environments. If the application had included a form that allowed a user to simultaneously update multiple tables, a view would have been created for that purpose.

Reports

Reports can be developed any time after the database is in place. Tasmanian Traders is not a report-intensive application, so the reports were created relatively late in the development process. Each report contains a local view in its data environment. Two of the reports make it possible for the user to specify parameters for the report:

  • The Orders report prints invoices for specified date ranges.
  • The ListEmpl report makes it possible for the user to filter the employee listing by job description.

The views in the data environments of these reports were created with parameters. When the report data environments opens the views, Visual FoxPro, by default, presents the user with a generic dialog asking for the parameters. To customize this behavior for Tasmanian Traders, the AutoOpenTables properties of the data environments were set to False (.F.). In the Init of the data environment, custom forms are run to get the parameter variables. Then the OpenTables method of the data environment is called.

Queries were considered as sources for the reports, but weren't used because they would require extra files (.qpr files) to maintain and track. Views, on the other hand, were contained in the database (.dbc) file and can be conveniently displayed and modified in the Database Designer, as well as added directly to the data environment. SQL SELECT statements, however, are used as needed, as in the RemainingCredit stored procedure.

The main menu in the application is defined in Main.mnx. The single toolbar used in the application is based on the tsToolbar class in Tsbase.vcx.

Cleanup code in the menu checks the user level of the person who has logged in to the application, and releases pads and bars to which the user should not have access. Code in the procedure associated with Return to Visual FoxPro on the File menu calls the Cleanup( ) method of the application object, which issues CLEAR EVENTS and restores the Visual FoxPro menu system.

Code associated with the Click event of buttons on the toolbar calls methods of the active form for functionality. In this way, the functionality associated with a form is encapsulated with the form.

To coordinate the menus and toolbars in the application, code in the menu items calls the Click event code of the command buttons on the toolbars. An expression in the Skip For clause enables or disables the menu items based on the state of the corresponding command button on the toolbar. For a detailed description of coordinating menus and toolbars, search Help for "coordinating toolbars and menus."

Error Handling

The goal for error handling was to anticipate errors and prevent them from appearing in code, if possible. For example, code in Main.prg is included to adjust the relative path setting if Main.prg (in \Samples\Tastrade\Progs) is run instead of Tastrade.app. Before code in a method sets or reads properties of the application object from outside the object, it first checks to make sure the application object exists:

IF TYPE('oApp') == 'O'
   * do some code
ENDIF

If choosing a control would cause an error in a particular situation, that control is disabled.

Not all errors can be anticipated in code, however. Error handling, when necessary, is managed in the code associated with the Error event of an object.

When an error occurs in method code, Visual FoxPro checks for error handling code associated with the Error event of the object. If no code has been written at the object level for the Error event, the Error event code inherited from the ParentClass, or from another class up the class hierarchy, is executed.

If no code has been written for the Error event anywhere in the class hierarchy, Visual FoxPro checks for an ON ERROR routine. If no ON ERROR routine exists, Visual FoxPro displays the default Visual FoxPro error message. To see the default Visual FoxPro error messages, see Error Messages.

Tasmanian Traders checks for three types of errors that could occur at the database level: field rule violated, primary key violated, and failed trigger. Because the forms in Tasmanian Traders have a BufferMode setting of 2 - Optimistic, the only times these errors could occur are when the user moves to a different record (causing the buffer to be flushed) or saves changes to the current record. Both of these tasks are handled through methods at the form level (inherited methods from tsBaseForm), so Visual FoxPro first checks for error handling code in the Error event of the form. For example, the following code is associated with the Error event of the Customer form:

DO CASE
   CASE nError = 1884  && Primary key violated
   THISFORM.pageframe1.page1.cntCustomerInfo.Error(nError, cMethod, nLine)
   CASE nError = 1582  && Field rule violated
   THISFORM.pageframe1.page1.cntCustomerInfo.Error(nError, cMethod, nLine)
   OTHERWISE
      tsMaintForm::Error(nError, cMethod, nLine)
ENDCASE

If the primary key or a field rule is violated, the error information is passed to the control it relates to, cntCustomerInfo, so that the control's error code can present a more specific error message than the Visual FoxPro default error message, restore old values as appropriate, and set the focus to the appropriate control.

If another error is encountered, the Customer form passes the error to the parent class, tsMaintForm, which inherits error-handling code from tsBaseForm. If the error indicates a failed trigger, code associated with the Error event of tsBaseForm displays the corresponding error message stored in the aErrorMsg[ ] property of the Customer form: "Insert trigger failed," "Update trigger failed," or "Delete trigger failed." (A custom error message unique to the each form can be created by initializing the appropriate element of the aErrorMsg[ ] array in the Init of that form.) Any other errors are handled by giving the user the option to Abort, Retry, or Ignore.

There is one ON ERROR setting in Tasmanian Traders in the RestoreWindowPos( ) method of tsBaseForm:

ON ERROR llError = .T.

The code that restores the window postions checks the value of llError. If an error occurs during the reading of the saved window positions from the .INI file, the form's default Top and Left properties determine the form's position.

Testing and Debugging

Testing and debugging was an integral part of all the implementation stages of the development process. As soon as any of the components in Tasmanian Traders were somewhat functional, they were tested. As soon as any bugs were encountered, they were isolated using the Visual FoxPro debugging tools and fixed.

An application-level constant defined in Tastrade.h was used in the development of Tasmanian Traders to facilitate testing and debugging:

#DEFINE DEBUGMODE .T.

Conditional code automatically logged any user in as the Applications Developer (Leverling) if DEBUGMODE is True (.T.) so that the developers didn't have to go through that process every time they ran the application to test it.

The Utilities menu of the main Tasmanian Traders menu bar is active when a user, or a tester, logs in as an Applications Developer. This menu provides access to the Trace, View, and Debug windows, as well as the ability to cancel, resume, and suspend code.

The Main Program

Code in Main.prg is the main program of the application. When you run the application, the code in Main.prg is executed first. This code:

  • Declares the API functions used in reading and writing to the application .ini file.

  • Saves some environment settings. These environment settings (CURDIR( ), PATH, and CLASSLIB) need to be set before the application object can be created.

  • Adjusts the path in case a user runs Main.fxp instead of Tastrade.app.

  • Sets the class libraries to MAIN and TSGEN.

  • Creates an application object based on Tastrade in Main.vcx:

    oApp = CREATEOBJECT('TasTrade')
    

The following actions occur as a result of this object creation:

  1. The Init event of the parent class (APPLICATION) is invoked. Code associated with this event creates an environment object and saves other environment settings:

    THIS.AddObject("oEnvironment", "Environment")

    THIS.oEnvironment.Set( )

  2. The default section of the Tastrade.ini file is read with GetPrivString to determine if the Introductory Screen should be displayed.

  3. If specified in the .INI, IntroForm (Introductory Screen) is displayed.

  4. The Login( ) method of the class is called in the Init event of Tastrade.

  5. The Login( ) method calls the DoFormRetVal( ) method of the class.

  6. DoFormRetVal( ) takes the parameter from Login and creates and shows an instance of the LogInPicture class.

  7. When the user logs in, the user's access level is returned and stored as a property, cUserLevel, of the application object.

  8. Calls the Do( ) method of the application object:

    oApp.Do( )
    

Code in the Do( ) method performs the following actions:

  1. Runs the menu program for the application:

    THIS.DoMenu( )
    
  2. Determines, based initially on the user's access level, the component of the application to run:

    lcAction = THIS.GetStartupAction( )
    * The GetStartupAction method returns
    * the value in the Action field of the User
    
  3. Runs that component, for example, Order Entry:

    IF !EMPTY(lcAction)
       &lcAction
    ENDIF
    
  4. Establishes the event wait state with READ EVENTS.

Comments about the Code

For localization purposes, all strings that will be translated into different languages have to be defined with the #DEFINE preprocessor directive. The defined constants have to end in _LOC so that localization tools can identify them for translation. These are defined in the include file Strings.h.

Most of the code in the application is written in class or form methods so that it can be encapsulated with the appropriate objects. Stored procedures allow code specifically used in data maintenance or validation to be stored in the database.

UTILITY.prg, the procedure library for the application, includes four functions:

  • IsTag( ) to make sure that an index tag exists before it is referenced.
  • NotYet( ) used during development as features are being added.
  • FileSize( ) to return the size of a file passed as a parameter.
  • FormIsObject( ) which returns .T. if the active form is an object based on the Form base class.

One could create a class to hold these utilities, but an object created just to provide access to these procedures adds an unnecessary level of abstraction.

See Also

Solutions Samples | Tasmanian Traders Sample | Creating the Specification for Tasmanian Traders | Designing and Creating the Tasmanian Trader Classes | Tasmanian Traders Class Libraries | Designing the Tastrade Database