Concepts¶
Validation of the code under test (the tested logic of procedure/function etc.) is performed by comparing the actual data against the expected data. To achieve that, we use a combination of expectation and matcher to perform the check on the data.
Example of unit test procedure body.
Expectation is a set of the expected value(s), actual values(s) and the matcher(s) to run on those values.
Matcher is defining the comparison operation to be performed on expected and actual values. Pseudo-code:
ut.expect( a_actual {data-type} ).to_( {matcher} );
ut.expect( a_actual {data-type} ).not_to( {matcher} );
All matchers have shortcuts like:
Matchers¶
utPLSQL provides following matchers to perform checks on the expected and actual values.
be_between
be_empty
be_false
be_greater_than
be_greater_or_equal
be_less_or_equal
be_less_than
be_like
be_not_null
be_null
be_true
equal
match
be_between¶
Validates that the actual value is between the lower and upper bound.
Example:
begin
ut.expect( a_actual => 3 ).to_be_between( a_lower_bound => 1, a_upper_bound => 3 );
ut.expect( 3 ).to_be_between( 1, 3 );
--or
ut.expect( a_actual => 3 ).to_( be_between( a_lower_bound => 1, a_upper_bound => 3 ) );
ut.expect( 3 ).to_( be_between( 1, 3 ) );
end;
be_empty¶
Unary matcher that validates if the provided data-set is empty.
Usage:
procedure test_if_cursor_is_empty is
l_cursor sys_refcursor;
begin
open l_cursor for select * from dual where 1 = 0;
ut.expect( l_cursor ).to_be_empty();
--or
ut.expect( l_cursor ).to_( be_empty() );
end;
When used with anydata, it is only valid for collection data types.
be_false¶
Unary matcher that validates if the provided value is false.
Usage:
be_greater_or_equal¶
Allows to check if the actual value is greater or equal than the expected.
Usage:
begin
ut.expect( sysdate ).to_be_greater_or_equal( sysdate - 1 );
--or
ut.expect( sysdate ).to_( be_greater_or_equal( sysdate - 1 ) );
end;
be_greater_than¶
Allows to check if the actual value is greater than the expected.
Usage:
be_less_or_equal¶
Allows to check if the actual value is less or equal than the expected.
Usage:
begin
ut.expect( 3 ).to_be_less_or_equal( 3 );
--or
ut.expect( 3 ).to_( be_less_or_equal( 3 ) );
end;
be_less_than¶
Allows to check if the actual value is less than the expected.
Usage:
be_like¶
Validates that the actual value is like the expected expression.
Usage:
begin
ut.expect( 'Lorem_impsum' ).to_be_like( a_mask => '%rem#_%', a_escape_char => '#' );
ut.expect( 'Lorem_impsum' ).to_be_like( '%rem#_%', '#' );
--or
ut.expect( 'Lorem_impsum' ).to_( be_like( a_mask => '%rem#_%', a_escape_char => '#' ) );
ut.expect( 'Lorem_impsum' ).to_( be_like( '%rem#_%', '#' ) );
end;
Parameters a_mask
and a_escape_char
represent a valid parameters of the Oracle like function
be_not_null¶
Unary matcher that validates if the actual value is not null.
Usage:
begin
ut.expect( to_clob('ABC') ).to_be_not_null();
--or
ut.expect( to_clob('ABC') ).to_( be_not_null() );
--or
ut.expect( to_clob('ABC') ).not_to( be_null() );
end;
be_null¶
Unary matcher that validates if the actual value is null.
Usage:
begin
ut.expect( cast(null as varchar2(100)) ).to_be_null();
--or
ut.expect( cast(null as varchar2(100)) ).to_( be_null() );
end;
be_true¶
Unary matcher that validates if the provided value is false.
- boolean
Usage:
equal¶
The equal matcher is a very restrictive matcher. It only returns true, if compared data-types are the same. That means, that comparing varchar2 to a number will fail even if the varchar2 contains the same number. This matcher is designed to capture changes of data-type, so that if you expect your variable to be number and is now something else, the test will fail and give you early indication of potential problem.
Usage:
declare
x varchar2(100);
y varchar2(100);
begin
ut.expect( 'a dog' ).to_equal( 'a dog' );
ut.expect( a_actual => y ).to_equal( a_expected => x, a_nulls_are_equal => true );
--or
ut.expect( 'a dog' ).to_( equal( 'a dog' ) );
ut.expect( a_actual => y ).to_( equal( a_expected => x, a_nulls_are_equal => true ) );
end;
a_nulls_are_equal
parameter decides on the behavior of null=null
comparison (this comparison by default is true!)
Comparing cursors¶
The equal
matcher accepts additional parameter a_exclude varchar2
or a_exclude ut_varchar2_list
, when used to compare cursor
data.
Those parameters allow passing a list of column names to exclude from data comparison. The list can be a comma separated varchar2
list or a ut_varchar2_list
collection.
The column names accepted by parameter are case sensitive and cannot be quoted.
If a_exclude
parameter is not specified, all columns are included.
If a column to be excluded does not exist, the column cannot be excluded and it's name is simply ignored.
It is useful when testing cursors containing data that is beyond our control (like default or trigger/procedure generated sysdate values on columns).
procedure test_cursors_skip_columns is
x sys_refcursor;
y sys_refcursor;
begin
open x for select 'text' ignore_me, d.* from user_tables d;
open y for select sysdate "ADate", d.* from user_tables d;
ut.expect( a_actual => y ).to_equal( a_expected => x, a_exclude => 'IGNORE_ME,ADate' );
end;
Using cursors to compare PLSQL records on Oracle 12c¶
There is a great article by Tim Hall on using the TABLE Operator with Locally Defined Types in PL/SQL. If you are on Oracle 12c, you can benefit from this feature to make comparison of PLSQL records and tables super-simple in utPLSQL. You can use the feature described in article to convert PLSQL records and collection types to cursors. Complex cursor data can then be compared in utPLQL.
Comparing cursor data containing DATE fields¶
Important note
utPLSQL uses XMLType internally to represent rows of the cursor data. This is by far most flexible and allows comparison of cursors containing LONG, CLOB, BLOB, user defined types and even nested cursors. Due to the way Oracle handles DATE data type when converting from cursor data to XML, utPLSQL has no control over the DATE formatting. The NLS_DATE_FORMAT setting from the moment the cursor was opened decides ont the formatting of dates used for cursor data comparison. By default, Oracle NLS_DATE_FORMAT is timeless, so data of DATE datatype, will be compared ignoring the time part of it.
You should use procedures ut.set_nls
, ut.reset_nls
around cursors that you want to compare in your tests.
This way, the DATE data in cursors will get properly formatted for comparison using date-time format.
The example below makes use of ut.set_nls
, ut.reset_nls
, so that date in l_expected
and l_actual
is compared using date-time formatting.
create table events (
description varchar2(4000),
event_date date
);
create or replace function get_events(a_date_from date, a_date_to date) return sys_refcursor is
l_result sys_refcursor;
begin
open l_result for
select description, event_date
from events
where event_date between a_date_from and a_date_to;
return l_result;
end;
/
create or replace package test_get_events is
--%suite(get_events)
--%beforeall
procedure setup_events;
--%test(returns event within date range)
procedure get_events_for_date_range;
end;
/
create or replace package body test_get_events is
gc_description constant varchar2(30) := 'Test event';
gc_event_date constant date := to_date('2016-09-08 06:51:22','yyyy-mm-dd hh24:mi:ss');
procedure setup_events is
begin
insert into events (description, event_date)
values (gc_description, gc_event_date);
end;
procedure get_events_for_date_range is
l_expected sys_refcursor;
l_actual sys_refcursor;
l_expected_bad_date sys_refcursor;
l_second number := 1/24/60/60;
begin
ut.set_nls();
open l_expected for select gc_description as description, gc_event_date as event_date from dual;
open l_expected_bad_date for select gc_description as description, gc_event_date + l_second as event_date from dual;
l_actual := get_events(gc_event_date-1, gc_event_date+1);
ut.reset_nls();
ut.expect(l_actual).to_equal(l_expected);
ut.expect(l_actual).not_to_equal(l_expected_bad_date);
end;
end;
/
begin
ut.run();
end;
/
drop table events;
drop function get_events;
drop package test_get_events;
Comparing user defined types and collections¶
The anydata
data type is used to compare user defined object and collections.
Example:
create type department as object(name varchar2(30));
/
create type departments as table of department;
/
create or replace package demo_dept as
-- %suite(demo)
--%test(demo of object to object comparison)
procedure test_department;
--%test(demo of collection comparison)
procedure test_departments;
end;
/
create or replace package body demo_dept as
procedure test_department is
v_expected department;
v_actual department;
begin
v_expected := department('HR');
v_actual := department('IT');
ut.expect( anydata.convertObject(v_expected) ).to_equal( anydata.convertObject(v_actual) );
end;
procedure test_department is
v_expected department;
v_actual department;
begin
v_expected := departments(department('HR'));
v_actual := departments(department('IT'));
ut.expect( anydata.convertCollection(v_expected) ).to_equal( anydata.convertCollection(v_actual) );
end;
end;
/
This test will fail as the v_acutal
is not equal v_expected
.
match¶
Validates that the actual value is matching the expected regular expression.
Usage:
begin
ut.expect( a_actual => '123-456-ABcd' ).to_match( a_pattern => '\d{3}-\d{3}-[a-z]', a_modifiers => 'i' );
ut.expect( 'some value' ).to_match( '^some.*' );
--or
ut.expect( a_actual => '123-456-ABcd' ).to_( match( a_pattern => '\d{3}-\d{3}-[a-z]', a_modifiers => 'i' ) );
ut.expect( 'some value' ).to_( match( '^some.*' ) );
end;
Parameters a_pattern
and a_modifiers
represent a valid regexp pattern accepted by Oracle regexp_like function
Supported data types¶
Below matrix illustrates the data types supported by different matchers.
be_between | be_empty | be_false | be_greater_than | be_greater_or_equal | be_less_or_equal | be_less_than | be_like | be_not_null | be_null | be_true | equal | match | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
anydata | X | X | X | X | |||||||||
blob | X | X | X | ||||||||||
boolean | X | X | X | X | X | ||||||||
clob | X | X | X | X | X | ||||||||
date | X | X | X | X | X | X | X | X | |||||
number | X | X | X | X | X | X | X | X | |||||
refcursor | X | X | X | X | |||||||||
timestamp | X | X | X | X | X | X | X | X | |||||
timestamp with timezone | X | X | X | X | X | X | X | X | |||||
timestamp with local timezone | X | X | X | X | X | X | X | X | |||||
varchar2 | X | X | X | X | X | X | |||||||
interval year to month | X | X | X | X | X | X | X | X | |||||
interval day to second | X | X | X | X | X | X | X | X |
Negating a matcher¶
Expectations provide a very convenient way to perform a check on negated matcher.
Syntax of check for matcher evaluating to true:
begin
ut.expect( a_actual {data-type} ).to_{matcher};
ut.expect( a_actual {data-type} ).to_( {matcher} );
end;
Syntax of check for matcher evaluating to false:
begin
ut.expect( a_actual {data-type} ).not_to_{matcher};
ut.expect( a_actual {data-type} ).not_to( {matcher} );
end;
If a matcher evaluated to NULL, then both to_
and not_to
will cause the expectation to report failure.
Example:
Since NULL is neither true not it is not true, both expectations will report failure.Created: January 15, 2017 02:28:15