ProUnit
 

Plugins

Configuring Plugins

ProUnit's architecture enables plugins to expand its functionality. The detailed list of available plugins may be found at the top of this page.

To activate a plugin you have to click the Configure Plugins button (). ProUnit will show you a list of all available plugins and then you may select which one(s) you want activated on you project, as in the example bellow:

Notice that when you save a project, plugins configuration is also saved, so when it's loaded all plugins will be automatically activated.

DBStat Plugin

DBStat Plugin monitors all database activity during the execution of every test performed and counts the reads, creates, updates and deletes of records on tables. This kind of information is very useful to find performance bottlenecks caused by bad database modeling or programming.

Bellow you'll find an example of a DBStat Plugin's execution report and the test code. It's based on sports database and does some trivial manipulation on some tables. If you analyze the code and the database activity reported you'll probably notice some interesting things:

  • On testReadRecords test, the query returns only 3 records for table Customer (checked by assertEqualsInt(3, iCountCustomers) ) but ProUnit reports 84 records read. It happens because there's no index for the field used (city), so Progress does a full table scan looking for records the match the condition.
  • testUpdateRecords changes only 14 records, but 78 are read (full table scan again).
  • testDeleteRecords also reads all records on table state but it also reads 2241 records of Customer over a validation that checks if the deleting state is being used on any Customer record. Since Customer has no index using this field, every checking causes Progress to read the full table. Since 27 records are being deleted and there are 83 records in Customer, the total reads on this table is 2241 (83 x 27).

After you have run your entire suite using this plugin, you may check the database activity saving the execution report using the DBMonitor template, as in the example bellow:


Click here to see an example.


Here's the code that generated this report (using a just created sports database):


PROCEDURE testReadRecords:
    DEFINE VARIABLE iCountCustomers     AS INTEGER      NO-UNDO.
    DEFINE VARIABLE iCountOrders        AS INTEGER      NO-UNDO.

    FOR EACH customer
        WHERE customer.city = "boston"
        NO-LOCK:
        iCountCustomers = iCountCustomers + 1.
        FOR EACH order
            WHERE order.cust-num = customer.cust-num
            NO-LOCK:
            iCountOrders = iCountOrders + 1.
        END.
    END.
    RUN assertEqualsInt(3, iCountCustomers).
    RUN assertEqualsInt(5, iCountOrders).
END.

PROCEDURE testCreateRecords:
    RUN createState ("AC (BR)", "Acre"               , "Brasil").
    RUN createState ("AL (BR)", "Alagoas"            , "Brasil").
    RUN createState ("AM (BR)", "Amazonas"           , "Brasil").
    RUN createState ("AP (BR)", "Amapa"              , "Brasil").
    RUN createState ("BA (BR)", "Bahia"              , "Brasil").
    RUN createState ("CE (BR)", "Ceara"              , "Brasil").
    RUN createState ("DF (BR)", "Distrito Federal"   , "Brasil").
    RUN createState ("ES (BR)", "Espirito Santo"     , "Brasil").
    RUN createState ("GO (BR)", "Goias"              , "Brasil").
    RUN createState ("MA (BR)", "Maranhao"           , "Brasil").
    RUN createState ("MG (BR)", "Minas Gerais"       , "Brasil").
    RUN createState ("MS (BR)", "Mato Grosso do Sul" , "Brasil").
    RUN createState ("MT (BR)", "Mato Grosso"        , "Brasil").
    RUN createState ("PA (BR)", "Para"               , "Brasil").
    RUN createState ("PB (BR)", "Paraiba"            , "Brazil").
    RUN createState ("PE (BR)", "Pernambuco"         , "Brazil").
    RUN createState ("PI (BR)", "Piaui"              , "Brazil").
    RUN createState ("PR (BR)", "Parana"             , "Brazil").
    RUN createState ("RJ (BR)", "Rio de Janeiro"     , "Brazil").
    RUN createState ("RN (BR)", "Rio Grande do Norte", "Brazil").
    RUN createState ("RO (BR)", "Rondonia"           , "Brazil").
    RUN createState ("RR (BR)", "Roraima"            , "Brazil").
    RUN createState ("RS (BR)", "Rio Grando de Sul"  , "Brazil").
    RUN createState ("SC (BR)", "Santa Catarina"     , "Brazil").
    RUN createState ("SE (BR)", "Sergipe"            , "Brazil").
    RUN createState ("SP (BR)", "Sao Paulo"          , "Brazil").
    RUN createState ("TO (BR)", "Tocantins"          , "Brazil").
    RUN assertTrue(TRUE).
END.

PROCEDURE testUpdateRecords:
    DEFINE VARIABLE iCountStates        AS INTEGER      NO-UNDO.
    FOR EACH state
        WHERE state.region = "Brasil"
        EXCLUSIVE-LOCK:
        iCountStates = iCountStates + 1.
        state.region = "Brazil".
    END.
    RUN assertEqualsInt(14, iCountStates).
END.

PROCEDURE testDeleteRecords:
    DELETE FROM state
        WHERE state.region = "Brazil".
    RUN assertTrue(TRUE).
END.

MemMonitor Plugin

MemMonitor monitors allocation of dynamic resources like persistent procedures, queries, buffers, sockets, server sockets, datasets, etc..

MemMonitor accounts all resources in memory when a test program is started (before initialized) and checks for any new object in memory when the test finishes. If there are new objects after the disposing code, MemMonitor will fail the test. For each type of object MemMonitor will show its name and how many instances were found.

WSMocker Plugin

WSMocker simulates Webspeed environment enabling WebSpeed programs to be tested using ProUnit. Some common functions used on WebSpeed are overridden enabling you to pass parameters or cookies to the tested program and check cookies it has saved.

WSMocker provides some extra methods that may be used on tests programs:

  • WSMocker.setParameter(<IN paramName>, <IN paramValue>) - Sets a parameter to be read by the web program using get-value function.
  • WSMocker.setCookie(<IN cookieName>, <IN cookieValue>) - Sets a cookie (read by get-value or get-cookie functions).
  • WSMocker.getCookie(<IN cookieName>, <OUT cookieValue>) - Gets the value of a cookie.
  • WSMocker.deleteCookie(<IN cookieName>) - Deletes a cookie.
  • WSMocker.invokeWS(<IN programPath>, <IN requestMethod>) - Runs a web program.
  • WSMocker.getWebStreamFile (<IN file>) - Returns temporary file create to receive Web stream content.
  • WSMocker.closeWebStream - Closes temporary stream.

WSMocker Sample code
RUN WSMocker.setParameter ("parameter1", "This is my first statement").
RUN WSMocker.setParameter ("parameter2", "Something else here").
RUN WSMocker.setCookie    ("cookie1"   , "Camels can't consume crisp cookies").

RUN WSMocker.invokeWS ("samples/WSProgram.p", "GET").

RUN WSMocker.getCookie("resultingCookie", OUTPUT cCookieValue).

Datapool Plugin

The Datapool plugin enables developers to separate test logic from data. Most times people create different test procedures to check system's behavior against different data input. Using Datapool Plugin the same test procedure can be used for many tests and the data scenarios are kept in a separated XML file, so new scenarios can be added just changing this XML file.

The XML file used by Datapool plugin is pretty simple and is valid for a single test case (or test program). Each test procedure may have its own data segment in the file, as in the example bellow:

