Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial import of CCL Unit Mocking. #7

Merged
merged 10 commits into from
Feb 15, 2019
6 changes: 3 additions & 3 deletions cclunit-framework-source/doc/CCLUTGUIDANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ An illustration of the concept can be seen [here](./examples/basic_example.inc)

## CCL Mocks

In general, there are two ways to mock objects in CCL unit tests. Generally speaking, use "with replace" to mock things that are defined outside the script or called directly by the script (CCL subroutines, UARs, other scripts), and use "with curnamespace" to mock subroutines executed by the subroutine being tested. When using "with curnamespace", add the PUBLIC namespace to the real thing and use an alternate namespace to define an override. Execute the script using 'with curnamespace = "\<alternate namespace\>"'. In practice, it is convenient to use the name of the test case for the alternate namespace.
In general, there are two ways to mock objects in CCL unit tests. Generally speaking, use "with replace" to mock things that are defined outside the script or called directly by the script (CCL subroutines, UARs, other scripts), and use "with curnamespace" to mock subroutines executed by the subroutine being tested. When using "with curnamespace", add the PUBLIC namespace to the real thing and use an alternate namespace to define an override. Execute the script using 'with curnamespace = "\<alternate namespace\>"'. In practice, it is convenient to use the name of the test for the alternate namespace.

The CCL Unit Testing framework provides an abstraction for creating mocks. The purpose is to help make it easier to define mock tables and other mock objects to be used when executing a script. Details on the API can be found at [CCLUTMOCKING.md](../CCLUTMOCKING.md)
The CCL Unit Testing framework provides an abstraction for creating mocks. The purpose is to make it easier to define mock tables and other mock objects to be used when executing a script. Details on the API can be found at [CCLUTMOCKING.md](../CCLUTMOCKING.md)

[These unit tests](./examples/mocking_api.inc) demonstrate how to accomplish a basic "with replace" while using the CCL Unit Testing framework's mocking API. They test a script named "the_script" and executes a program called "other_script". They test for scenarios where other_script returns 0 items, more than 5 items, and a failed ("F") status.
[These unit tests](./examples/mocking_api.inc) demonstrate how to accomplish a basic "with replace" while using the CCL Unit Testing framework's mocking API. They leverage a script named "mock_other_script" to mock the behavior of "other_script" and test "the_script" in scenarios where "other_script" returns 0 items, more than 5 items and a failed ("F") status.

There are other variations on this. For example, you could put asserts within mock_other_script itself. Additionally, other_script might generate its own reply structure, so you would want to do the same in mock_other_script.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless one follows the links as they are presented, this is the first mention of "mock_other_script". To make this less startling, the previous paragraph could say 'They leverage a script named "mock_other_script" to mock the behavior of "other_script" and test "the_script" in scenarios where "other_script" returns 0 items, more than 5 items and a failed ("F") status.


Expand Down
62 changes: 49 additions & 13 deletions cclunit-framework-source/doc/CCLUTMOCKING.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
# CCL Unit Mocking
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just looked at the pretty view of this file for the first time. It seems like it would be better to have a brief high-level description of what CCL Unit Mocking is all about in the location of the warning and put the warning after that. It might also be good to have a TOC just below the description making it easy to jump directly to the warning, spec, implementation notes and example.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

The CCL Unit Test framework's mocking API is available for consumers to mock certain objects to better isolate unit tests from outside variability and provide more control over the scenarios under which unit tests are run. The API provides ways to mock tables, variables, subroutines, scripts, etc.

**\*CAUTION\*** **The CCL Unit Mocking framework should only be used in non-production environments. Table mocking creates new tables against an Oracle instance for the lifetime of the test. Because the DDL is generated in a dynamic way, it is possible through inappropriate use of the framework to affect the actual table. Please only use the documented API.**
**\*CAUTION\*** **The CCL Unit Mocking framework should only be used in non-production environments. Table mocking creates new tables against an Oracle instance for the lifetime of the test. Because the DDL is generated in a dynamic way, it is possible through inappropriate use of the framework to affect an actual table. Please only use the documented API.**

