GLR
 Software web site
11/19/2008
Visitor: 82591


 Home
 LifeCycle utilities
     Build
     FUnit

 Consulting Services
 Web Site Development
 Careers

 Articles
 Links & Resources
 Tips and Tricks
 User Comments

 Free Downloads

 About Us
 Pictures
 My Blog
 Contact Information
 Email Us
 Fun Stuff
 Email a Friend
 Google Analytics

 Add to Favorites
 Printer Friendly

  Updated since last visited.
 Now viewable in:
Microsoft IE ® 5.0+
Netscape ® 7.0+
Opera ® 7.54+
Mozilla FireFox ® 1.0+

Fox with Sheep


 

 

FUnit

Embedded Unit Testing for Visual FoxPro

Version 1.00

Product from the LifeCycle series

 

Developed by GLR software © 2006

Authored by Gregory Reichert

 



Download FUnit here.


Table of Contents

 

Legal Notice. iii

Introduction. 3

Ok, how does it work. 4

Using Preprocessors to define Test Cases. 10

Writing your first FUnit Test Case. 14

Using Intellisense for quick test suites. 15

Running the FUnit Test Cases. 17

The FUnit objects. 19

The general object modal 19

_FUnit object 20

_FUnit.Maint object 22

_FUnit.Maint.Counter object 25

_FUnit.Maint.Coverage object 25

_FUnit.Maint.Exception object 26

_FUnit.Maint.Log object 26

_FUnit.Maint.Menu object 27

_FUnit.Maint.Misc object 28

Requirements. 29

References. 30

Conclusion. 31

 

 

 


Tables

Table 1: FUnit Structure directives. 7

Table 2: The DOs and DONTs in write Test Cases. 8

Table 3: Test Case Control Attributes. 9

Table 4: Environment Control Attributes. 10

Table 5: Test Case Types. 10

Table 6: Inellisense keyword for FUnit 12

 

 

Figures

Figure 1: Actual Code with FUnit Test Cases. 4

Figure 2: Test Result dialog. 4

Figure 3: An example of the Result.log file. 6

Figure 4: Test Suite definition using the FUnit directive. 7

Figure 5: Assign Classification Types. 10

Figure 6: FUnit.h include definition. 11

Figure 7: Test Suite example. 11

Figure 8: Test Suite example from using TEST_SUITE. 13

Figure 9: Test Case example using TEST_CASE. 13

Figure 10: Test Suite is marked as Excluded using NOTEST. 13

Figure 11: Launching  FUnit 14

Figure 12: Run all test cases in an application. 14

Figure 13: Run test cases only in the cmdSave button. 14

Figure 14: FUnit DOM... 16

 


Legal Notice

 

 

Information in this document, including URL and other Internet Web site references, is subject to change without notice. Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place or event is intended or should be inferred. Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of GLR software.

The names of actual companies and products mentioned herein may be the trademarks of their respective owners.

© 2005. GLR software. All rights reserved.

1303 Silver St

Sumner, WA 98390, USA

Phone 253.826.4704

Email: LifeCycle@GLRoftware.com

Web Site: http://www.GLRsoftware.com

 

 

 



Introduction

 

            While developing the version 2.0 of the LifeCycle Build utility, it became evident that I was going to need a means to test it as I added new feature and refractor the old code.  I knew of the open source utility called FoxUnit available at http://www.foxunit.org/ .  But I was looking for something more inline like how NUnit is applied.  Where Test Code is actually part of the production code.  The tests as embedded, at times, with the actually production code.  This appeared to make better sense then have the test cases external to the code that is being tested.  If the code and test cases are together, then if I reuse the routine in other projects, I will not have to move or copy the test cases.  Also, with the test case as apart of the source code, I can scan the source for the test cases, then extract, and execute them.  After some consideration as to how this would all work I came up with FUnit.

 

            FUnit (the “F” stands for FoxPro) is an Open Source / Public Domain tool.  It is a class that becomes apart of the Debug version of production projects.  After coding some or all of the test cases, we simply call the FUnit’s Run method, passing it an object reference to be tested.  It scans the source code of the object and executes the test cases.  This in turn produces a test result log.  The log contains the results of each of the test cases and any error caught by the tests.  In addition to just running the test cases, FUnit will also perform Coverage analysis with code output.

 

 

Ok, how does it work

 

