Skip to content

Commit

Permalink
Merge 7c01afb into 4e7631b
Browse files Browse the repository at this point in the history
  • Loading branch information
lwasylow committed Jun 28, 2020
2 parents 4e7631b + 7c01afb commit 15f75b6
Show file tree
Hide file tree
Showing 38 changed files with 1,340 additions and 214 deletions.
84 changes: 83 additions & 1 deletion docs/userguide/expectations.md
Expand Up @@ -333,7 +333,8 @@ The matrix below illustrates the data types supported by different matchers.
| **be_like** | | | X | | | | | | X | | | | | | |
| **be_empty** | X | | X | | | | | | | | | X | X | | X |
| **have_count** | | | | | | | | | | | | X | X | | X |

| **be_within().of_()** | | | | x | x | | | | | | | | | | |
| **be_within_pct().of()**| | | | | x | | | | | | | | | | |

# Expecting exceptions

Expand Down Expand Up @@ -1093,6 +1094,87 @@ SUCCESS
<ROW><UT_VARCHAR2_LIST>D</UT_VARCHAR2_LIST></ROW><ROW><UT_VARCHAR2_LIST>E</UT_VARCHAR2_LIST></ROW><ROW><UT_VARCHAR2_LIST>F</UT_VARCHAR2_LIST></ROW>
```
## to_be_within of
This matcher is created to determine wheter expected value is approximately equal or "close" to another value.
Matcher will allow to compare numbers as well as dates.
When comparing a number the tolerance / distance can be expressed as another postive number or a percentage.
When comparing a two dates tolerance can be expressed in interval time either Day-To-Second or Year-To-Month.
Matcher for numbers will calculate a absolute distance between expected and actual and check whether that value is within a tolerance.
When comparing a date a distance is measured in interval, the check is done that actual value is within date range of expected taking into account interval plus and minus.
**Example 1.**
```sql
begin
ut.expect(3).to_be_within(1).of_(4);
end;
/
```
**Example 2.**
```sql
begin
ut.expect(3).to_be_within(1).of_(5);
end;
/
```
Returns following output via DBMS_OUTPUT:
```
Failures:
1) wihtin_test
Actual: 3 (number) was expected to be within 1 of 5 (number)
at "UT3_DEVELOP.UT_BE_WITHIN.OF_", line 48 l_result.expectation.to_(l_result );
at "UT3_DEVELOP.TEST_BETWNSTR.WIHTIN_TEST", line 5
```
**Example 3.**
```sql
begin
ut.expect(sysdate).to_be_within(interval '1' day).of_(sysdate+2);
end;
/
```
Returns following output via DBMS_OUTPUT:
```
Failures:
1) wihtin_test
Actual: 2020-06-07T13:32:58 (date) was expected to be within 1 day of 2020-06-09T13:32:58 (date)
at "UT3_DEVELOP.UT_BE_WITHIN.OF_", line 55 l_result.expectation.to_(l_result );
at "UT3_DEVELOP.TEST_BETWNSTR.WIHTIN_TEST", line 5
```
## to_be_within_pct of
This matcher is created to determine wheter expected value is approximately equal or "close" to another value within percentage value of expected.
Matcher will allow to compare numbers.
When comparing a number the tolerance / distance can be expressed as another postive number or a percentage.
When comparing a two dates tolerance can be expressed in interval time either Day-To-Second or Year-To-Month.
Matcher for numbers will calculate a absolute distance between expected and actual and check whether that value is within a tolerance.
When comparing a date a distance is measured in interval, the check is done that actual value is within date range of expected taking into account interval plus and minus.
**Example 1.**
```sql
begin
ut.expect(9).to_be_within_pct(10).of_(10);
end;
/
```
## Comparing cursors, object types, nested tables and varrays
Expand Down
1 change: 1 addition & 0 deletions source/api/be_within.syn
@@ -0,0 +1 @@
create synonym be_within for ut_be_within;
1 change: 1 addition & 0 deletions source/api/be_within_pct.syn
@@ -0,0 +1 @@
create synonym be_within_pct for ut_be_within_pct;
2 changes: 1 addition & 1 deletion source/core/ut_expectation_processor.pkb
Expand Up @@ -159,7 +159,7 @@ create or replace package body ut_expectation_processor as
-- when 11g and 12c reports only package name
function cut_header_and_expectations( a_stack varchar2 ) return varchar2 is
begin
return regexp_substr( a_stack, '(.*\.(UT_EXPECTATION[A-Z0-9#_$]*|UT|UTASSERT2?)(\.[A-Z0-9#_$]+)?\s+)+((.|\s)*)', 1, 1, 'm', 4);
return regexp_substr( a_stack, '(.*\.(UT_EQUAL|UT_BE_WITHIN[A-Z0-9#_$]*|UT_EXPECTATION[A-Z0-9#_$]*|UT|UTASSERT2?)(\.[A-Z0-9#_$]+)?\s+)+((.|\s)*)', 1, 1, 'm', 4);
end;
function cut_address_columns( a_stack varchar2 ) return varchar2 is
begin
Expand Down
43 changes: 43 additions & 0 deletions source/core/ut_utils.pkb
Expand Up @@ -877,5 +877,48 @@ create or replace package body ut_utils is
end;
end;

function interval_to_text(a_interval dsinterval_unconstrained) return varchar2 is
l_day varchar2(100) := extract(day from a_interval);
l_hour varchar2(100) := extract(hour from a_interval);
l_minute varchar2(100) := extract(minute from a_interval);
l_second varchar2(100) := extract(second from a_interval);
l_result varchar2(32767);
begin
l_result := case
when l_day = 1 then l_day ||' day'
when l_day > 1 then l_day ||' days'
end ||
case
when l_hour = 1 then ' '|| l_hour ||' hour'
when l_hour > 1 then ' '|| l_hour ||' hours'
end ||
case
when l_minute = 1 then ' '||l_minute ||' minute'
when l_minute > 1 then ' '||l_minute ||' minutes'
end ||
case
when l_second = 1 then ' '||l_second ||' second'
when l_second > 1 then ' '||l_second ||' seconds'
end;
return trim(leading ' ' from l_result);
end;

function interval_to_text(a_interval yminterval_unconstrained) return varchar2 is
l_year varchar2(4) := extract(year from a_interval);
l_month varchar2(20) := extract(month from a_interval);
l_result varchar2(32767);
begin
l_result := case
when l_year = 1 then l_year ||' year'
when l_year > 1 then l_year ||' years'
end ||
case
when l_month = 1 then ' '||l_month ||' month'
when l_month > 1 then ' '||l_month ||' months'
end;
return trim(leading ' ' from l_result);
end;


end ut_utils;
/
12 changes: 11 additions & 1 deletion source/core/ut_utils.pks
Expand Up @@ -434,6 +434,16 @@ create or replace package ut_utils authid definer is
* If null value passed returns null
*/
function qualified_sql_name(a_name varchar2) return varchar2;


/*
* Return value of interval in plain english
*/
function interval_to_text(a_interval dsinterval_unconstrained) return varchar2;

/*
* Return value of interval in plain english
*/
function interval_to_text(a_interval yminterval_unconstrained) return varchar2;

end ut_utils;
/
2 changes: 2 additions & 0 deletions source/create_grants.sql
Expand Up @@ -87,6 +87,8 @@ grant execute on &&ut3_owner..ut_be_like to &ut3_user;
grant execute on &&ut3_owner..ut_be_not_null to &ut3_user;
grant execute on &&ut3_owner..ut_be_null to &ut3_user;
grant execute on &&ut3_owner..ut_be_true to &ut3_user;
grant execute on &&ut3_owner..ut_be_within to &ut3_user;
grant execute on &&ut3_owner..ut_be_within_pct to &ut3_user;
grant execute on &&ut3_owner..ut_contain to &ut3_user;
grant execute on &&ut3_owner..ut_equal to &ut3_user;
grant execute on &&ut3_owner..ut_have_count to &ut3_user;
Expand Down
2 changes: 2 additions & 0 deletions source/create_synonyms.sql
Expand Up @@ -103,6 +103,8 @@ create &action_type. synonym &ut3_user.be_like for &&ut3_owner..be_like;
create &action_type. synonym &ut3_user.be_not_null for &&ut3_owner..be_not_null;
create &action_type. synonym &ut3_user.be_null for &&ut3_owner..be_null;
create &action_type. synonym &ut3_user.be_true for &&ut3_owner..be_true;
create &action_type. synonym &ut3_user.be_within for &&ut3_owner..be_within;
create &action_type. synonym &ut3_user.be_within_pct for &&ut3_owner..be_within_pct;
create &action_type. synonym &ut3_user.contain for &&ut3_owner..contain;
create &action_type. synonym &ut3_user.equal for &&ut3_owner..equal;
create &action_type. synonym &ut3_user.have_count for &&ut3_owner..have_count;
Expand Down
Expand Up @@ -31,7 +31,7 @@ create or replace type body ut_data_value_dsinterval as

overriding member function to_string return varchar2 is
begin
return ut_utils.to_string(self.data_value);
return ut_utils.interval_to_text(self.data_value);
end;

overriding member function compare_implementation(a_other ut_data_value) return integer is
Expand Down
Expand Up @@ -15,7 +15,7 @@ create or replace type ut_data_value_dsinterval under ut_data_value(
See the License for the specific language governing permissions and
limitations under the License.
*/
data_value dsinterval_unconstrained,
data_value interval day(9) to second(9),
constructor function ut_data_value_dsinterval(self in out nocopy ut_data_value_dsinterval, a_value dsinterval_unconstrained) return self as result,
overriding member function is_null return boolean,
overriding member function to_string return varchar2,
Expand Down
Expand Up @@ -31,7 +31,7 @@ create or replace type body ut_data_value_yminterval as

overriding member function to_string return varchar2 is
begin
return ut_utils.to_string(self.data_value);
return ut_utils.interval_to_text(self.data_value);
end;

overriding member function compare_implementation(a_other ut_data_value) return integer is
Expand Down
Expand Up @@ -15,7 +15,7 @@ create or replace type ut_data_value_yminterval under ut_data_value(
See the License for the specific language governing permissions and
limitations under the License.
*/
data_value yminterval_unconstrained,
data_value interval year(9) to month,
constructor function ut_data_value_yminterval(self in out nocopy ut_data_value_yminterval, a_value yminterval_unconstrained) return self as result,
overriding member function is_null return boolean,
overriding member function to_string return varchar2,
Expand Down
135 changes: 135 additions & 0 deletions source/expectations/matchers/ut_be_within.tpb
@@ -0,0 +1,135 @@
create or replace type body ut_be_within as
/*
utPLSQL - Version 3
Copyright 2016 - 2019 utPLSQL Project

Licensed under the Apache License, Version 2.0 (the "License"):
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

constructor function ut_be_within(self in out nocopy ut_be_within, a_distance_from_expected number) return self as result is
begin
self.init(ut_data_value_number(a_distance_from_expected), $$plsql_unit);
return;
end;

constructor function ut_be_within(self in out nocopy ut_be_within, a_distance_from_expected dsinterval_unconstrained) return self as result is
begin
self.init(ut_data_value_dsinterval(a_distance_from_expected), $$plsql_unit);
return;
end;

constructor function ut_be_within(self in out nocopy ut_be_within, a_distance_from_expected yminterval_unconstrained) return self as result is
begin
self.init(ut_data_value_yminterval(a_distance_from_expected), $$plsql_unit);
return;
end;

member procedure of_(self in ut_be_within, a_expected date) is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_date(a_expected);
if l_result.is_negated_flag = 1 then
l_result.expectation.not_to(l_result );
else
l_result.expectation.to_(l_result );
end if;
end;

member function of_(self in ut_be_within, a_expected date) return ut_be_within is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_date(a_expected);
return l_result;
end;

member procedure of_(self in ut_be_within, a_expected timestamp) is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_timestamp(a_expected);
if l_result.is_negated_flag = 1 then
l_result.expectation.not_to(l_result );
else
l_result.expectation.to_(l_result );
end if;
end;

member function of_(self in ut_be_within, a_expected timestamp) return ut_be_within is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_timestamp(a_expected);
return l_result;
end;

member procedure of_(self in ut_be_within, a_expected timestamp_tz_unconstrained) is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_timestamp_tz(a_expected);
if l_result.is_negated_flag = 1 then
l_result.expectation.not_to(l_result );
else
l_result.expectation.to_(l_result );
end if;
end;

member function of_(self in ut_be_within, a_expected timestamp_tz_unconstrained) return ut_be_within is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_timestamp_tz(a_expected);
return l_result;
end;

member procedure of_(self in ut_be_within, a_expected timestamp_ltz_unconstrained) is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_timestamp_ltz(a_expected);
if l_result.is_negated_flag = 1 then
l_result.expectation.not_to(l_result );
else
l_result.expectation.to_(l_result );
end if;
end;

member function of_(self in ut_be_within, a_expected timestamp_ltz_unconstrained) return ut_be_within is
l_result ut_be_within := self;
begin
l_result.expected := ut_data_value_timestamp_ltz(a_expected);
return l_result;
end;

overriding member function run_matcher(self in out nocopy ut_be_within, a_actual ut_data_value) return boolean is
l_result boolean;
begin
if self.expected.data_type = a_actual.data_type then
if self.expected is of (ut_data_value_date, ut_data_value_number, ut_data_value_timestamp, ut_data_value_timestamp_tz, ut_data_value_timestamp_ltz) then
l_result := ut_be_within_helper.values_within_abs_distance(self.expected, a_actual, self.distance_from_expected) ;
else
l_result := (self as ut_matcher).run_matcher(a_actual);
end if;
else
l_result := (self as ut_matcher).run_matcher(a_actual);
end if;
return l_result;
end;

overriding member function failure_message(a_actual ut_data_value) return varchar2 is
begin
return (self as ut_matcher).failure_message(a_actual) || ' '||self.distance_from_expected.to_string ||' of '|| expected.to_string_report();
end;

overriding member function failure_message_when_negated(a_actual ut_data_value) return varchar2 is
begin
return (self as ut_matcher).failure_message_when_negated(a_actual) || ' '||self.distance_from_expected.to_string ||' of '|| expected.to_string_report();
end;

end;
/

0 comments on commit 15f75b6

Please sign in to comment.