In the rare event that CCL crashes midway through a test or another abnormal abort occurs (e.g. as the result of an infinite loop in a test), it may be necessary to clean up any tables that the framework could not. All tables created by the CCL Unit Test framework will be prepended with "CUST_CCLUT_".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am fine with doing it this way, by the way.


## Table of Contents
[API](#api)
Expand All @@ -28,11 +30,25 @@ cclutExecuteProgramWithMocks.
&nbsp;&nbsp;&nbsp;&nbsp;The namespace under which to execute the program.

Example:
call cclutExecuteProgramWithMocks("ccl_my_program", "\^MINE^, 1.0, ^string parameter^", "MYNAMESPACE")
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You didn't like the javascript version? It is kind of colorful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh, I can add it to all of them if you'd prefer. I figured since it wasn't actually JavaScript, some of the keywords might be incorrectly colored/not colored, so it might be better just to have nothing. Some things (like strings) would benefit though.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It stands out a lot better.
A global search/replace for Example:\s*[\n\r]*``` should catch most of them.
Do you have a markdown viewer plugin for your browser?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do, though I typically use the one in my IDE. I'll update them to javascript.

call cclutExecuteProgramWithMocks("ccl_my_program")
call cclutExecuteProgramWithMocks("ccl_my_program", "^MINE^, 1.0, ^string parameter^")
call cclutExecuteProgramWithMocks("ccl_my_program", "", "MYNAMESPACE")
call cclutExecuteProgramWithMocks("ccl_my_program", "^MINE^, 1.0, ^string parameter^", "MYNAMESPACE")
```

**cclutRemoveAllMocks**

Removes all mock implementations and mock tables that have been added through the mocking APIs. This should be called at the completion of a test case to clean up all mocks.

Example:
```
call cclutRemoveAllMocks(null)
```

**cclutDefineMockTable(tableName = vc, fieldNames = vc, fieldTypes = vc)**

Defines a mock table structure that can be created for use within a program. This is the first function to be called in the process of mocking a table. It must be called before cclutAddMockIndex() and cclutCreateMockTable() can be called. The table will not be mocked in cclutExecuteProgramWithMocks() unless cclutCreateMockTable() is called. This function can be called for the same table after cclutCreateMockTable() in order to redefine it; however, the existing mocked table will be dropped and cclutCreateMockTable() will need to be called again to recreate it with the new defintion. tableName, columnNames, and columnTypes are required. columnNames and columnTypes are expected to be pipe-delimited strings. The columnTypes should have the same count as columnNames and be in the same order.
Defines a mock table structure that can be created for use within a program. This is the first function to be called in the process of mocking a table. It must be called before cclutAddMockIndex() or cclutCreateMockTable() can be called. The table will not be mocked in cclutExecuteProgramWithMocks() unless finalized by calling cclutCreateMockTable(). This function can be called for the same table after cclutCreateMockTable() in order to redefine it; however, the existing mocked table will be dropped and cclutCreateMockTable() will need to be called again to recreate it with the new defintion. tableName, columnNames, and columnTypes are required. columnNames and columnTypes are expected to be pipe-delimited strings. The columnTypes should have the same count as columnNames and be in the same order.

@param tableName
&nbsp;&nbsp;&nbsp;&nbsp;The table to be mocked.
Expand All @@ -44,7 +60,9 @@ Defines a mock table structure that can be created for use within a program. Th
&nbsp;&nbsp;&nbsp;&nbsp;The name of the mock table (This can be used to select data for testing)

Example:
```
call cclutDefineMockTable("person", "person_id|name_last|name_first|birth_dt_tm", "f8|vc|vc|dq8")
```

**cclutAddMockIndex(tableName = vc, columnNames = vc, isUnique = i4)**

Expand All @@ -58,8 +76,10 @@ Adds an index to a mock table. The table must already be defined through cclutD
&nbsp;&nbsp;&nbsp;&nbsp;TRUE to create a unique index; FALSE to create a non-unique index

Example:
```
call cclutAddMockIndex("person", "person_id", TRUE)
call cclutAddMockIndex("person", "name_last|name_first", FALSE)
```

**cclutCreateMockTable(tableName = vc)**

Expand All @@ -69,7 +89,9 @@ Creates a mock table. The table must already be defined through cclutDefineMock
&nbsp;&nbsp;&nbsp;&nbsp;The name of the source table to be mocked.

Example:
```
call cclutCreateMockTable("person")
```

**cclutRemoveMockTable(tableName = vc)**

Expand All @@ -80,14 +102,18 @@ mocked, it will return silently. tableName is required.
&nbsp;&nbsp;&nbsp;&nbsp;The name of the source table that is mocked.

Example:
```
call cclutRemoveMockTable("person")
```

**cclutRemoveAllMockTables(null)**

Removes all mock tables. Any tables that have already been created will also be dropped.

Example:
```
call cclutRemoveAllMockTables(null)
```

**cclutAddMockData(tableName = vc, rowData = vc)**

Expand All @@ -104,23 +130,26 @@ Supported escape values
&nbsp;&nbsp;&nbsp;&nbsp;A pipe-delimited string of data to be inserted into the mock table.

Example:
```
call cclutDefineMockTable("person", "person_id|name_last|name_first|birth_dt_tm", "f8|vc|vc|dq8")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about setting off all examples as code fragments using ` or ``` or ```javascript ?
Observe that backslashes should not be escaped inside of code fragments.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

call cclutCreateMockTable("person")
call cclutAddMockData("person", "1.0|Washington|George|01-JAN-1970 00:00") ;Will add George Washington
call cclutAddMockData("person", "2.0|A\\|d\\\\ams|John|02-FEB-1971 11:11") ;Will add John A|d\ams
call cclutAddMockData("person", "2.0|A\|d\\ams|John|02-FEB-1971 11:11") ;Will add John A|d\ams
call cclutAddMockData("person", "3.0|Jefferson|\null|03-MAR-1972 22:22") ;Will add Jefferson (no first name)
call cclutAddMockData("person", "4.0|Madison||04-APR-1973 10:33") ;Will add Madison (empty string for first name)
```

**cclutClearMockData(tableName = vc)**

Clears all data from the mock table. This is functionally similar to a truncate. tableName is required. The table
must have been created through cclutCreateMockTable() or else an error will be thrown.
Clears all data from a specified mock table. This is functionally similar to a truncate. tableName is required. The table must have been created through cclutCreateMockTable() or else an error will be thrown.

@param tableName
&nbsp;&nbsp;&nbsp;&nbsp;The name of the source table for the mock table to be cleared.

Example:
```
call cclutClearMockData("person")
```

**cclutAddMockImplementation(originalName = vc, replaceName = vc)**

Expand All @@ -132,7 +161,9 @@ Adds a mock implementation to be utilized by cclutExecuteProgramWithMocks. This
&nbsp;&nbsp;&nbsp;&nbsp;The mocked object.

Example:
```
call cclutAddMockImplementation("uar_get_code_by", "mock_uar_get_code_by")
```

**cclutRemoveMockImplementation(originalName = vc)**
feckertson marked this conversation as resolved.
Show resolved Hide resolved

Expand All @@ -142,21 +173,18 @@ Removes a mock implementation.
&nbsp;&nbsp;&nbsp;&nbsp;The object that is mocked.

Example:
```
call cclutRemoveMockImplementation("uar_get_code_by")
```

**cclutRemoveAllMockImplementations(null)**

Removes all mock implementations.

Example:
```
call cclutRemoveAllMockImplementations(null)

**cclutRemoveAllMocks**

Removes all mock implementations and mock tables that have been added through the cclutAddMockImplementation() and cclutCreateMockTable() APIs. This should be called at the completion of a test suite to clean up all mocks.

Example:
call cclutRemoveAllMocks(null)
```

## Implementation Notes
1. For consistency, all mocking functions normalize names (tables, subroutines, records, etc.) to be uppercase. This matches with CCL and Oracle.
Expand Down Expand Up @@ -279,6 +307,14 @@ Test Code:
"")
call cclutAssertf8Equal(CURREF, "test_get_people_happy 016", agp_reply->persons[4].birth_dt_tm,
cnvtdatetime("04-APR-1973 10:33"))

; A contrived example to demonstrate a potential use for the return value from cclutDefineMockTable
select into "nl:"
personCount = count(*)
from (value(mock_table_person) mtp)
head report
call cclutAsserti4Equal(CURREF, "test_get_people_happy 017", cnvtint(personCount), 4)
with nocounter

call cclutRemoveAllMocks(null)

9 changes: 6 additions & 3 deletions cclunit-framework-source/src/main/resources/cclutmock.inc
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
declare cclutAddMockImplementation(cclutOriginalName = vc(val), cclutReplaceName = vc(val)) = null with protect
declare cclutRemoveMockImplementation(cclutOriginalName = vc(val)) = null with protect
declare cclutRemoveAllMockImplementations(null) = null with protect
declare cclutExecuteProgramWithMocks(cclutProgramName = vc(val), cclutParams = vc(val),
declare cclutExecuteProgramWithMocks(cclutProgramName = vc(val), cclutParams = vc(val, " "),
cclutNamespace = vc(val, "PUBLIC")) = null with protect
declare cclutRemoveAllMocks(null) = null with protect

Expand Down Expand Up @@ -125,6 +125,9 @@ namespace.
The namespace under which to execute the program.

Example:
call cclutExecuteProgramWithMocks("ccl_my_program")
call cclutExecuteProgramWithMocks("ccl_my_program", "^MINE^, 1.0, ^string parameter^")
call cclutExecuteProgramWithMocks("ccl_my_program", "", "MYNAMESPACE")
call cclutExecuteProgramWithMocks("ccl_my_program", "^MINE^, 1.0, ^string parameter^", "MYNAMESPACE")
**/
subroutine cclutExecuteProgramWithMocks(cclutProgramName, cclutParams, cclutNamespace)
Expand Down Expand Up @@ -172,8 +175,8 @@ subroutine cclutExecuteProgramWithMocks(cclutProgramName, cclutParams, cclutName
end ;cclutExecuteProgramWithMocks

/**
Removes all mock implementations and mock tables that have been added through the cclutAddMockImplementation() and
cclutCreateMockTable() APIs. This should be called at the completion of a test suite to clean up all mocks.
Removes all mock implementations and mock tables that have been added through the mocking APIs. This should be called
at the completion of a test case to clean up all mocks.

Example:
call cclutRemoveAllMocks(null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,10 @@ set cclut_mockTables->markerTime = cnvtint(curtime3)

/**
Defines a mock table structure that can be created for use within a program. This is the first function to be called in
the process of mocking a table. It must be called before cclutAddMockIndex() and cclutCreateMockTable() can be called.
The table will not be mocked in cclutExecuteProgramWithMocks() unless cclutCreateMockTable() is called. tableName,
columnNames, and columnTypes are required. columnNames and columnTypes are expected to be pipe-delimited strings. The
columnTypes should have the same count as columnNames and be in the same order.
the process of mocking a table. It must be called before cclutAddMockIndex() or cclutCreateMockTable() can be called.
The table will not be mocked in cclutExecuteProgramWithMocks() unless finalized by calling cclutCreateMockTable().
tableName, columnNames, and columnTypes are required. columnNames and columnTypes are expected to be pipe-delimited
strings. The columnTypes should have the same count as columnNames and be in the same order.

@param tableName
The table to be mocked.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,12 @@ declare public::internalSubroutine(null) = null with protect
subroutine (public::internalSubroutine(null) = null with protect)
set public_subroutine = 1

set reply->number_parameter = $1
set reply->string_parameter = $2
if (reflect(parameter(1, 0)) > " ")
set reply->number_parameter = $1
endif
if (reflect(parameter(2, 0)) > " ")
set reply->string_parameter = $2
endif

declare newSize = i4 with protect, noconstant(0)

Expand Down