Figure 1 illustrates a sample code with a class definition and the FUnit test blocks (highlighted in blue).  At the head of the example, we first include the FUint.h header file.  This provides us with the preprocessors we will need to define the test cases.  Next, we instantiate our object to the Accounting object.  We will use this object as reference when we start our test run.  The FUnit object is created by calling the FUnit program. It creates a public variable called _FUnit.  (The underscore is added to make resemble other FoxPro system variables.)   Then we call the Run method of the _FUnit object; passing it the object reference of the Accounting (oApp) object. 

 

FUnit will scan the object heritage, looking in to the source of the methods for Test Cases.  When one is found, it is extracted and executed.  The tests results are determined with the help of built in test case asserts.  The asserts are accessed via the _FUnit global object.  (See the INIT event in the object defined in Figure 1.  This code is from the program file called Example.prg.)

 


 

* Program:     EXAMPLE.PRG

* Description: Example code using the FUnit unit test class.

* Created:     11/16/2006

* Developer:   Gregory L Reichert - UT #046387

* Copyright:   Copyright (c) 2006 GLR software

*------------------------------------------------------------

*--------------------------------------------------

* Include the FUnit header file

*--------------------------------------------------

#include INCLUDE\FUnit.h

 

*--------------------------------------------------

* Create and gain access to the Accounting object

*--------------------------------------------------

PRIVATE oApp

oApp = CREATEOBJECT("Accounting")

 

*--------------------------------------------------

* RUN the tests

*--------------------------------------------------

DO prg\FUnit

 

_funit.maint.Coverage.enabled = .T.

_funit.maint.Coverage.IncludeCode = .t.

 

? _FUnit.RUN( oApp )

 

*--------------------------------------------------

* release the objects

*--------------------------------------------------

RELEASE oApp

RELEASE _FUnit

 

RETURN

***************************************************

 

*--------------------------------------------------

* Class Definition.

*--------------------------------------------------

DEFINE CLASS Accounting AS SESSION

 

    nBalance = 0

 

    PROCEDURE INIT

        nInitBal = 0.00

        this.nBalance = m.nInitBal

   

        #IF COMPILE_TESTCASES

            TEST_SUITE Validate properties

            TEST_CASE (41952554) Validate nBalance property

                *-- setup

                *-- do test

                oApp.Init()

                _FUnit.isEqual( 0, oApp.nBalance, "nBalance is not initially zero.")

                *-- cleanup

            ENDTEST_CASE

        #ENDIF

 

    ENDPROC

 

    FUNCTION Balance()

        RETURN THIS.nBalance

 

        #IF COMPILE_TESTCASES

            TEST_SUITE Balance method

            TEST_CASE (42158287) Ensure the Balance method returns the proper value.

                *-- setup

                LOCAL lnBalance

                lnBalance = oApp.nBalance

                oApp.nBalance = 10.12

                *-- do test

                _FUnit.isNotEqual( 0, oApp.Balance(), "The nBalance property did not change after being set.")

                _FUnit.isEqual( 10.12, oApp.Balance(), "The nBalance property get set properly." )

                *-- cleanup

                oApp.nBalance = lnBalance

            ENDTEST_CASE

        #ENDIF

 

    ENDFUNC

 

    FUNCTION Deposit

    LPARAMETERS tnAmount AS NUMBER

   

        THIS.nBalance = THIS.nBalance + tnAmount

        RETURN this.Balance()

 

        #IF COMPILE_TESTCASES

            TEST_SUITE Deposit method

            TEST_CASE (42715240) Positive amount

                *-- setup

                LOCAL lnBalance

                lnBalance = oApp.nBalance

                oApp.nBalance = 100.00        && reset to stable state

                *-- do test

                LOCAL lnReturn

                lnReturn = oApp.Deposit( 100.00 )

                _FUnit.isEqual( 200.00, oApp.nBalance, "The deposit was not accumulated correctly." )

                _FUnit.isTrue( TYPE("lnReturn")=="N", "A non-numeric data type was returned.")

                _FUnit.isEqual( 200.00, oApp.nBalance )

                *-- cleanup

                oApp.nBalance = lnBalance

            ENDTEST_CASE

 

            TEST_CASE (43001537) Negitive amount

                *------------------------------------------------------------

                * In a real world, deposits are Always positive.

                * The negitive amount passed should be rejected.

                *------------------------------------------------------------

                *-- setup

                LOCAL lnBalance

                lnBalance = oApp.nBalance

                oApp.nBalance = 100.00        && reset to stable state

                *-- do test

                oApp.Deposit( -50.00 )

                _FUnit.isNotEqual( 50.00, oApp.nBalance, "The amount was add as a negative to the balance." )

                _FUnit.isEqual( 100.00, oApp.nBalance,"The amount was not add to the balance, but the balance is still off." )

                *-- cleanup

                oApp.nBalance = lnBalance

            ENDTEST_CASE

        #ENDIF

 

    ENDFUNC

 

    FUNCTION Withdrawl( tnAmount as Number ) as Number

        IF tnAmount <= this.Balance()

            this.nBalance = this.nBalance - tnAmount

        ENDIF

        RETURN this.Balance()

       

        #IF COMPILE_TESTCASES

            TEST_SUITE Withdrawl method

            TEST_CASE (41488266) Positive withdrawl

                *-- setup

                LOCAL lnBalance

                lnBalance = oApp.nBalance

                oApp.nBalance = 100.00        && reset to stable state

                *-- do test

                LOCAL lnReturn

                lnReturn = oApp.Withdrawl( 50.00 )

                _FUnit.isEqual( 50.00, oApp.nBalance )

                _FUnit.isTrue( TYPE("lnReturn")=="N", "A non-numeric data type was returned.")

                _FUnit.isEqual( 50.00, oApp.nBalance )

                *-- cleanup

                oApp.nBalance = lnBalance

            ENDTEST_CASE

        #ENDIF

    ENDFUNC

 

