utPLSQL: a JUnit style unit testing framework for Oracle PL/SQL
Testing Framework for PL/SQL
At a glance
- What is it?
- utPLSQL is an Apache-2.0 unit testing framework for Oracle PL/SQL and SQL, with matchers, annotation defined suites, automatic transaction control, coverage reporting and reporters for SonarQube, Jenkins and TeamCity. It needs Oracle Database 19c or newer, and its v3 release line is a substantial rewrite of the v2 framework.
- Who is it for?
- utPLSQL suits a team that writes PL/SQL and wants unit tests that run inside the database, in any Oracle client or from a command line, with coverage and CI reporting included. It does not suit a project that cannot run PL/SQL in the database itself, and it is a poor fit for a team unwilling to adopt the v3 annotation and expectation model, since the project publishes a version 2 to version 3 comparison precisely because the change was large.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly PLSQL, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Testing that runs where the code runs
utPLSQL is a unit testing framework for Oracle PL/SQL and SQL, and the project states that it follows the industry standards and best practices of modern testing frameworks like JUnit and RSpec. That framing is deliberate, because PL/SQL testing has traditionally been the awkward corner of database work: the code under test runs in the database engine, so the test runner has to be there too.
The requirement is Oracle Database 19c or newer, and that is the whole platform story. There is no separate application server, no external test process and no agent to install. The framework is installed into a database schema, and tests are PL/SQL packages that call into your code and assert on the results.
The feature list is worth reading in full because it shows how much of the usual test framework machinery has been reproduced in PL/SQL. There are multiple ways to compare data with matchers, and native comparison of complex types, meaning objects, collections and cursors, which is the part that is genuinely hard to do well in a database. There is in-depth and consistent reporting of failures and errors, tests identified and configured by annotations, hierarchies of test suites configured with annotations, automatic and configurable transaction control, and built-in coverage reporting. There is integration with SonarQube, Jenkins and TeamCity through reporters, and a plugin architecture for reporters and matchers, so an organisation can add its own comparison rules or output formats.
The last item on that list is the one with the most use for day to day use: flexible and simple test invocation, with multi-format reporting from the command line client.
The project is stewarded by utPLSQL Development Labs Ltd, and the code is licensed under Apache 2.0, with contributors retaining copyright of their contributions.
A test package is a package with annotations
The quick start is the clearest statement of the model. You write a package specification, annotate it to define the suite and its tests, and then implement each test procedure in the package body.
The specification carries the annotations and the procedure declarations:
create or replace package test_between_string as
-- %suite(Between string function)
-- %test(Returns substring from start position to end position)
procedure normal_case;
-- %test(Returns null for null input string value)
procedure null_string;
end;
/The body holds the actual assertions, and the assertion syntax is the part borrowed from the RSpec family. `ut.expect(...)` takes a value and `.to_(...)` states what it should be, with `equal('2345')` for a value and `be_null` for the null case:
create or replace package body test_between_string as
procedure normal_case is
begin
ut.expect( betwnstr( '1234567', 2, 5 ) ).to_( equal('2345') );
end;
procedure null_string is
begin
ut.expect( betwnstr( null, 2, 5 ) ).to_( be_null );
end;
end;
/Two details are worth noticing. The `equal` matcher takes a plain literal rather than a string comparison, so the framework can distinguish a null from an empty string, which is the single most common source of quietly wrong PL/SQL tests. And the suite hierarchy is built from annotations, so nested directories of tests become nested suites without any configuration file.
The framework's own build artefacts are visible in the repository listing, with `source/`, `test/`, `development/`, `docs/` and an `examples/` directory that includes `RunAllExamples.sql`, `RunAllExamplesAsTests.sql`, `RunCustomReportersExamples.sql` and `RunWithDocumentationReporter.sql`, along with example packages such as `test_pkg1.pck` and `test_pkg2.pck`.
Three ways to run the suite from any Oracle client
Running the tests needs no special tooling, because the runner is a call into the schema. The documentation says the commands work from any Oracle-compatible IDE, naming SQL Developer, TOAD and PL/SQL Developer, and offers three forms.
The shortest is a single statement:
exec ut.run();The explicit block form is:
begin
ut.run();
end;
/and the result set form is:
select * from table(ut.run());All three run all the suites in the current schema, and the results are reported to `DBMS_OUTPUT` or returned as a result set depending on which form you use. The third form is the one that matters for anything automated, because a result set can be consumed by a query rather than scraped from a message buffer.
The output itself is a plain hierarchical listing, the suite name, then the test names underneath:
Between string function
Returns substring from start position to end position
Returns null for null input string value
Finished in .036027 seconds
2 tests, 0 failuresThe transaction control deserves a mention because it is the feature that makes this usable in a real project. Automatic and configurable transaction control means the framework handles the rollback between tests, so a test that leaves data behind does not poison the next one. Without that, every test package would need its own cleanup and the suite would be order dependent, which is the usual reason PL/SQL test suites stop being trusted.
The installation itself is a single script. To install utPLSQL into a new database schema and grant it to public, execute the script `install_headless.sql`, which creates a new user `UT3`, grants all required privileges and creates PUBLIC synonyms. The name is literal: it does the install without any interactive steps. All install options are covered by the Install Guide in the documentation.
The command line client and coverage for CI
The command line client is a separate project, utPLSQL-cli, and the distinction matters. The framework itself is the thing you install into the database; the client is the thing you run from a shell.
The reason the client exists is stated plainly: it lets you run tests without an Oracle Client or IDE. That is a different constraint from anything in the framework, because a CI runner that does not have SQL*Plus installed cannot call `exec ut.run();` at all, and a container that only carries the database client cannot reproduce a developer's local workflow.
The client provides real-time test reporting, simultaneous output of multiple report formats into different files, and source files and test files mapping for coverage reports. That last item is the bridge to the framework's built-in coverage reporting, and the mapping is what lets a coverage figure be attributed to the right source file when the test tree and the source tree do not share a layout.
The reporters close the loop with the wider toolchain. Integration with SonarQube, Jenkins and TeamCity through reporters means a coverage result can end up in the same place as the rest of a project's build feedback, and the plugin architecture for reporters and matchers is what keeps that extensible rather than hard coded.
The build and quality setup in the repository reflects the same intent. There is a `sonar-project.properties` file at the root, a GitHub Actions build workflow, and a quality gate badge and a coverage badge pointing at SonarCloud, along with a `BUILD_NO` file and a `VERSION` file. For a framework that is used to test other people's code, having its own coverage measured is not decoration.
Stewardship, sponsorship and what the project sells
utPLSQL is free and open source under the Apache 2.0 licence, and the README explains who stewards it: utPLSQL Development Labs Ltd, which holds the utPLSQL name, branding and project assets, and manages sponsorships and funded development. Contributors retain copyright of their contributions, which are licensed under the Apache 2.0 licence.
That structure is worth understanding, because it is a specific model rather than a vague appeal for donations. A company holds the trademark and the project assets, which means the name cannot be taken by someone else, and it runs the sponsorship programme, which means the funding has an organisation behind it rather than a personal account. The link to the announcement about the incorporation of utPLSQL Development Labs Ltd is in the README.
Sponsorship is described as funding the development of new features and tools, maintaining compatibility with new Oracle versions and platforms, and documentation. The sponsorship page is said to offer organisation tiers, commercial support and individual sponsorship, and all current and past sponsors are listed in `SPONSORS.md` in the repository.
For an adopter, the practical reading is that the framework itself is free and permissively licensed, and the paid tier is support and influence over the roadmap. A team that wants a support contract, or that needs a feature to land on a schedule, has a route to ask for it. A team that just wants the framework does not need to buy anything.
The licence itself is Apache 2.0, with the licence text in the `LICENSE` file, and contributors keep their copyright, which is the arrangement most companies find straightforward to work with internally.
Versions, the v2 to v3 move, and where the documentation lives
The default branch is `develop`, and the release history is recent and regular. The tagged versions are v.3.2.3 from 2026-07-10, v3.2.2 from 2026-05-24 and v3.2.01 from 2026-04-22. The repository is not archived and the last push was on 2026-10-20, so the branch is active even between releases.
The versioning scheme itself has a quirk worth flagging, because a version picker that assumes semantic versioning will trip on it. v.3.2.3 and v3.2.01 are not the same string, so a tool comparing tags as text can order them wrongly, and a two component patch level like 3.2.01 needs a numeric comparison rather than a lexicographic one. The v3.2.01 form is also a three part number where the last component is zero padded, which is another place for naive comparisons to go wrong.
The bigger question for anyone with an existing suite is the v2 to v3 change. The README specifically points migrating users to a version 2 to version 3 comparison document, which tells you the change was large enough to need a document. The v3 model is the annotation and expectation style described earlier, and a v2 suite would have to be rewritten to it rather than adjusted.
Documentation is published as a static site at utplsql.org/utPLSQL, built with MkDocs, since the repository carries `mkdocs.yml` and `mkdocs_offline.yml` for an offline build. There are also cheat-sheets hosted on Cheatography for quick reference, the `CHANGELOG.md` for the full version history, and a `CONTRIBUTING.md` and code of conduct for anyone who wants to contribute. GitHub Discussions is named as the place to ask questions.
Two channel decisions are worth making early. Questions and proposals go to Discussions rather than to the issue tracker, and the sponsorship page is the place to look before committing a team to a plan, since it is where compatibility commitments and commercial support are described.
Editorial conclusion
utPLSQL suits a team that writes PL/SQL and wants unit tests that run inside the database, in any Oracle client or from a command line, with coverage and CI reporting included. It does not suit a project that cannot run PL/SQL in the database itself, and it is a poor fit for a team unwilling to adopt the v3 annotation and expectation model, since the project publishes a version 2 to version 3 comparison precisely because the change was large. Before adopting, confirm your database is on 19c or newer, run install_headless.sql into a schema of its own, and read the v2 to v3 comparison if you are migrating an existing suite.
Frequently asked questions
What database version does utPLSQL require?
Oracle Database 19c or newer. The framework runs inside the database as PL/SQL, so there is no external test process to install, but the database version is a hard requirement.
How do I install utPLSQL into a database?
Execute the script `install_headless.sql` to install it into a new database schema and grant it to public. The script creates a new user `UT3`, grants all required privileges and creates PUBLIC synonyms, and other install options are covered by the Install Guide in the documentation.
How do I run a utPLSQL test suite?
From any Oracle-compatible IDE you can run `exec ut.run();`, the `begin ut.run(); end;` block, or `select * from table(ut.run());`. All three run every suite in the current schema and report results to `DBMS_OUTPUT` or as a result set.
Can utPLSQL tests run without an Oracle Client or IDE?
Yes, through the separate utPLSQL-cli project, which provides real-time test reporting, simultaneous output of multiple report formats into different files, and source to test file mapping for coverage reports.
Is migrating from utPLSQL v2 to v3 a small change?
No, the project publishes a dedicated version 2 to version 3 comparison because the change was substantial. Version 3 uses the annotation based suite definition and the `ut.expect(...).to_(...)` expectation style, so an existing v2 suite has to be rewritten rather than adjusted.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/utplsql-utplsql)Was this useful?