SQLUnit je testovací framework na regresné a unit automatizované testovanie SQL-storovaných procedúr. Regresné testovanie je testovanie všetkých, už predtým testovaných častí softvérového projektu po každej vykonanej zmene. Väčšina ľudí regresné testovanie vynecháva, pretože si myslí, že vykonaná zmena bola “neškodná”.
SQLUnit testovacie scenáre sa píšu vo formáte XML. SQLUnit samotný je napísaný v Jave, používa JUnit na prevod testovacích XML šecifikácií na JDBC volania a na porovnania výsledkov generovaných z JDBC volaní s očakávanými výsledkami.
Java programátori často používajú populárny JUnit na testovanie svojho kódu. Použitie SQLUnit-u na unit testy storovaných procedúry sa tak môže javiť prirodzeným pokračovaním procesu testovania. Poskytuje pokrytie testami DB-vrstvu, ktorá je často prehliadaná.
Príklad na rýchle pochopenie použitia SQL unitu
Stored procedure, napísana v T-SQL:
SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO IF (SELECT OBJECT_ID('testSP','P')) IS NOT NULL --means, the procedure already exists BEGIN PRINT 'Procedure already exists. So, dropping and recreating it' DROP PROC testSP END GO CREATE PROCEDURE [testSP] @msg nvarchar(100), @msg2 nvarchar(100) AS BEGIN select @msg+@msg2 as result END GO
Ako príklad slúži jednouchá SP pre MSSQL Server, ktorá má dva vstupné stringové parametre, ktoré “opíše” zo vstupu ako výsledok.
SQLUnit test, ktorý otestuje stored procedure:
<test name="Testing Stored Proc Hello-World"> <call connection-id="1"> <stmt>testSP ?,?</stmt> <param id="1" name="@msg" type="microsoft_sql_server.NVARCHAR">hello</param> <param id="2" name="@msg2" type="microsoft_sql_server.NVARCHAR">-world</param> </call> <result> <resultset id="1"> <row id="1"> <col id="1" name="result" type="microsoft_sql_server.NVARCHAR">hello-world2</col> </row> </resultset> </result> </test>
XML zápis SQLUnit testu – failne, pretože test posiela parametre “hello” a “–world”, ale očakáva “hello-world2”. Takto bude vyzerať výsledok testu – report:
Výsledok testu, ak opravíme očakávaný string na “hello-world”:
Na vytvorenie celej SQLUnit testovacej infraštruktúry budeme okrem vlastného SQLUnitu a jeho dependencies potrebovať 3 vlastné súbory:
- Build.xml – ANT-ovský build súbor, ktorý obsahuje testovacie targety. Spustí sa “ant o”.
- Connection.properties – súbor obsahujúci JDBC konfiguráciu na prístup k testovanej DB.
- Test01.xml – vlastný testovací script.
Build.xml:
- Target “o” – otvorí report – v tomto prípade Firefox a zobrazí html súbor result_test01.html.
- Závisí od targetu “report”, ktorý generuje výsledky testu z logu do html pomocu canoo XSLT.
- Závisí od targetu “def”,ktorý definuje vstupné súbory a formát report.
<?xml version="1.0"?> <project name="slqlunit" basedir="."> <description>SQLUnit tests tasks</description> <property name="sql.debug" value="false" /> <property name="out" value=""/> <target name="def"> <taskdef name="sqlunit" classname="net.sourceforge.sqlunit.ant.SqlunitTask"> <classpath> <pathelement location="..\sqlunit-5.0\lib\sqlunit-5.0.jar" /> <pathelement location="..\junit-4.7\junit-4.7.jar" /> <pathelement location="..\sqlunit-5.0\lib\log4j-1.2.13.jar" /> <pathelement location="..\jdom\jdom-1.1.jar" /> <pathelement location="..\xerces-2_9_0\xercesImpl.jar" /> <pathelement location="..\commons-jexl-2.1.1\commons-jexl-2.1.1.jar"/> <pathelement location="..\commons-logging-1.1.3\commons-logging-1.1.3.jar"/> <pathelement location="c:\MyProjects\workspaces\GBS\tools\sqljdbc\sqljdbc4.jar" /> </classpath> </taskdef> </target> <!-- This is repeated for each test or group of tests in case of nested filesets --> <target name="run" depends="def"> <delete file="..\results\result_test01.log"/> <sqlunit testfile="test01.xml" haltOnFailure="false" debug="${sql.debug}" logfile="..\results\result_test01.log" logformat="canoo"/> </target> <!-- reporting --> <target name="report" depends="run"> <delete file="..\results\result_test01.html" /> <xslt in="..\results\result_test01.log" out="..\results\result_test01.html" style="..\sqlunit-5.0\etc\canoo2html_result_transform.xsl" /> </target> <!— opening report --> <target name="o" depends="report" description="Opens the html result file in the browser"> <echo message="Opening result file ..."/> <exec executable="C:\Program Files (x86)\Mozilla Firefox\firefox.exe" spawn="true"> <arg line="..\results\result_test01.html"/> </exec> </target> </project>
Conection.properties:
sqlunit.driver = oracle.jdbc.driver.OracleDriver
sqlunit.url = jdbc:oracle:thin:@localhost:1521:XE
sqlunit.user = roman
sqlunit.password = hesteric
Test01.xml:
Súbor Test01.xml demonštruje okrem vlastého testu storovanej procedúry aj ukážku priameho selectu z tabulky (<testname=”Select with param”>).
<?xml version="1.0"?> <!DOCTYPE sqlunit SYSTEM "file:docs/sqlunit.dtd"> <sqlunit> <connection connection-id="1" extern="connection.properties" transaction-support="off"/> <test name="Select with param"> <sql connection-id="1"> <stmt>select 1 from TABLE where COLUMN=?</stmt> <param id="1" name="@ObjektName" type="NVARCHAR">Text123</param> </sql> <result> <resultset id="1"> <row id="1"> <col id="1" name="col1" type="INTEGER">1</col> </row> </resultset> </result> </test> <test name="Testing Stored Proc Hello-World"> <call connection-id="1"> <stmt>testSP ?,?</stmt> <param id="1" name="@msg" type="microsoft_sql_server.NVARCHAR">hello</param> <param id="2" name="@msg2" type="microsoft_sql_server.NVARCHAR">-world</param> </call> <result> <resultset id="1"> <row id="1"> <col id="1" name="result" type="microsoft_sql_server.NVARCHAR">hello-world2</col> </row> </resultset> </result> </test> </sqlunit>
Vlastné spustenie skriptu bude na konzole vyzerať takto:
A takto by vyzerala storovana procedure pre Oracle aj s jej SQLUnit testom:
CREATE OR REPLACE PROCEDURE get_emp_name(p_empno IN NUMBER,p_empname OUT VARCHAR2) AS CURSOR emp_csr IS SELECT ename FROM emp WHERE empno = p_empno; BEGIN OPEN emp_csr; FETCH emp_csr INTO p_empname; CLOSE emp_csr; END get_emp_name; <sqlunit> <connection> <driver>oracle.jdbc.driver.OracleDriver</driver> <url>jdbc:oracle:thin:@localhost:1521:orcl</url> <user>roman</user> <password>hesteric</password> </connection> <test name="Simple PROC call" failure-message="Error with Simple PROC call"> <call> <stmt>{call get_emp_name(?,?)}</stmt> <param id="1" name="p_empno" type="INTEGER" inout="in">7934</param> <param id="2" name="p_empname" type="VARCHAR" inout="out"></param> </call> <result> <outparam id="2" type="VARCHAR">MILLER</outparam> </result> </test> </sqlunit>
A na záver troška teórie
Softvér má nanešťastie jednu zaujímavú vlastnosť. Občas sa totiž správa neočakávateľne. Preto je nevyhnutné vykonať regresné testovanie na overenie, či sa testovaný softvér správa tak ako pred vykonanou zmenou.
Ak sa teda vykoná nejaká zmena, či už za účelom odstránenia chyby, rozšírenia funkčnosti alebo zvýšenia výkonnosti softvéru, je potrebné otestovať, či bola zmena vykonaná tak, ako sa predpokladalo. To znamená, že je potrebné overiť si, či boli odstránené chyby, ktoré sa mali odstrániť, či nové časti systému fungujú správne, alebo či sa zvýšila výkonnosť.