ENDDEFINE

Figure 1: Actual Code with FUnit Test Cases

 

 

Once all the Test Cases have been discovered and executed, the results are presented to the user. Figure 2 shows the total results from running the code in Figure 1.

 

As mentioned before, the main output is written to a Test Result log (Figure 3).  In addition to the totals, as displayed in the dialog, the result file contains which Test Suites and Test Cases were run, and the output results from each.  By default the result file is built in a subfolder of the current folder with the name of Log\Result.log.

 

If the Coverage is enabled, the number of executable lines touched and missed is accumulated, plus the percentage covered is calculated.  In our example (Figure 3), the source code that was executed is included.  The vertical bars on the left margin are the lines that were actually executed during the execution of the test cases.          

 

 

Figure 2: Test Result dialog

 

            For better readability in this document, I color coded the example in Figure 3.  The test suite and test case names are displayed in black.  The test result is green for those that passed, and red for those that failed.  The blue sections are the coverage output.  At the end, the total result of all the tests are in purple. 

 


 

******************************

Suite: Balance method

------------------------------

Test Case: EXAMPLE.Accounting.BALANCE.Balance method.(42158287) Ensure the Balance method returns the proper value.

: Passed [sec: 0.012]

 

Lines Touched: 1

Lines Missed: 0

Percentage Covered: 100.00%

================================================== 

          *-- return the current balance

|         RETURN THIS.nBalance

==================================================

******************************

Suite: Deposit method

------------------------------

Test Case: EXAMPLE.Accounting.DEPOSIT.Deposit method.(42715240) Positive amount

: Passed [sec: 0.014]

 

------------------------------

Test Case: EXAMPLE.Accounting.DEPOSIT.Deposit method.(43001537) Negitive amount

Failed: The amount was add as a negative to the balance.

Failed: The amount was not add to the balance, but the balance is still off.

: Failed [sec: 0.011]

 

Lines Touched: 3

Lines Missed: 0

Percentage Covered: 100.00%

================================================== 

|     LPARAMETERS tnAmount AS NUMBER

          *-- Add deposit to balance

|         THIS.nBalance = THIS.nBalance + tnAmount

          *-- return new balance

|         RETURN this.Balance()

==================================================

******************************

Suite: Validate properties

------------------------------

Test Case: EXAMPLE.Accounting.INIT.Validate properties.(41952554) Validate nBalance property

: Passed [sec: 0.011]

 

Lines Touched: 2

Lines Missed: 0

Percentage Covered: 100.00%

================================================== 

          *-- set default balance

|         nInitBal = 0.00

|         this.nBalance = m.nInitBal

==================================================

******************************

Suite: Withdrawl method

------------------------------

Test Case: EXAMPLE.Accounting.WITHDRAWL.Withdrawl method.(41488266) Positive withdrawl

: Passed [sec: 0.017]

 

Lines Touched: 4

Lines Missed: 0

Percentage Covered: 100.00%

==================================================

  PARAMETERS  tnAmount as Number  as Number

          *-- if withdrawl amount is less then or equal to balance

|         IF tnAmount <= this.Balance()

              *-- subtract amount

|             this.nBalance = this.nBalance - tnAmount

|         ENDIF

          *-- return new balance

|         RETURN this.Balance()

==================================================

 

Start Time: 11/17/2006 10:34:22

End Time: 11/17/2006 10:34:23

--------------------

Lines Touched: 9

Lines Missed: 0

Coverage:  100.00%

--------------------

TestCase ran: 5

Passed: 4

Failed: 1

Excluded: 0

Disabled: 0

Methods with TestCase: 4

Total Methods: 4

 

 

Figure 3: An example of the Result.log file

 

 

 

 


Using Preprocessors to define Test Cases

 

            In the file FUnit.H are listed a set of directives that are used to create and control the test cases we embed in our code.    A reference to the Funit.H must to include in code before using any of the directives.  It can included as a #INCLUDE statement, from the Class menu pad’s include file option, and even referenced from a higher level included. 

 

With the exception of the COMPILE_TESTCASES, all the directives from this list evaluate at compile time to comment lines, namely they are replaced with an asterisk.  They must be the first word on the line to be effective.  Any statement, comment, or naming methodology can be placed after the directive.

 

            In the section below, the directives have been divided into four separate sets to better understand each sets usage.  Let us examine each now.

 

Test Structure

Description / Usage

COMPILE_TESTCASES

If true, test case code can be checked by the compiler.  Set to false for runtime.

TEST_SUITE [<name>]

Defines a Test Suite grouping of Test Cases.

TEST_CASE [<name>]

Start of a test case code block

ENDTEST_CASE

End of test case code block

Table 1: FUnit Structure directives

 

            The first set of directives that we will discuss is the Test Structure preprocessors.  They are used to define the Test Block, Test Suite(s), and the Test Cases declared in our code blocks.  As we compare these directives to how they are used in the example of Figure 4 to see their basic usage.

 

#IF COMPILE_TESTCASES

TEST_SUITE Test Suite Name / Title

*------------------------------------------------------------

* Description: Test Case Description

*------------------------------------------------------------

* Id Date                       By         Description

*  1 11/14/2006             GLR       Initial Creation

*

*------------------------------------------------------------

TEST_CASE (33424823) Test Case Name / Title

            *-- setup

            *-- do test

            *-- cleanup

ENDTEST_CASE

#ENDIF

Figure 4: Test Suite definition using the FUnit directive.

 

            The first one we encounter is the COMPILE_TESTCASES directive.  It is used as an expression in a #IF statement.  This has two usages: First is to provide a block marker signifying the beginning of a Test Block.  Because, by default, the value of the directive is False, the block of code is never included in the compilation, therefore does not make it to the actual run-time code.   The second purpose is when it value is set to True.  This, of course, allows the compiler to compile the code inside the Test Block.  This provides a means to validate the syntax of the Test Cases.  There is a method at _FUnit.Maint.Misc.Compile() that is for the purpose of testing the syntax of the test cases.  We talk more in detail when look at the method.

 

            The next directive we notice is the TEST_SUITE statement.  This one defines the suite the following test cases are associated with.  The only real purpose of this directive is to aid in readability of the output result log.  Generally a name or comment follows the directive.  This part is what is included in the result file.  My rule of thumb is to have it describe the purpose of the test cases set that follow as a group. 

 

            The next two directives, TEST_CASE and ENDTEST_CASE, define the beginning and end of a test case code block.  Each test case must begin with TEST_CASE directive followed be some name or definition.  By rule, I include a unique identifier to help locate the test case if it fails.  It also is good if needing to reference the test case in a document or bug tracking system.  In our example, the number inside the parenthesis is auto-generated from the Intellisense shortcut using the SYS(3) function.  Also remember that all test cases must end with an ENDTEST_CASE directive. 

 

            In the test case block of Figure 4 are three lines of comments: setup, do test, and cleanup.  I added these to illustrate the need that all test cases need to isolate themselves from all other code.  Be sure to declare all variables as Local variables, and if you open a table then close it before ending the test.  In other words, if you make a mess, clean up after yourself.

 

Test cases can not be nested, nor can they use any references to the THIS objects.  Even though they may be executed inside the scope of the method they are actually executed with a ExecScript() function, and therefore outside the objects scope.  All references to any apart of the application must be done explicitly through the application object.  See Table 2 for more rules involving writing Test Cases.

 

DO

DONT

Explicitly reference object through from the root application object and drill down to object in question.

Don’t use the THIS, THISFORM, or THISFORMSET object reference.

 

 

 

 

Table 2: The DOs and DONTs in write Test Cases

 

            Don’t forget the end the Test Block with a #ENDIF to compliment the #IF.

 

            To aid in developing Test Cases faster, I have included a small set of Intellisense shortcuts.  See Using Intellisense for quick test suites for more information on these shortcuts.

 

            This set of directives help control how FUnit views any of the Test Cases.

 

Test Case Control Attributes

Description / Usage

TESTCASE_DISABLED  [<comment>]

If present in test case, skip test case and log as skipped.

TESTCASE_EXCLUDE  [<comment>]

Exclude all test cases in the method.

TESTCASE_PROPERTIES_ONLY

The Test Case is only for testing properties.  If Coverage is ON, do not output coverage results or source code

Table 3: Test Case Control Attributes

 

            The directives defined in Table 3 help the Test Case developer when writing the test cases.  Sometimes, we start constructing a Test Case only to discover we need more information before we can complete it.  There are two directives to the rescue.  The first one is the TESTCASE_DISABLED directive.  If place inside a Test Case, the test case is not ran.  It does appear in the result log as disabled, to help us remember that we need to return to it and complete it.  It is not logged as a Passed or Failed test case, just skipped.  You can add a comment after the directive to have a clearer understanding as to why the test case has been disabled.

 

            The TESTCASE_EXCLUDE is like the TESTCASE_DISABLED, but instead affecting only the current test case, it affects all test cases in the method.  It is basically used when a method has become obsolete in the code or there are no meaningful test cases for it.  An event that only has a DoDefault() statement  really does not require a test case.  Any comment that follows the directive is for documentation of the test suites only.

 

            The last directive in this set is the TESTCASE_PROPERTIES_ONLY.   Unlike the other two in this set, this one indicates that the test cases in this method are primarily for testing the properties of the object.  It is more for documentation only. It is currently not used or monitor by FUnit, but maybe in future releases.

 

            The next two directives are for restricting test case to version of Visual FoxPro and the Operating System (see Table 4).  You can have more then one of the both of these two.  Define a directive for each version that the test case can properly function.  If omitted, all versions are considered legal.  If the test case is considered outside the version, it is omitted from the result log.

 

Environment Control Attributes

Description / Usage

TESTCASE_VFP_Version

Defines the minimum VFP version the Test Case will run under.  I.E.  TESTCASE_VFP_VERSION 8.0

TESTCASE_OS_Version

Defines the minimum OS version.  I.E.  TESTCASE_OS_VERSION 5.0   && WinXP

Table 4: Environment Control Attributes

 

            The TESTCASE_VFP_Version directive uses the VERSION(5) as a point of comparison, and  the TESTCASE_OS_Version directive uses expression VAL(SUBSTR(OS(1),9)) to get the major and minor version number.

           

            The next set helps in classifying the test case as different types of tests.  By default, if none of these are in the test case, the test case is in all type classifications, and executes in all test runs. 

 

I have provided seven best know (at least to me) test types.  But additional types can be added to the FUnit.H file to create more.  You can place as many of these directives in a test case block as need.  We inform the FUnit object prior to the run which type(s) we which to run, and the system restricts the test execution to only those marked with the proper test type directives.  We do the restriction by assign a string with a comma delimited set of types to the _FUnit.Maint.Types property.  You DO NOT need to prefix the types with the TESTTYPE_ prefix; it is implied.  The property is case insensitive and if set to empty string, all tests are ran. 

 

Test Case Types

Description / Usage

TESTTYPE_BVT

Build Verification Test or Smoke Test

TESTTYPE_REGRESSION

Regression Test

TESTTYPE_FUNCTIONAL

Functional Test.

TESTTYPE_STRESS

Stress Test

TESTTYPE_PREFORMANCE

Performance Test

TESTTYPE_UNIT

Unit Test

