Skip to content
Snippets Groups Projects
Commit a2896595 authored by felmer's avatar felmer
Browse files

SqlUnitTestRunner: slightly improved, Javadoc added, tests added

SVN: 1383
parent 45c17338
No related branches found
No related tags found
No related merge requests found
...@@ -17,18 +17,48 @@ ...@@ -17,18 +17,48 @@
package ch.systemsx.cisd.common.db; package ch.systemsx.cisd.common.db;
import java.io.File; import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter; import java.io.FilenameFilter;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator;
import java.util.List; import java.util.List;
import ch.systemsx.cisd.common.utilities.FileUtilities; import ch.systemsx.cisd.common.utilities.FileUtilities;
import ch.systemsx.cisd.common.utilities.OSUtilities; import ch.systemsx.cisd.common.utilities.OSUtilities;
/** /**
* Runner of SQL Unit tests. Needs an implementation of {@link ISqlScriptExecutor} to do the actual tests.
* The runner executes all test scripts found in the specified test scripts folder. The folder should have the
* following structure
* <pre>
* &lt;<i>test script folder</i>&gt;
* &lt;<i>1. test case</i>&gt;
* buildup.sql
* 1=&lt;<i>first test</i>&gt;.sql
* 2=&lt;<i>second test</i>&gt;.sql
* ...
* teardown.sql
* &lt;<i>2. test case</i>&gt;
* ...
* ...
* </pre>
* The test cases are executed in lexicographical order of their name. For each test case <code>buildup.sql</code>
* will be executed first. The test scripts follow the naming schema
* <pre>
* &lt;<i>decimal number</i>&gt;=&lt;<i>test name</i>&gt;.sql
* </pre>
* They are executed in ascending order of their numbers. Finally <code>teardown.sql</code> is executed.
* If execution of <code>buildup.sql</code> failed all test scripts and the tear down script are skipped.
* Note that <code>buildup.sql</code> and <code>teardown.sql</code> are optional.
* <p>
* A script fails if its execution throws an exception. Its innermost cause (usually a {@link SQLException}) will
* be recorded together with the name of the test case and the script. All failed scripts will be recorded.
* <p>
* The runner throws an {@link AssertionError} if at least one script failed.
* *
*
* @author Franz-Josef Elmer * @author Franz-Josef Elmer
*/ */
public class SqlUnitTestRunner public class SqlUnitTestRunner
...@@ -75,23 +105,37 @@ public class SqlUnitTestRunner ...@@ -75,23 +105,37 @@ public class SqlUnitTestRunner
private final ISqlScriptExecutor executor; private final ISqlScriptExecutor executor;
private final PrintWriter writer; private final PrintWriter writer;
/**
* Creates an instance for the specified SQL script executor and writer.
*
* @param executor SQL script executor.
* @param writer Writer used to monitor running progress by printing test and test case names.
*/
public SqlUnitTestRunner(ISqlScriptExecutor executor, PrintWriter writer) public SqlUnitTestRunner(ISqlScriptExecutor executor, PrintWriter writer)
{ {
assert executor != null : "Undefined SQL script executor.";
assert writer != null : "Undefined writer.";
this.executor = executor; this.executor = executor;
this.writer = writer; this.writer = writer;
} }
public void run(File testScriptsFolder) /**
* Executes all scripts in the specified folder. Does nothing if it does not exists or if it is empty.
*
* @throws AssertionError if at least one script failed.
*/
public void run(File testScriptsFolder) throws AssertionError
{ {
if (testScriptsFolder.exists() == false) if (testScriptsFolder == null || testScriptsFolder.exists() == false)
{ {
return; // no tests return; // no tests
} }
File[] testCases = testScriptsFolder.listFiles(new FilenameFilter() File[] testCases = testScriptsFolder.listFiles(new FileFilter()
{ {
public boolean accept(File dir, String name) public boolean accept(File pathname)
{ {
return name.startsWith(".") == false; return pathname.isDirectory() && pathname.getName().startsWith(".") == false;
} }
}); });
Arrays.sort(testCases); Arrays.sort(testCases);
...@@ -105,7 +149,9 @@ public class SqlUnitTestRunner ...@@ -105,7 +149,9 @@ public class SqlUnitTestRunner
{ {
if (result.isOK() == false) if (result.isOK() == false)
{ {
builder.append("Test script ").append(getName(result.getTestScript())).append(" failed because of "); File testScript = result.getTestScript();
builder.append("Script '").append(testScript.getName()).append("' of test case '");
builder.append(testScript.getParentFile().getName()).append("' failed because of ");
builder.append(result.getThrowable()).append(OSUtilities.LINE_SEPARATOR); builder.append(result.getThrowable()).append(OSUtilities.LINE_SEPARATOR);
} }
} }
...@@ -118,20 +164,19 @@ public class SqlUnitTestRunner ...@@ -118,20 +164,19 @@ public class SqlUnitTestRunner
private void runTestCase(File testCaseFolder, List<TestResult> results) private void runTestCase(File testCaseFolder, List<TestResult> results)
{ {
writer.println("====== Test case " + testCaseFolder.getName() + " ======"); writer.println("====== Test case: " + testCaseFolder.getName() + " ======");
File buildupFile = new File(testCaseFolder, "buildup.sql"); File buildupFile = new File(testCaseFolder, "buildup.sql");
if (buildupFile.exists()) if (buildupFile.exists())
{ {
results.add(runScript(buildupFile)); TestResult result = runScript(buildupFile);
} results.add(result);
File[] testScripts = testCaseFolder.listFiles(new FilenameFilter() if (result.isOK() == false)
{ {
public boolean accept(File dir, String name) writer.println(" script failed: skip test scripts and teardown script.");
{ return;
return name.length() > 1 && name.charAt(1) == '='; }
} }
}); File[] testScripts = getTestScripts(testCaseFolder);
Arrays.sort(testScripts);
for (File testScript : testScripts) for (File testScript : testScripts)
{ {
results.add(runScript(testScript)); results.add(runScript(testScript));
...@@ -142,6 +187,25 @@ public class SqlUnitTestRunner ...@@ -142,6 +187,25 @@ public class SqlUnitTestRunner
results.add(runScript(teardownFile)); results.add(runScript(teardownFile));
} }
} }
private File[] getTestScripts(File testCaseFolder)
{
File[] testScripts = testCaseFolder.listFiles(new FilenameFilter()
{
public boolean accept(File dir, String name)
{
return getNumber(name) >= 0;
}
});
Arrays.sort(testScripts, new Comparator<File>()
{
public int compare(File f1, File f2)
{
return getNumber(f1.getName()) - getNumber(f2.getName());
}
});
return testScripts;
}
private TestResult runScript(File scriptFile) private TestResult runScript(File scriptFile)
{ {
...@@ -160,9 +224,19 @@ public class SqlUnitTestRunner ...@@ -160,9 +224,19 @@ public class SqlUnitTestRunner
} }
} }
private String getName(File testScript) private int getNumber(String name)
{ {
return testScript.getParentFile().getName() + File.separatorChar + testScript.getName(); int index = name.indexOf('=');
if (index < 0)
{
return -1;
}
try
{
return Integer.parseInt(name.substring(0, index));
} catch (NumberFormatException ex)
{
return -1;
}
} }
} }
\ No newline at end of file
/*
* Copyright 2007 ETH Zuerich, CISD
*
* 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.
*/
package ch.systemsx.cisd.common.db;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.fail;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.apache.commons.io.FileUtils;
import org.jmock.Mockery;
import org.jmock.api.Action;
import org.jmock.internal.Cardinality;
import org.jmock.internal.InvocationExpectationBuilder;
import org.jmock.lib.action.ThrowAction;
import org.jmock.lib.action.VoidAction;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import ch.systemsx.cisd.common.utilities.FileUtilities;
import ch.systemsx.cisd.common.utilities.OSUtilities;
/**
*
*
* @author Franz-Josef Elmer
*/
public class SqlUnitTestRunnerTest
{
private static final File TEST_SCRIPTS_FOLDER = new File("temporary_test_scripts_folder");
private Mockery context;
private ISqlScriptExecutor executor;
private StringWriter monitor;
private SqlUnitTestRunner testRunner;
@BeforeMethod
public void setup()
{
TEST_SCRIPTS_FOLDER.mkdir();
context = new Mockery();
executor = context.mock(ISqlScriptExecutor.class);
monitor = new StringWriter();
testRunner = new SqlUnitTestRunner(executor, new PrintWriter(monitor));
}
@AfterMethod
public void teardown()
{
assert FileUtilities.deleteRecursively(TEST_SCRIPTS_FOLDER);
// To following line of code should also be called at the end of each test method.
// Otherwise one do not known which test failed.
context.assertIsSatisfied();
}
@Test
public void testNullFolder()
{
testRunner.run(null);
assertEquals("", monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testNotExistingFolder()
{
testRunner.run(new File("blabla"));
assertEquals("", monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testEmptyFolder()
{
testRunner.run(TEST_SCRIPTS_FOLDER);
assertEquals("", monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testNonEmptyFolderButNoTestCases() throws IOException
{
assert new File(TEST_SCRIPTS_FOLDER, "some file").createNewFile();
assert new File(TEST_SCRIPTS_FOLDER, ".folder").mkdir();
testRunner.run(TEST_SCRIPTS_FOLDER);
assertEquals("", monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testEmptyTestCase()
{
assert new File(TEST_SCRIPTS_FOLDER, "my test case").mkdir();
testRunner.run(TEST_SCRIPTS_FOLDER);
assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR, monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testNonEmptyTestCaseButNoScripts() throws IOException
{
File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
assert testCaseFolder.mkdir();
assert new File(testCaseFolder, "blabla.sql").createNewFile();
assert new File(testCaseFolder, "folder").mkdir();
testRunner.run(TEST_SCRIPTS_FOLDER);
assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR, monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testTestCaseWithNoTestsButBuildupScript() throws IOException
{
File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
assert testCaseFolder.mkdir();
createScriptPrepareExecutor(new File(testCaseFolder, "buildup.sql"), "-- build up\n", null);
testRunner.run(TEST_SCRIPTS_FOLDER);
assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR
+ " execute script buildup.sql" + OSUtilities.LINE_SEPARATOR, monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testTestCaseWithFailingBuildupScript() throws IOException
{
File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
assert testCaseFolder.mkdir();
RuntimeException runtimeException = new RuntimeException("42");
createScriptPrepareExecutor(new File(testCaseFolder, "buildup.sql"), "-- build up\n", runtimeException);
try
{
testRunner.run(TEST_SCRIPTS_FOLDER);
fail("AssertionError expected");
} catch (AssertionError e)
{
assertEquals("Script 'buildup.sql' of test case 'my test case' failed because of "
+ runtimeException + OSUtilities.LINE_SEPARATOR, e.getMessage());
}
assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR
+ " execute script buildup.sql" + OSUtilities.LINE_SEPARATOR
+ " script failed: skip test scripts and teardown script." + OSUtilities.LINE_SEPARATOR,
monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testOrderOfExecutingTestScripts() throws IOException
{
File testCaseFolder = new File(TEST_SCRIPTS_FOLDER, "my test case");
assert testCaseFolder.mkdir();
RuntimeException runtimeException = new RuntimeException("42");
createScriptPrepareExecutor(new File(testCaseFolder, "9=b.sql"), "Select 9\n", runtimeException);
FileUtils.writeStringToFile(new File(testCaseFolder, "abc=abc.sql"), "Select abc\n");
createScriptPrepareExecutor(new File(testCaseFolder, "10=c.sql"), "Select 10\n", null);
createScriptPrepareExecutor(new File(testCaseFolder, "1=a.sql"), "Select 1\n", null);
try
{
testRunner.run(TEST_SCRIPTS_FOLDER);
fail("AssertionError expected");
} catch (AssertionError e)
{
assertEquals("Script '9=b.sql' of test case 'my test case' failed because of "
+ runtimeException + OSUtilities.LINE_SEPARATOR, e.getMessage());
}
assertEquals("====== Test case: my test case ======" + OSUtilities.LINE_SEPARATOR
+ " execute script 1=a.sql" + OSUtilities.LINE_SEPARATOR
+ " execute script 9=b.sql" + OSUtilities.LINE_SEPARATOR
+ " execute script 10=c.sql" + OSUtilities.LINE_SEPARATOR,
monitor.toString());
context.assertIsSatisfied();
}
@Test
public void testOrderOfExecutingTestCases() throws IOException
{
assert new File(TEST_SCRIPTS_FOLDER, "TC002").mkdir();
File testCaseFolder1 = new File(TEST_SCRIPTS_FOLDER, "TC001");
assert testCaseFolder1.mkdir();
createScriptPrepareExecutor(new File(testCaseFolder1, "buildup.sql"), "create table\n", null);
createScriptPrepareExecutor(new File(testCaseFolder1, "1=a.sql"), "Select 1\n", null);
createScriptPrepareExecutor(new File(testCaseFolder1, "2=b.sql"), "Select 2\n", null);
createScriptPrepareExecutor(new File(testCaseFolder1, "teardown.sql"), "drop table\n", null);
testRunner.run(TEST_SCRIPTS_FOLDER);
assertEquals("====== Test case: TC001 ======" + OSUtilities.LINE_SEPARATOR
+ " execute script buildup.sql" + OSUtilities.LINE_SEPARATOR
+ " execute script 1=a.sql" + OSUtilities.LINE_SEPARATOR
+ " execute script 2=b.sql" + OSUtilities.LINE_SEPARATOR
+ " execute script teardown.sql" + OSUtilities.LINE_SEPARATOR
+ "====== Test case: TC002 ======" + OSUtilities.LINE_SEPARATOR,
monitor.toString());
context.assertIsSatisfied();
}
private void createScriptPrepareExecutor(File scriptFile, String script, Throwable throwable) throws IOException
{
FileUtils.writeStringToFile(scriptFile, script);
InvocationExpectationBuilder builder = new InvocationExpectationBuilder();
builder.setCardinality(new Cardinality(1, 1));
builder.of(executor).execute(script);
Action action = throwable == null ? new VoidAction() : new ThrowAction(throwable);
context.addExpectation(builder.toExpectation(action));
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment