Compare commits

...
9 Commits
Author SHA1 Message Date
KKlochko b99f90132a Add the main entry point.
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-10-10 21:43:19 +03:00
KKlochko 3d6b7b4d58 Add a constructor to set only a formatter for CLIReporter.
continuous-integration/drone/tag Build is passing
2023-10-10 21:41:43 +03:00
KKlochko d1399dac02 Add the CLITestRunner to run tests and to report the result.
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-10-10 21:38:47 +03:00
KKlochko 59d8fe7a4f Update the CLI test formatting.
continuous-integration/drone/tag Build is passing
2023-10-10 21:33:15 +03:00
KKlochko c27d3eca13 Add the SingleTestLoader to create an object of a loaded Test class.
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-10-08 19:57:51 +03:00
KKlochko 1e3446a431 Add a constructor with a default factory for TestCommandQueueFactory.
continuous-integration/drone/tag Build is passing
2023-10-08 19:56:10 +03:00
KKlochko b1ef8dc32f Fix the test title for SingleTestClassLoader.
continuous-integration/drone/tag Build is passing
2023-10-08 19:55:30 +03:00
KKlochko b57b005d97 Add the CLIReporter and its tests.
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-10-07 20:11:45 +03:00
KKlochko 9945480a6a Add the AbstractReporter. 2023-10-07 20:10:53 +03:00
12 changed files with 507 additions and 7 deletions
@@ -0,0 +1,24 @@
package space.kklochko.simple_jbdd;
import space.kklochko.simple_jbdd.tests.factories.AbstractTestRunnerFactory;
import space.kklochko.simple_jbdd.tests.factories.SimpleTestRunnerFactory;
import space.kklochko.simple_jbdd.tests.loaders.NameClassLoader;
import space.kklochko.simple_jbdd.tests.reports.AbstractReporter;
import space.kklochko.simple_jbdd.tests.reports.CLIReporter;
import space.kklochko.simple_jbdd.tests.reports.fmt.SimpleTestReportFormatter;
import space.kklochko.simple_jbdd.tests.runners.CLITestRunner;
public class Main {
public static void main(String[] args) {
NameClassLoader loader = new NameClassLoader();
AbstractTestRunnerFactory testRunnerFactory = new SimpleTestRunnerFactory();
AbstractReporter reporter = new CLIReporter(new SimpleTestReportFormatter());
CLITestRunner runner = new CLITestRunner(loader, testRunnerFactory, reporter);
boolean status = runner.run();
System.exit(status ? 0 : 1);
}
}
@@ -9,6 +9,11 @@ public class TestCommandQueueFactory {
private ArrayList<Test> tests;
private TestCommandFactory factory;
public TestCommandQueueFactory(ArrayList<Test> tests) {
setTests(tests);
setFactory(new TestCommandFactory());
}
public TestCommandQueueFactory(ArrayList<Test> tests, TestCommandFactory factory) {
setTests(tests);
setFactory(factory);
@@ -0,0 +1,50 @@
package space.kklochko.simple_jbdd.tests.loaders;
import space.kklochko.simple_jbdd.tests.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class SingleTestLoader {
String className;
public SingleTestLoader(String className) {
setClassName(className);
}
public Test load() {
SingleClassLoader loader = new SingleClassLoader(getClassName());
Class<?> aClass = loader.load();
if(aClass == null)
return null;
Constructor defaultConstructor = null;
for(Constructor constructor : aClass.getDeclaredConstructors()) {
if(constructor.getParameterCount() == 0) {
defaultConstructor = constructor;
break;
}
}
try {
return (Test) defaultConstructor.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
}
@@ -0,0 +1,40 @@
package space.kklochko.simple_jbdd.tests.reports;
import space.kklochko.simple_jbdd.tests.factories.meta.AbstractTestCommandMetaDataFactory;
import space.kklochko.simple_jbdd.tests.reports.fmt.AbstractTestReportFormatter;
import java.util.AbstractMap;
import java.util.ArrayList;
public abstract class AbstractReporter {
ArrayList<AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>> Metadata;
AbstractTestReportFormatter formatter;
public AbstractReporter(ArrayList<AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>> metadata, AbstractTestReportFormatter formatter) {
setMetadata(metadata);
setFormatter(formatter);
}
abstract public void report();
public ArrayList<AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>> getMetadata() {
return Metadata;
}
public void setMetadata(ArrayList<AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>> metadata) {
Metadata = metadata;
}
public void addMetadata(AbstractTestCommandMetaDataFactory testMetaData, boolean isPassed) {
getMetadata().add(new AbstractMap.SimpleEntry<>(testMetaData, isPassed));
}
public AbstractTestReportFormatter getFormatter() {
return formatter;
}
public void setFormatter(AbstractTestReportFormatter formatter) {
this.formatter = formatter;
}
}
@@ -0,0 +1,99 @@
package space.kklochko.simple_jbdd.tests.reports;
import space.kklochko.simple_jbdd.tests.factories.meta.AbstractTestCommandMetaDataFactory;
import space.kklochko.simple_jbdd.tests.reports.fmt.AbstractTestReportFormatter;
import java.util.AbstractMap;
import java.util.ArrayList;
import org.apache.commons.lang3.StringUtils;
public class CLIReporter extends AbstractReporter {
final int terminalWidth = 80;
public CLIReporter(AbstractTestReportFormatter formatter) {
super(null, formatter);
}
public CLIReporter(ArrayList<AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>> metadata, AbstractTestReportFormatter formatter) {
super(metadata, formatter);
}
public void report() {
ArrayList<AbstractMap.SimpleEntry<String, Boolean>> testData = format();
ArrayList<AbstractMap.SimpleEntry<String, Boolean>> failedTestData = getFailedTests(testData);
int passed = count(testData, true);
int failed = count(testData, false);
System.out.printf("%s\n\n", center(" Tests "));
printTests(testData);
if(failed != 0) {
System.out.printf("%s\n\n", center(" Errors "));
printTests(failedTestData);
}
System.out.printf("%s\n", center(" Summary "));
System.out.print(summary(passed, failed));
}
public ArrayList<AbstractMap.SimpleEntry<String, Boolean>> format() {
ArrayList<AbstractMap.SimpleEntry<String, Boolean>> data = new ArrayList<>();
for(var pair : getMetadata()) {
boolean isPassed = pair.getValue();
getFormatter().setTestMetaData(pair.getKey().create());
getFormatter().setPassed(isPassed);
data.add(new AbstractMap.SimpleEntry<>(getFormatter().format(), isPassed));
}
return data;
}
public ArrayList<AbstractMap.SimpleEntry<String, Boolean>> getFailedTests(ArrayList<AbstractMap.SimpleEntry<String, Boolean>> testData) {
ArrayList<AbstractMap.SimpleEntry<String, Boolean>> failed = new ArrayList<>();
for(var pair : testData) {
boolean isPassed = pair.getValue();
if(isPassed) continue;
failed.add(pair);
}
return failed;
}
public int count(ArrayList<AbstractMap.SimpleEntry<String, Boolean>> testData, boolean isPassedStatus) {
int count = 0;
for(var pair : testData) {
boolean isPassed = pair.getValue();
if(isPassed == isPassedStatus)
count++;
}
return count;
}
public void printTests(ArrayList<AbstractMap.SimpleEntry<String, Boolean>> testData) {
for(var pair : testData) {
System.out.print(pair.getKey());
}
}
public String center(String content) {
return StringUtils.center(content, 80, '=');
}
public String summary(int passed, int failed) {
int all = passed + failed;
double passedPercent = (double) passed / (double) all * 100.0;
double failedPercent = (double) failed / (double) all * 100.0;
return String.format("Ran %d tests.\nPassed: %d (%.2f%%)\nFailed: %d (%.2f%%)\n", all, passed, passedPercent, failed, failedPercent);
}
}
@@ -25,10 +25,10 @@ public class SimpleTestReportFormatter extends AbstractTestReportFormatter {
for(int i=1; i<getTestMetaData().size(); ++i) {
AbstractMap.SimpleEntry<String, String> pair = getTestMetaData().get(i);
result += String.format("\t%s %s\n", pair.getKey(), pair.getValue());
result += String.format("\t%s: %s\n", pair.getKey(), pair.getValue());
}
return result;
return result + '\n';
}
public String getPassedIcon() {
@@ -0,0 +1,102 @@
package space.kklochko.simple_jbdd.tests.runners;
import space.kklochko.simple_jbdd.tests.Test;
import space.kklochko.simple_jbdd.tests.commands.AbstractTestCommand;
import space.kklochko.simple_jbdd.tests.factories.AbstractTestRunnerFactory;
import space.kklochko.simple_jbdd.tests.factories.TestCommandQueueFactory;
import space.kklochko.simple_jbdd.tests.factories.meta.AbstractTestCommandMetaDataFactory;
import space.kklochko.simple_jbdd.tests.factories.meta.TestCommandMetaDataFactory;
import space.kklochko.simple_jbdd.tests.loaders.AbstractNameClassLoader;
import space.kklochko.simple_jbdd.tests.loaders.SingleTestLoader;
import space.kklochko.simple_jbdd.tests.reports.AbstractReporter;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class CLITestRunner extends AbstractTestRunner {
private ArrayList<AbstractTestCommand<Test>> commands;
private AbstractNameClassLoader classLoader;
private AbstractTestRunnerFactory runnerFactory;
private AbstractReporter reporter;
public CLITestRunner(AbstractNameClassLoader classLoader, AbstractTestRunnerFactory runnerFactory, AbstractReporter reporter) {
setClassLoader(classLoader);
setRunnerFactory(runnerFactory);
setReporter(reporter);
}
public boolean run() {
commands = createCommands(load());
ArrayList<Boolean> testStatuses = runTests(commands);
report(createMetadata(commands, testStatuses));
return isFailed(testStatuses);
}
public ArrayList<Test> load() {
Set<Class<?>> classes = this.classLoader.load();
ArrayList<Test> tests = new ArrayList();
for(var aClass : classes) {
tests.add((new SingleTestLoader(aClass.getName())).load());
}
return tests;
}
public ArrayList<AbstractTestCommand<Test>> createCommands(ArrayList<Test> tests) {
TestCommandQueueFactory factory = new TestCommandQueueFactory(tests);
return factory.create();
}
public ArrayList<Boolean> runTests(ArrayList<AbstractTestCommand<Test>> commands) {
return commands.stream()
.map(c -> runnerFactory.create(c).run())
.collect(Collectors.toCollection(ArrayList::new));
}
public ArrayList<AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>> createMetadata(ArrayList<AbstractTestCommand<Test>> commands, ArrayList<Boolean> testStatuses) {
return IntStream.range(0, commands.size())
.mapToObj(i -> new AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>(
new TestCommandMetaDataFactory(commands.get(i)),
testStatuses.get(i)))
.collect(Collectors.toCollection(ArrayList::new));
}
public void report(ArrayList<AbstractMap.SimpleEntry<AbstractTestCommandMetaDataFactory, Boolean>> metadata) {
reporter.setMetadata(metadata);
reporter.report();
}
public boolean isFailed(ArrayList<Boolean> testStatuses) {
return !testStatuses.stream().anyMatch(b -> b == false);
}
public AbstractNameClassLoader getClassLoader() {
return classLoader;
}
public void setClassLoader(AbstractNameClassLoader classLoader) {
this.classLoader = classLoader;
}
public AbstractTestRunnerFactory getRunnerFactory() {
return runnerFactory;
}
public void setRunnerFactory(AbstractTestRunnerFactory runnerFactory) {
this.runnerFactory = runnerFactory;
}
public AbstractReporter getReporter() {
return reporter;
}
public void setReporter(AbstractReporter reporter) {
this.reporter = reporter;
}
}
@@ -9,7 +9,7 @@ import spock.lang.Title
@Narrative("""The loader must return a class or null, so
those tests check if responses are right.
""")
@Title("Integrated tests for ClassLoader.")
@Title("Integrated tests for SingleClassLoader.")
class IntegratedSingleClassLoader extends Specification {
def "The class has been loaded."() {
given: "I have a class loader"
@@ -0,0 +1,31 @@
package space.kklochko.simple_jbdd.tests.loaders
import space.kklochko.simple_jbdd.test_examples.tests.SimpleThenTest
import spock.lang.Narrative
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Title
@Narrative("""The loader must return a class or null, so
those tests check if responses are right.
""")
@Title("Integrated tests for SingleTestLoader.")
class IntegratedSingleTestLoader extends Specification {
def "The class has been loaded."() {
given: "I have a class loader"
@Subject
def loader = new SingleTestLoader(className)
when: "The class was loaded"
def aClass = loader.load()
then: "Checking that the test result is expected"
aClass?.class == expectedClass
where: "Possible variants of tests"
className || expectedClass
"fourHundredFour" || null
"space.kklochko.simple_jbdd.test_examples.tests.SimpleThenTest" || SimpleThenTest
}
}
@@ -0,0 +1,72 @@
package space.kklochko.simple_jbdd.tests.reports
import space.kklochko.simple_jbdd.tests.factories.meta.AbstractTestCommandMetaDataFactory
import space.kklochko.simple_jbdd.tests.reports.fmt.AbstractTestReportFormatter
import space.kklochko.simple_jbdd.tests.reports.fmt.SimpleTestReportFormatter
import spock.lang.Narrative
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Title
@Narrative("""The reporter must generate a report in the same format, so
those tests check if the reporter generate it right.
""")
@Title("Unit tests for CLIReporter.format()")
class CLIReporterFormatMethodSpec extends Specification {
private metaData
def setup() {
def factory = Mock(AbstractTestCommandMetaDataFactory) {
create() >> [
new AbstractMap.SimpleEntry<>("Title", "A simple test"),
new AbstractMap.SimpleEntry<>("Then", "Then block")
]
}
def metaData = [
new AbstractMap.SimpleEntry<>(factory, true),
new AbstractMap.SimpleEntry<>(factory, false)
]
this.metaData = metaData
}
def "Format function use formatter twice."() {
given: "I have a formatter"
def formatter = Mock(AbstractTestReportFormatter)
and: "I have a reporter"
@Subject
def reporter = new CLIReporter(metaData, formatter)
when: "Generate the summary format"
def formattedTestData = reporter.format()
then: "The methods must called 2 times"
2 * formatter.format()
2 * formatter.setPassed(_)
2 * formatter.setTestMetaData(_)
and: "The data must have 2 elements"
2 == formattedTestData.size()
}
def "Format function returns the formatted data."() {
given: "I have a formatter"
def formatter = new SimpleTestReportFormatter()
and: "I have a reporter"
@Subject
def reporter = new CLIReporter(metaData, formatter)
when: "Generate the summary format"
def formattedTestData = reporter.format()
then: "The format must have not null values"
formattedTestData.every({ it.getKey() != null })
and: "The data must have 2 elements"
2 == formattedTestData.size()
}
}
@@ -0,0 +1,77 @@
package space.kklochko.simple_jbdd.tests.reports
import space.kklochko.simple_jbdd.tests.factories.meta.AbstractTestCommandMetaDataFactory
import space.kklochko.simple_jbdd.tests.reports.fmt.AbstractTestReportFormatter
import space.kklochko.simple_jbdd.tests.reports.fmt.SimpleTestReportFormatter
import spock.lang.Narrative
import spock.lang.Specification
import spock.lang.Subject
import spock.lang.Title
@Narrative("""The reporter must generate a report in the same format, so
those tests check if the reporter generate it right.
""")
@Title("Unit tests for CLIReporter")
class CLIReporterSpec extends Specification {
def "Function, getFailedTest, returns the data of failed tests."() {
given: "I have a reporter"
@Subject
def reporter = new CLIReporter(null, null)
and: "I have a testData"
def passedTestPair = new AbstractMap.SimpleEntry<>("Passed", true)
def failedTestPair = new AbstractMap.SimpleEntry<>("Failed", false)
def testData = Collections.nCopies(countPassed, passedTestPair) + Collections.nCopies(countFailed, failedTestPair)
when: "Generate the summary format"
def failedTestData = reporter.getFailedTests(testData)
then: "The format must be the same"
failedTestData.every({ it.getKey() == "Failed" && it.getValue() == false})
expectedFailedCount == failedTestData.size()
where: "Possible variants of tests"
countPassed | countFailed || expectedFailedCount
5 | 10 || 10
}
def "Count function returns the count of passed or failed tests."() {
given: "I have a reporter"
@Subject
def reporter = new CLIReporter(null, null)
and: "I have a testData"
def passedTestPair = new AbstractMap.SimpleEntry<>("Passed", true)
def failedTestPair = new AbstractMap.SimpleEntry<>("Failed", false)
def testData = Collections.nCopies(countPassed, passedTestPair) + Collections.nCopies(countFailed, failedTestPair)
when: "Generate the summary format"
def count = reporter.count(testData, isPassed)
then: "The format must be the same"
expectedCount == count
where: "Possible variants of tests"
countPassed | countFailed | isPassed || expectedCount
10 | 20 | true || 10
10 | 20 | false || 20
}
def "Summary format."() {
given: "I have a reporter"
@Subject
def reporter = new CLIReporter(null, null)
when: "Generate the summary format"
def format = reporter.summary(passed, failed)
then: "The format must be the same"
expectedFormat == format
where: "Possible variants of tests"
passed | failed || expectedFormat
10 | 20 || "Ran 30 tests.\nPassed: 10 (33.33%)\nFailed: 20 (66.67%)\n"
10 | 10 || "Ran 20 tests.\nPassed: 10 (50.00%)\nFailed: 10 (50.00%)\n"
}
}
@@ -36,8 +36,8 @@ class SimpleTestReportFormatterSpec extends Specification {
where: "Possible variants of tests"
isPassed || expectedFormat
true || "✓ A simple test\n\tThen Then block\n"
false || " A simple test\n\tThen Then block\n"
true || "✓ A simple test\n\tThen: Then block\n\n"
false || " A simple test\n\tThen: Then block\n\n"
}
def "If the map of methods have GivenWhenThen blocks."() {
@@ -61,8 +61,8 @@ class SimpleTestReportFormatterSpec extends Specification {
where: "Possible variants of tests"
isPassed || expectedFormat
true || "✓ A simple test\n\tGiven Given block\n\tWhen When block\n\tThen Then block\n"
false || " A simple test\n\tGiven Given block\n\tWhen When block\n\tThen Then block\n"
true || "✓ A simple test\n\tGiven: Given block\n\tWhen: When block\n\tThen: Then block\n\n"
false || " A simple test\n\tGiven: Given block\n\tWhen: When block\n\tThen: Then block\n\n"
}
}