utPLSQL Framework

repository·develop·Indexed 20 days ago

https://github.com/utplsql/utplsql

A unit testing framework for Oracle PL/SQL and SQL (Oracle Database 19c or newer) that implements modern testing practices similar to JUnit or RSpec. It features declarative configuration via annotations, built-in code coverage, and advanced data comparison for compound data-types (refcursor, object types, nested tables, varrays, and JSON). The ecosystem includes the utPLSQL-cli for CI/CD integration and real-time reporting, as well as tools for migrating from v2 to v3.

Tokens
34.7K
Snippets
97
Records
127
Agent score
70%

What's inside utPLSQL

  1. Key differences in utPLSQL v3 features

    develop

    utPLSQL v3 introduces several modern testing features that were not present or were handled differently in v2. Key improvements include:

    • Declarative Configuration: Uses Annotations (specially formatted comments in package specifications) for test configuration, setup/teardown, and automatic test detection.
    • Suite Management: Supports hierarchical suites (suites within suites) and multiple suites.
    • Execution Granularity: Allows executing single test procedures, single packages, or specific subsets of a suite.
    • Code Coverage: Includes built-in support for HTML and Sonar XML coverage reports.
    • Output & Reporting: Supports multiple output reporters simultaneously, real-time progress reporting, XUnit format support, and client-side file output. Custom reporters can be added without complex configuration.
    • Assertions: Provides 26 matchers (13 positive + 13 negated) and supports extendable custom matchers.
    • Transaction Control: Managed via Annotations.
    • License: Changed from GPL v2 to Apache 2.0.
  2. Compare compound data types (cursors, objects, collections)

    develop

    utPLSQL supports comparing complex data types including ref cursors, object types, and nested table/varray types.

    Key Comparison Rules:

    • Order Matters: Attributes in nested tables/arrays and columns in cursors are compared as ordered lists. If the order differs, the expectation fails. Use the unordered_columns option (see advanced data comparison guide) if column order is irrelevant.
    • Type Awareness: Comparison is data-type aware. A NUMBER column is not equal to a VARCHAR2 column, even if the values match.
    • Date Comparison: By default, DATE columns are compared by the date part only, ignoring the time. To include time, use ut.set_nls() and ut.reset_nls() to control the session's NLS settings.
    • Timestamp Precision: When comparing TIMESTAMP columns against TIMESTAMP bind variables, you must use the CAST operator to ensure the precision matches, otherwise, the comparison may fail due to Oracle's compatibility behavior.
    • Conversion Requirements:
      • To compare object types, convert them using anydata.convertObject().
      • To compare nested tables/varrays, convert them using anydata.convertCollection().
    • Limitations: utPLSQL cannot distinguish between NULL and whitespace-only values when comparing compound data due to Oracle XMLType limitations.
    -- Example: Comparing an object type
    exec ut.expect( anydata.convertObject( get_dept() ) ).to_equal( anydata.convertObject( department('HR') ) );
    
    -- Example: Comparing a collection
    exec ut.expect( anydata.convertCollection( t_tab_varchar('A') ) ).to_equal( anydata.convertCollection( t_tab_varchar('B') ) );
  3. How annotations work in utPLSQL

    develop

    Annotations allow you to configure tests and suites declaratively within your PL/SQL test packages. This eliminates the need for external configuration files or tables by storing test configuration directly alongside the test logic.

    Annotations follow a specific syntax:

    1. A single line comment starting with -- (double hyphen).
    2. Followed immediately by % (percent).
    3. Followed by the annotation name.
    4. Optionally followed by text in single brackets ().

    Syntax Rules:

    • All text between the first opening bracket ( and the last closing bracket ) on the line is treated as annotation text.
    • Annotations are case-insensitive, but lower-case is recommended.
    • Annotations must be placed in the package specification to be interpreted.
    • Do not place comments within the same line as an annotation to avoid unexpected behavior.
    --%suite(The name of my test suite)
  4. Understand the utPLSQL test execution order

    develop

    utPLSQL follows a hierarchical execution model using savepoints to manage transactions. The order of execution is determined by the nesting of annotations: suite > context > test.

    Key Rules:

    • Nesting: Annotations like --%beforeall or --%beforeeach inside a --%context block are scoped to that context. A --%beforeeach defined at the package level runs for every test, while a --%beforeeach inside a context runs only for tests within that context.
    • Ordering: utPLSQL does not guarantee the order of tests or contexts within a suite (it may be random). However, if multiple before/after procedures exist within the same block, they are executed in the order they appear in the package specification.
    • Transaction Control: The framework uses savepoints (e.g., before-suite, before-context, before-test) to ensure tests can be rolled back without affecting the entire suite.
    -- Example of hierarchical execution structure
    create or replace package test_employee_pkg is
      --%suite(Employee management)
      --%suitepath(com.my_company.hr)
    
      --%beforeall
      procedure setup_employees;
    
      --%context(add_employee)
        --%beforeeach
        procedure setup_for_add_employees;
    
        --%test(Inserts employee to emp table)
        procedure add_employee;  
      --%endcontext
    
      --%test(Test without context)
      procedure some_test;
    end test_employee_pkg;
  5. Organize tests and code under test

    develop

    To maintain a clean architecture and manage security effectively, follow these organizational patterns:

    • Package Separation: Always place tests and the code under test in separate packages. This maintains a fundamental separation of responsibilities.
    • Schema Strategy: It is common and recommended to keep test code in the same schema as the tested code. This simplifies testing by removing the need to manage complex cross-schema privileges.
  6. How code coverage works in utPLSQL

    develop

    utPLSQL includes a built-in coverage reporting engine that combines data from the Oracle packages DBMS_PROFILER and DBMS_PLSQL_CODE_COVERAGE.

    Supported Source Types

    Coverage is gathered for:

    • package bodies
    • type bodies
    • triggers
    • procedures
    • functions

    Limitations

    • Specifications Excluded: Package and type specifications are excluded from analysis to avoid false-negatives (reporting 0% coverage for non-executable code). Only executable bodies are analyzed.
    • Native Code: Code compiled as NATIVE will not report coverage.
    • Permissions: Coverage reporting depends heavily on the privileges of the user running the tests (see Security Model).
  7. Filter tests using Tag Expressions

    develop

    You can filter which tests, suites, or contexts are executed by using the a_tags parameter in ut.run. Tags are defined in your PL/SQL code using the --%tags annotation.

    Tag Operators

    Use boolean logic to combine tags:

    • ! : NOT
    • & : AND
    • | : OR
    • ( and ) : Grouping for precedence

    Reserved Keywords

    • any: Selects tests/suites that have at least one tag.
    • none: Selects tests/suites that have no tags. Note: Using none will exclude any tests or suites that are contained within a tagged parent suite.

    Examples

    • catalog | shipping: All tests tagged catalog OR shipping.
    • catalog & shipping: Only tests tagged with BOTH catalog AND shipping.
    • product & !end-to-end: All product tests, excluding those tagged end-to-end.
    -- Example: Run tests tagged 'fast' AND 'complex'
    select * from table(ut.run(a_tags => 'fast&complex'));
    
    -- Example: Run tests tagged 'api' or 'fast', but exclude 'complex'
    select * from table(ut.run(a_tags => '(api|fast)&!complex'));
  8. Understand utPLSQL exception handling and reporting

    develop

    utPLSQL traps most exceptions to prevent a single test or package from crashing the entire test run. When an exception occurs, the framework provides a full stacktrace that excludes utPLSQL library calls, focusing on your code.

    Important Exception Types:

    • Package State Invalidation: Exceptions like ORA-04068 and ORA-04061 are not handled by the framework to ensure rerunability. If these occur, test execution will be interrupted.
    • --%afterall Exceptions: If an exception is thrown in an afterall procedure, utPLSQL will not report a test failure. Instead, it will display a warning in the summary.
  9. Configure autonomous transactions for testing commits

    develop

    When testing code that performs explicit or implicit commits, you can set the test procedure to run as an autonomous transaction using pragma autonomous_transaction.

    Warning: When a test runs as an autonomous transaction, it will not see data prepared in a setup procedure unless that setup procedure explicitly committed its changes.

    procedure my_test_procedure is
      pragma autonomous_transaction;
    begin
      -- test logic that involves commits
    end;
  10. Coexist utPLSQL v3 with utPLSQL v2

    develop

    You can have both utPLSQL v2 and utPLSQL v3 installed on the same database simultaneously.

    Requirement: utPLSQL v3 must be installed in a different schema than the existing utPLSQL v2 installation. The two versions do not collide on public synonym names.

  11. How partial coverage works

    develop
    Partial coverage combines data from the profiler and block coverage. If a line is identified by both sources but block coverage shows it was only partially executed, the report will display the line as partially covered, specifying how many blocks on that line were executed out of the total. utPLSQL automatically manages the required Oracle dbms_plsql_code_coverage tables and permissions.
  12. How different exception locations affect test execution

    develop

    The impact of an exception depends on where it is thrown within the test lifecycle. Use this mapping to troubleshoot why tests are failing or being skipped:

    LocationImpact on Execution
    Invalid Package SpecPackage is excluded from execution. Explicitly running it raises an exception.
    Missing/Invalid BodyEvery --%test is reported as failed with an exception; nothing is executed.
    --%beforeallEvery --%test fails with an exception. --%test, --%beforeeach, --%beforetest, and --%aftertest are skipped. --%afterall is executed for cleanup.
    --%beforeeachEvery --%test fails with an exception. --%test, --%beforetest, and --%aftertest are skipped. --%aftereach and --%afterall are executed for cleanup.
    --%beforetestThe specific --%test fails with an exception and is not executed. --%aftertest, --%aftereach, and --%afterall are executed for cleanup.
    --%testThe specific --%test fails with an exception. Other tests and blocks continue normally.
    --%aftertestThe specific --%test fails with an exception. Other tests and blocks continue normally.
    --%aftereachEvery --%test in the package is reported as failed with an exception.
    --%afterallDoes not affect test results. A warning with the stacktrace is displayed in the summary.