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:
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.
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:
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:
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:
/* 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:
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) |