<?xml version="1.0" ?>
<ProUnitDataPool>
     <TestPool test="testSample">
         <PoolRecord description="First Scenario">
             <name>UserName</name>
             <length>8</length>
         </PoolRecord>
         <PoolRecord description="Second Scenario">
             <name>Michael Jackson</name>
             <length>15</length>
         </PoolRecord>
         <PoolRecord description="Third Scenario">
             <name>Santa Claus</name>
             <length>11</length>
         </PoolRecord>
     </TestPool>

     <TestPool test="testChildren">
       <PoolRecord description="No Children">
         <counter>0</counter>
       </PoolRecord>

       <PoolRecord description="One Child">
         <counter>1</counter>
         <TestPool name="Items">
           <PoolRecord>
             <id>1</id>
           </PoolRecord>
         </TestPool>
       </PoolRecord>

       <PoolRecord description="Many Children">
         <counter>3</counter>
         <TestPool name="Items">
           <PoolRecord>
             <id>2</id>
           </PoolRecord>
           <PoolRecord>
             <id>3</id>
           </PoolRecord>
           <PoolRecord>
             <id>4</id>
           </PoolRecord>
         </TestPool>
       </PoolRecord>
     </TestPool>
</ProUnitDataPool>

In the sample above, we have two data segments inside the ProUnitDataPool tag: testSample and testChildren. Each TestPool segment identifies the test procedure where it's used (test attribute) and contains one or many PoolRecords entries. Each PoolRecord tag represents one record used for the test, or one test scenario, in this case.

PoolRecord's fields are created as inner tags, like <name> or <length>. So, in the first segment of the example above, we can find the following data:

testSample Test Data
Record Id name length
First Scenario UserName 8
Second Scenario Michael Jackson 15
Third Scenario Santa Claus 11

It's possible to have another TestPool as an inner tag a PoolRecord tag, like a master-detail relationship between tables in the database. However, you don't need to replicate master's primary key as a foreigh key in the detail table since the records already related by the XML structure. The second segment in the above example shows how to create inner pools and its data is shown in the table bellow:

testSample Test Data
Record Id counter Items / Id
No Children 0
One Child 1 1
Many Children 3 2
3
4

The sample bellow shows two test procedures using DataPool Plugin:

Datapool Example
/* This include adds support to the functions defined by DataPool plugin */
{prounit/plugins/DataPool/datapool.i}

PROCEDURE testSample:
    DEFINE VARIABLE iLength         AS INTEGER       NO-UNDO.

    DO WHILE DataPoolIterator("/"):
        iLength = LENGTH(DataPoolRecord("/name")).
        RUN assertEqualsInt (DataPoolRecord("/length"), iLength).
    END.
END.

PROCEDURE testChildren:
    DEFINE VARIABLE iCounter       AS INTEGER       NO-UNDO.
    DEFINE VARIABLE iItemCounter   AS INTEGER       NO-UNDO.

    iItemCounter = 0.
    DO WHILE DataPoolIterator("/"):
        iCounter = 0.

        DO WHILE DataPoolIterator("/items"):
            ASSIGN
                iItemCounter = iItemCounter + 1
                iCounter     = iCounter     + 1.

            RUN assertEqualsInt(DataPoolRecord("/items/id"), iItemCounter).
        END.

        RUN assertEqualsInt (iCounter, INTEGER(DataPoolRecord("/counter"))).
    END.
END.

The datapool plugin provides a set of user-defined functions:

Functions provided by DataPool Plugin
Name Description Prototype
DataPoolIterator Iterates the datapool defined by the cPath parameter, moving its cursor to the next record available. Returns true if the cursor was successfully moved or false if there were no record available.
FUNCTION DataPoolIterator RETURNS LOGICAL
    (INPUT cPath        AS CHARACTER)
DataPoolRecord Gets a value from the current record in the pool.
FUNCTION DataPoolRecord RETURNS CHARACTER
    (INPUT cPath        AS CHARACTER)
DataPoolReset Resets a pool's cursor making it possible to navigate through its records. Only needed after a pool has reached the end and you want to loop through the records again.
FUNCTION DataPoolReset RETURNS LOGICAL
    (INPUT cPath        AS CHARACTER)