TESTTYPE_UAT

User Acceptance Test

Table 5: Test Case Types

 

Figure 5 is an example of the assigning the Types property to only execute tests marker as either “BVT”,”UNIT”, or “UAT”.

 

 

DO FUnit

_FUnit.Maint.Types = “BVT,UNIT,UAT”

? _FUnit.Run( oApp )

Release _FUnit

 

Figure 5: Assign Classification Types

 

           


Writing your first FUnit Test Case

 

 

            A Test Suite (and all Test Cases within it) is part of a Preprocessor block.  The Preprocessors allow us to define the Test Suites and Cases much the same fashion as we would with other Visual FoxPro statements.  To gain access to the predefined preprocessors we have to include the header file FUnit.H.   I have a series of header files with one main one that I include in all my projects.  It is here that I add the statement to include FUnit header file.

 

 

#INCLUDE Include\FUnit.H

 

Figure 6: FUnit.h include definition

 

            Let’s take a look at a standard Test Suite block.

 

            The first thing we encounter is the #IF statement.   The COMPILE_TESTCASES is defaulted to False (.F.).  This preprocessor prevents the test code from being included in the actual runtime compiled code that it tests.  We will discuss it more when we talk about the Compile method of the class.  But for now, it represents the beginning of a test block.  A test block can be place anywhere in a method, event, or subroutine.  Multiple test blocks can be defined in any chunk of code.  But as a rule of thumb for myself, I generally place all my test blocks at the end of the routine they are related to.

 

 

****************************************

#IF COMPILE_TESTCASES

TEST_SUITE Test Suite Name / Title

*------------------------------------------------------------

* Description: Test Case Description

*------------------------------------------------------------

* Id Date                       By         Description

*  1 11/14/2006             GLR       Initial Creation

*

*------------------------------------------------------------

TEST_CASE (33424823) Test Case Name / Title

            *-- setup

            *-- do test

            *-- cleanup

ENDTEST_CASE

 

#ENDIF

 

Figure 7: Test Suite example

 

The next statement in the test block is the TEST_SUITE declaration statement.  These preprocessors aids in classifying the test cases into smaller blocks.  The title or name of the suite should be descriptive of the purpose of the test cases defined in it.  When the test cases are executed, the Test Suite title is outputted to the log along with the name of the class library, class name, and method or event name.  All these help in find the test case if it is reported as failed.

 

Using Intellisense for quick test suites

 

            In order to speed up the writing of FUnit Test Suites and Test Case, I have developed a few Intellisense keywords.  The output of these keyword can be seen in Figure 8, 9, and 10.

 

            Use the TEST_SUITE keyword to generate the framework for a new Test Suite.  It will have one Test Case framework already include.  Complete the Suite and Test Case names or title to help identify it in the result log.  The large comment block is included to provide a means to track the history of changes and offer a place for large description of the Test Case.  As in the Test_Case keyword, the TestCase block has a number inside a pair of parenthesis.  This is auto-generated Test Case number.  It is from the SYS(3) function and may not be entirely unique, but come close enough for now.  I use this number in Code References at times to quickly locate the Test Case that has failing.  

 

Intellisense keyword

Description

Test_Suite

Generate a Test Suite block.  (See Figure 8)

Test_Case

Inserts a Test Case into a Test Suite block. (See Figure 9)

NoTest

Generates a Test Suite and mark it as excluded.  (See Figure 10) Generally these are placed as markers.   They will be logged in the result log as a reminder that the not all the test cases have been defined.  They will be neither flagged as a pass or a fail, just skipped.

Table 6: Inellisense keyword for FUnit

 

            The TEST_CASE keyword is used to include additional test case blocks to existing test suites.

 

****************************************

#IF COMPILE_TESTCASES

TEST_SUITE Test Suite Name / Title

*------------------------------------------------------------

* Description: Test Case Description

*------------------------------------------------------------

* Id Date                       By         Description

*  1 11/14/2006             GLR       Initial Creation

*

*------------------------------------------------------------

TEST_CASE (33424823) Test Case Name / Title

            *-- setup

            *-- do test

            *-- cleanup

ENDTEST_CASE

 

#ENDIF

Figure 8: Test Suite example from using TEST_SUITE

 

 

*------------------------------------------------------------

* Description: Test Case Description

*------------------------------------------------------------

* Id Date                       By         Description

