1. User Guide
2. Overview
This guide aims to overview how the geography unit testing framework runs and how to create and use unit tests.
A document detailing basic configuration of the dtabase can be found here.
3. Tools
3.1. PostgreSQL
The version of postgresql avialable internally within the Bureau is 8.4.20 as of this writing. Hostility towards non-oracle, non-mysql databases from software approval groups within the bureau prevent newer versions from being implemented.
3.2. PostGIS
Postgis 1.5 was installed as it was the only source available and is compatibile with Postgres 8.4.
3.3. PGUnit
PGUnit is a 10+ year old framework for writing and running unit tests in PostgreSQL. More modern solutions area available but due to software restrictions imposed by the SWG we’re restricted to using an ancient version of PostgreSQL on the lehd2 cluster. PGUnit was chosen because it was written/designed for the available postgres version on hand.
From the original documentation:
3.3.1. What is PGUnit?
PGUnit is a xUnit-style framework for stored procedures in PostgreSQL 8.3+. It allows database developers to write automated tests for existed stored procedures or develop procedures using concepts of Test Driven Development (TDD). All test cases are stored in the database, so you don’t need any external resources (like files, version control, command-line utilities etc.) to save tests.
As in traditional xUnit, tests may be grouped in test case; each test-case may have its own environment initialization code ("fixture preparation code", or setUp block). The main benefit of PGUnit is that setUp block (usually quite CPU intensive) is executed only once, and its effect is stored in a savepoint. Then, all tests are executed from that savepoint, so the fixture initialization overheat is minimal. All tests are still executed independently, because their effects are automatically rolled back after the execution.
Download: dklab_pgunit_2008-11-09.sql
4. Configuration
4.1. Constants
A single file, 00_setenvvars.bash, contains the full set of constants needed for any modules or unit tests. This script is run in each module and sets some standard constants such as:
dbname=otmgeo
dbusername=*your username here*
dbport=5432
congresssession=116
acsdatayear=2016
tigeryear=2018
schemaprefix=*a custom prefix for the data vintage*
4.1.1. Schema Prefix
Setting a schema prefix allows multiple vintages to live beside each other in the database. For example, the following shows that we’re currently storing final versions of the data for an em patch, the OnTheMap 2018 vintage, and QWI 2018 vintage.
otmgeo=> \dn
List of schemas
Name | Owner
--------------------+----------
otm2018final | otmgeo
patchemfinal | otmgeo
qwi2018r1final | otmgeo
4.1.2. Module
At the end of each module a single line indicates which qa test script should be run. This script contains all unit tests corresponding to the current module.
For example, the module:
01_facesloader.bash
runs the following qa script
./01_facesloader_qa.bash run
4.2. Unit Test
4.2.1. Documentation Block
The unit tests contain a documentation block at the top that can be mostly ignored or minorly edited. It provides instructions for how to run each unit test and what arguments are expected. For example:
Short help
$ ./01_facesloader_qa.bash
Usage:
./01_facesloader_qa.bash <run|print|help>
Full help
$ ./01_facesloader_qa.bash help
SYNOPSIS
./01_facesloader_qa.bash <run|print|help>
DESCRIPTION
This program creates and runs the unit tests for the module.
Multiple unit tests must be separated by commas. All other
commands are separated by a newline for clarity.
OPTIONS
help Print the extended help
print Print unit tests to console
run Run unit tests
EXAMPLES
01_facesloader_qa.bash run
01_facesloader_qa.bash print
4.2.2. Load Constants and Unit modules
At the end of the header the unit test module begins by loading the necessary components. For example:
Load the constants
#set environment variables (dbname dbusername dbport schemaprefix)
source ./00_setenvvars.bash
In this case, the only expected constants are the dbname, dbusername, dbport and schemaprefix. They can be called as variables after this statement.
Load the unit tests
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/table_exists.bash
These steps load the needed unit tests for this module. In this case, all that’s happening is that the setup functions are made available, and that a single test to check for a table’s existence is loaded
4.2.3. Set the test module name and generate the sql
# name the test suite
suitename=test_01_facesloader
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(table_exists test1 'table exists' ${schemaprefix}public faces),
$(table_exists test2 'table exists' ${schemaprefix}public facesah)
$(end_unit)
SQL
This block creates a sql function called test_01_facesloader which runs two tests - a check that the faces table exists and the facesah table exists in the prefixed schema. The two lines above call a function called table_exists with 4 arguments.
The argument order may seem confusing but can be viewed in the source code for the unit test itself. For example:
$ head unit_tests/table_exists.bash
#!/bin/bash
table_exists() {
label=$1
description=$2
schema=$3
table=$4
...
4.2.4. Print the Unit Tests
Calling the unit test with the print argument will display the full sql function as seen by the database. This can be useful for testing and debuging. For example:
> ./01_facesloader_qa.bash print
CREATE OR REPLACE FUNCTION pgunit.test_01_facesloader()
RETURNS pgunit.testfunc[]
AS
$body$
SELECT pgunit.testcase(
...
4.2.5. Run the Unit Tests
Calling the unit test with the run argument will send the function to the database and run it as a unit test on the data from the associated module. The output of those unit tests will be displayed back to the user upon completion. For example:
> ./01_facesloader_qa.bash run
CREATE FUNCTION
NOTICE: *^*^*^*^*^
NOTICE:
NOTICE: PostgreSQL Unit Tests (PGUnit)
NOTICE:
NOTICE: pgunit.test_01_facesloader
NOTICE: - OK setUp (12 ms)
NOTICE: ! FAIL test1 (otm2018public.faces) table exists (5 ms)
NOTICE: ! FAIL test2 (otm2018public.facesah) table exists (1 ms)
NOTICE:
NOTICE: Time: 00:00:00
In the above block, the function was created and ran against the current database. Both tests report as failed because the data from the otm2018public schema doesn’t exist anymore. This only serves to demonstrate what to expect when running the unit test frameworks.
5. Tests
Unit tests are stored in a directory called unit_tests/ and are designed for reuseability. Arguments below begin at index 1 since, in bash, index 0 would reference the caller.
5.1. Start and End
All unit test blocks begin with a start_unit
call and end with a end_unit
call. The name of the set of tests is passed to the start module so that results can be recalled at a later date without needing to run the suite again.
$(start_unit $suitename)
*UNIT TESTS HERE*
$(end_unit)
5.2. Table Existence
This test determines whether a table exists in the database.
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
The text based explanation of what the test accomplishes |
3 |
schema |
The table schema to be checked (can include $variables) |
4 |
table |
The table name to be checked (can include $variables) |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/table_exists.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(table_exists test1 'public.faces exists' public faces)
$(end_unit)
SQL
5.3. Column Existence
This test determines whether a column exists within a table.
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
The text based explanation of what the test accomplishes |
3 |
schema |
The table schema to be checked (can include $variables) |
4 |
table |
The table name to be checked (can include $variables) |
5 |
column |
The column name to be checked (can include $variables) |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/column_exists.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(column_exists test1 'exo_id column exists' working aiannh exo_id)
$(end_unit)
SQL
5.4. Geometry Type
This test determines whether a column matches an expected postgis geometry type.
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
The text based explanation of what the test accomplishes |
3 |
schema |
The table schema to be checked (can include $variables) |
4 |
table |
The table name to be checked (can include $variables) |
5 |
geomtype |
The geometry type to be validated via ST_GeometryType() |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/geometry_is.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(geometry_is test15 'working.pointlm is type point' working pointlm ST_Point),
$(end_unit)
SQL
5.5. Where does not exist
This test determines whether there are any occurrences of a given where case.
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
The text based explanation of what the test accomplishes |
3 |
schema |
The table schema to be checked (can include $variables) |
4 |
table |
The table name to be checked (can include $variables) |
5 |
where |
The SQL statement to be passed to the where clause |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/does_not_exist_where.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(does_not_exist_where test1 'no undefined sldls in working.sldl' working sldl "sldlst like 'Z%'"),
$(end_unit)
SQL
5.6. Counts are equal between two tables
This test compares two tables or select statements and determines if the counts of the same variable are identical.
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
The text based explanation of what the test accomplishes |
3 |
tobecounted |
The variable to be compared (e.x. '*' or 'distinct col') |
4 |
firstselection |
The SQL statement or schema.table passed to the FROM clause |
5 |
secondselection |
the SQL statement or schema.table passed to the FROM clause |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/count_is_equal_where.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(count_is_equal_where test1 'count of distinct stfids match' 'distinct stfid' "public.facesxwalk" "working.block_xwalk_intpt")
$(end_unit)
SQL
5.7. Is equal to value
This test compares an input value against a simple select statement from a given table
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
The text based explanation of what the test accomplishes |
3 |
schema |
The table schema to be checked (can include $variables) |
4 |
table |
The table name to be checked (can include $variables) |
5 |
value |
The value to be compared |
6 |
select |
The SQL to be passed to the select statement |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/is_equal_to.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(is_equal_to test1 'all stfid of length 15 in working.cd_assignments' working cd_assignments 15 "DISTINCT char_length(stfid)")
$(end_unit)
SQL
5.8. Is equal to value where
This test compares an input value against a simple select statement from a given table with an additional where clause
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
The text based explanation of what the test accomplishes |
3 |
schema |
The table schema to be checked (can include $variables) |
4 |
table |
The table name to be checked (can include $variables) |
5 |
value |
The value to be compared |
6 |
select |
The SQL to be passed to the select statement |
7 |
where |
The SQL statement to be passed to the where clause |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/is_equal_to_where.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(is_equal_to_where test1 'all stfid of length 15 in working.cd_assignments in state 01' working cd_assignments 15 "char_length(stfid)" "st='01'")
$(end_unit)
SQL
5.9. Is equal to value where (multiple tables)
This test compares an input value against a select statement with a from and where variable
Order | Name | Description |
---|---|---|
1 |
label |
The high level label given as reference (e.g. test1) |
2 |
description |
the text based explanation of what the test accomplishes |
3 |
value |
The value to be compared |
4 |
select |
The SQL to be passed to the select statement |
5 |
from |
The SQL statement to be passed to the from clause |
6 |
where |
The SQL statement to be passed to the where clause |
#import the test modules used
source ./unit_tests/setup.bash
source ./unit_tests/is_equal_to_where_multi.bash
# create the unit test sql
read -r -d '' testsuitesql <<SQL >/dev/null
$(start_unit $suitename)
$(is_equal_to_where_multi test1 'at least one cd assignment updated' true 1 "working.block_xwalk_intpt AS main, public.facesxwalk AS sub" "main.stfid = sub.stfid AND main.stcd115 <> sub.stcd115")
$(end_unit)
SQL
6. Future
More tests are being added as the use case is needed