*  1 11/14/2006             GLR       Initial Creation

*

*------------------------------------------------------------

TEST_CASE (33424823) Test Case Name / Title

            *-- setup

            *-- do test

            *-- cleanup

ENDTEST_CASE

 

Figure 9: Test Case example using TEST_CASE

 

 

 

****************************************

#IF COMPILE_TESTCASES

TEST_SUITE Test Suite Name / Title

TESTCASE_EXCLUDE (36738281) Comment as to why excluded.

#ENDIF

 

Figure 10: Test Suite is marked as Excluded using NOTEST

 

 


Running the FUnit Test Cases

 

            The FUnit class is designed to Unit Test application that stem from a single application object.  Most frameworks are built as object orientated structures, where all the objects spanned from a root object.  The case of the following example we will refer to it as oApp.  The code in Example.prg provides a sample of using FUnit.

 

            The first step is to establish the FUnit object itself.  We do this by calling the FUnit.prg from the command window.

 

 

oApp = MyApp()                   && Start Application to be tested.

DO FUnit.prg                         && Start FUnit.

 

Figure 11: Launching  FUnit

 

            Up on return, a public variable is declared by the name of _FUnit.  This variable will be how we run our test cases.  (See The FUnit objects)  The Run method starts the test run against the entire object model, in this case the oApp object.  It returns a pass (true) or fail (result) as a result.   If any test case fails, a false return value occurs.  But if the all pass, a true value is returned.  We call the Run method, and pass it a reference to the oApp object.  It scans the objects PEM looking for methods that contain Test Cases.  If a child object is discovered, it scans the child too.  If the _FUnit.Maint.ParentOnly is set to true, only the object passed is scan, and none of the child objects.

 

 

? _FUnit.Run( oApp )

 

Figure 12: Run all test cases in an application

 

            But not always do we want to test the entire application.  Sometimes, we only need to test a smaller portion.  We can do this by passing a reference to object inside the main application.  Figure 13 is an example of performing test cases in a single object.

 

 

? _FUnit.Run( oApp.Forms[1].cmdSave )

 

Figure 13: Run test cases only in the cmdSave button.

 

            By default, when the all the test cases have been located and executed, the FUnit system displays the result dialog.  From this dialog, you can view the result of the tests.

 

            Next, let us examine some of the advanced features incorporated into the FUnit system.  On the FUnit.Maint object we have a property called Silent.  This property suppresses the result dialog from appearing.  This is good to automation runs.  There is also a Iteration property that tell FUnit how many times to run each of the test cases.  By default they are only ran once to test run.

 

            Off the FUnit.Maint object we have few support child objects.  One these is the Menu object.  It displays a FUnit menu from the System Menu.  The Visible property controls whether the menu is displayed or hidden.  The Enabled property enabled and disabled the menu.

 

            Another child support object is the Coverage object.  It includes coverage analysis while the tests are being done.  The results are displayed as the number of code lines touch, the total number code lines, and the percentage of lines touched.  The idea is write the test cases to achieve 100 percent coverage.  If the IncludeCode property is set to true, then the source code is added to the test result log.  Each line touch (or executed) will have a bar ( | ) in the left margin.

 

 


The FUnit objects

 

            This section describes the various objects and their properties and methods that can be used to properly unit test the routines and applications.

 

The general object modal

 

In Figure 11 we have the FUnit Object Modal hierarchy.  Even though not defined here, the main class (_FUnit) is derived from the fuAssert class that is turn derived from the fuSupport class.  I separated them for maintenance purposed.  The Maint class is separate from the main class to ease the finding Test Assert methods from the internal and maintenance methods found in the Maint class.  Again, for ease of maintenance and future expansions, I have isolated the other general functionality into separate classes.  These will be explained in more detail as we study each.

 

_FUnit

 

 

 

Maint

 

 

 

 

 

 

Counter

 

 

 

 

Coverage

 

 

 

 

Exception

 

 

 

 

Log

 

 

 

 

Menu

 

 

 

 

Misc

 

 

Figure 14: FUnit DOM

 

            Before we dig deeper into the classes themselves, let me help you understand how the support classes get added to the Maint object.  Actually the Maint class does not explicitly reference all of the support object.  It scans the FUnit folder looking for filenames that match the pattern of “FUnit_*.prg”.  It then scans the file and retrieves the class name, and load the class as a support