Compare commits

...
12 Commits
Author SHA1 Message Date
KKlochko f384ce5fe5 Add SimpleEmptyTest and its tests.
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-10-15 12:37:56 +03:00
KKlochko 5c1e69ed32 Add the error for an empty test.
continuous-integration/drone/tag Build is passing
2023-10-15 12:35:18 +03:00
KKlochko 2e12fdbd34 Update the titles for SimpleFailedThenTest and SimpleGivenWhenThenTest.
continuous-integration/drone/push Build is passing
continuous-integration/drone/tag Build is passing
2023-10-15 12:11:56 +03:00
KKlochko 95a29a14fe Update the tests to test the multi-block test.
continuous-integration/drone/tag Build is passing
2023-10-15 12:10:33 +03:00
KKlochko d69cf31964 Add a multi-block test. 2023-10-15 12:02:24 +03:00
KKlochko 79c2f159e1 Add the multi-block tests support.
continuous-integration/drone/tag Build is passing
2023-10-15 12:02:02 +03:00
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
18 changed files with 385 additions and 62 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);
}
}
@@ -0,0 +1,12 @@
package space.kklochko.simple_jbdd.test_examples.tests;
import space.kklochko.simple_jbdd.annotations.Title;
import space.kklochko.simple_jbdd.tests.Test;
@Title("A Simple Empty Test for Sum")
public class SimpleEmptyTest extends Test {
public void check() {
assert 30 == 10 + 20 + 1;
}
}
@@ -5,7 +5,7 @@ import space.kklochko.simple_jbdd.annotations.Title;
import space.kklochko.simple_jbdd.tests.Test;
@Title("A Simple Failed GivenWhenTest for Sum")
@Title("A Simple Failed Then Test for Sum")
public class SimpleFailedThenTest extends Test {
@Then
public void check() {
@@ -0,0 +1,51 @@
package space.kklochko.simple_jbdd.test_examples.tests;
import space.kklochko.simple_jbdd.annotations.Given;
import space.kklochko.simple_jbdd.annotations.Then;
import space.kklochko.simple_jbdd.annotations.Title;
import space.kklochko.simple_jbdd.annotations.When;
import space.kklochko.simple_jbdd.test_examples.samples.Sum;
import space.kklochko.simple_jbdd.tests.Test;
@Title("A Simple GivenGivenWhenWhenThenThen Test for Sum")
public class SimpleGivenGivenWhenWhenThenThenTest extends Test {
private Sum sum;
private int a;
private int b;
private int c;
private int resultAB;
private int resultCB;
@Given("Create the Sum object")
void setup() {
sum = new Sum();
}
@Given("Setup the arguments")
void setupArgs() {
a = 10;
b = 20;
c = 30;
}
@When("Call the .sum(10, 20)")
void triggerAB() {
resultAB = sum.sum(a, b);
}
@When("Call the .sum(30, 20)")
void triggerCB() {
resultCB = sum.sum(c, b);
}
@Then("10 + 20 is 30")
void checkAB() {
assert 30 == resultAB;
}
@Then("30 + 20 is 50")
void checkCB() {
assert 50 == resultCB;
}
}
@@ -7,7 +7,7 @@ import space.kklochko.simple_jbdd.annotations.When;
import space.kklochko.simple_jbdd.test_examples.samples.Sum;
import space.kklochko.simple_jbdd.tests.Test;
@Title("A Simple GivenWhenTest for Sum")
@Title("A Simple GivenWhenThen Test for Sum")
public class SimpleGivenWhenThenTest extends Test {
private Sum sum;
private int a;
@@ -0,0 +1,20 @@
package space.kklochko.simple_jbdd.tests.commands.decorators;
import space.kklochko.simple_jbdd.tests.Test;
import space.kklochko.simple_jbdd.tests.commands.AbstractTestCommand;
public class ErrorDecorator<T extends Test> extends AbstractDecorator<T> {
public ErrorDecorator(String error_label, AbstractTestCommand<T> command, String type) {
super(error_label, command);
setType(type);
}
public boolean test() {
return false;
}
public boolean runMethod() {
return false;
};
}
@@ -8,40 +8,55 @@ import space.kklochko.simple_jbdd.tests.Test;
import space.kklochko.simple_jbdd.tests.commands.AbstractTestCommand;
import space.kklochko.simple_jbdd.tests.commands.SimpleTestCommand;
import space.kklochko.simple_jbdd.tests.commands.decorators.BlockDecorator;
import space.kklochko.simple_jbdd.tests.commands.decorators.ErrorDecorator;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.TreeMap;
import java.util.*;
public class TestCommandFactory {
public AbstractTestCommand create(Test input) {
Class<?> aClass = input.getClass();
Map<String, Method> test_methods = this.getTestMethods(aClass);
Map<String, ArrayList<Method>> test_methods = this.getTestMethods(aClass);
AbstractTestCommand<Test> aTestCommand = createTest(aClass, input);
if(test_methods.size() == 0)
return new ErrorDecorator("ERROR!!! No blocks!!!", aTestCommand, "Error");
if(test_methods.containsKey("Then")) {
Method then = test_methods.get("Then");
String label = then.getAnnotation(Then.class).value();
aTestCommand = new BlockDecorator<Test>(label, aTestCommand, then, "Then");
ArrayList<Method> thens = getMethodsInReverseOrder(test_methods, "Then");
for(Method then : thens) {
String label = then.getAnnotation(Then.class).value();
aTestCommand = new BlockDecorator<Test>(label, aTestCommand, then, "Then");
}
}
if(test_methods.containsKey("When")) {
Method when = test_methods.get("When");
String label = when.getAnnotation(When.class).value();
aTestCommand = new BlockDecorator<Test>(label, aTestCommand, when, "When");
ArrayList<Method> whens = getMethodsInReverseOrder(test_methods, "When");
for(Method when : whens) {
String label = when.getAnnotation(When.class).value();
aTestCommand = new BlockDecorator<Test>(label, aTestCommand, when, "When");
}
}
if(test_methods.containsKey("Given")) {
Method when = test_methods.get("Given");
String label = when.getAnnotation(Given.class).value();
aTestCommand = new BlockDecorator<Test>(label, aTestCommand, when, "Given");
ArrayList<Method> givens = getMethodsInReverseOrder(test_methods, "Given");
for(Method given : givens) {
String label = given.getAnnotation(Given.class).value();
aTestCommand = new BlockDecorator<Test>(label, aTestCommand, given, "Given");
}
}
return aTestCommand;
}
private ArrayList<Method> getMethodsInReverseOrder(Map<String, ArrayList<Method>> test_methods, String key) {
ArrayList<Method> methods = test_methods.get(key);
Collections.reverse(methods);
return methods;
}
protected AbstractTestCommand<Test> createTest(Class<?> aClass, Test input) {
Title aTitle = aClass.getAnnotation(Title.class);
String title = aClass.getName();
@@ -52,8 +67,8 @@ public class TestCommandFactory {
return new SimpleTestCommand<Test>(title, input);
}
protected <T extends Test> Map<String, Method> getTestMethods(Class<?> aClass) {
Map<String, Method> test_methods = new TreeMap<String, Method>();
protected <T extends Test> Map<String, ArrayList<Method>> getTestMethods(Class<?> aClass) {
Map<String, ArrayList<Method>> test_methods = new TreeMap<>();
for(Method method : aClass.getDeclaredMethods()) {
Annotation[] annotations = method.getAnnotations();
@@ -62,15 +77,21 @@ public class TestCommandFactory {
continue;
if(method.isAnnotationPresent(Given.class))
test_methods.put("Given", method);
addMethodTo(test_methods, "Given", method);
if(method.isAnnotationPresent(When.class))
test_methods.put("When", method);
addMethodTo(test_methods, "When", method);
if(method.isAnnotationPresent(Then.class))
test_methods.put("Then", method);
addMethodTo(test_methods, "Then", method);
}
return test_methods;
}
private void addMethodTo(Map<String, ArrayList<Method>> test_methods, String key, Method method) {
ArrayList<Method> methods = test_methods.getOrDefault(key, new ArrayList<>());
methods.add(method);
test_methods.put(key, methods);
}
}
@@ -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;
}
}
@@ -10,6 +10,10 @@ 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);
}
@@ -20,15 +24,15 @@ public class CLIReporter extends AbstractReporter {
int passed = count(testData, true);
int failed = count(testData, false);
System.out.printf("%s\n", center(" Tests "));
System.out.printf("%s\n\n", center(" Tests "));
printTests(testData);
if(failed != 0) {
System.out.printf("\n%s\n", center(" Errors "));
System.out.printf("%s\n\n", center(" Errors "));
printTests(failedTestData);
}
System.out.printf("\n%s\n", center(" Summary "));
System.out.printf("%s\n", center(" Summary "));
System.out.print(summary(passed, failed));
}
@@ -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;
}
}
@@ -1,5 +1,7 @@
package space.kklochko.simple_jbdd.tests.factories
import space.kklochko.simple_jbdd.test_examples.tests.SimpleEmptyTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleGivenGivenWhenWhenThenThenTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleGivenWhenThenTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleThenTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleThenTestWithoutTitle
@@ -14,19 +16,28 @@ those tests check if factory generate them right.
""")
@Title("Unit tests for TestCommandFactory")
class TestCommandFactorySpec extends Specification {
def "If the map of methods have one test block."() {
def "The command have the same block count."() {
given: "I have a TestCommandFactory object"
@Subject
def factory = new TestCommandFactory();
and: "I also have a SimpleThenTest object"
def simpleThenTest = new SimpleThenTest()
when: "If a test command have blocks"
def map = factory.getTestMethods(testClass.getClass())
when: "If a test have only one block"
def map = factory.getTestMethods(simpleThenTest.getClass())
def count = map.values()
.stream()
.map {it.size()}
.reduce(0) { acc, num -> acc + num }
then: "the map size is 1 method"
1 == map.size()
then: "the command have the expected block count"
expectedBlockCount == count
where:
testClass || expectedBlockCount
new SimpleEmptyTest() || 0
new SimpleThenTest() || 1
new SimpleGivenWhenThenTest() || 3
new SimpleGivenGivenWhenWhenThenThenTest() || 6
}
def "createTest() sets the title to a class name if no Title annotation."() {
@@ -63,34 +74,22 @@ class TestCommandFactorySpec extends Specification {
title == test.getLabels()['Title']
}
def "createTest() build a Test with only Then."() {
def "createTest() build a TestCommand that contains the expected blocks."() {
given: "I have a test factory"
@Subject
def factory = new TestCommandFactory()
and: "I have a test class with Title"
def testClass = new SimpleThenTest()
when: "I build a test"
def test = factory.create(testClass)
then: "The test have title and then method"
['Title', 'Then'].toSet() == test.getLabels().keySet()
}
then: "The test have the expected blocks"
expectedBlocks.toSet() == test.getLabels().keySet()
def "createTest() build a Test with Given-When-Then."() {
given: "I have a test factory"
@Subject
def factory = new TestCommandFactory()
and: "I have a test class with Title and Given-When-Then"
def testClass = new SimpleGivenWhenThenTest()
when: "I build a test"
def test = factory.create(testClass)
then: "The test have title and then method"
['Title', 'Given', 'When', 'Then'].toSet() == test.getLabels().keySet()
where:
testClass || expectedBlocks
new SimpleThenTest() || ['Title', 'Then']
new SimpleGivenWhenThenTest() || ['Title', 'Given', 'When', 'Then']
new SimpleGivenGivenWhenWhenThenThenTest() || ['Title', 'Given', 'When', 'Then']
}
}
@@ -22,7 +22,7 @@ class IntegratedClassLoader extends Specification {
def classes = loader.load()
then: "Checking that the test result is expected"
3 == classes.size()
5 == classes.size()
}
def "The classes must be represent the Test interface."() {
@@ -19,7 +19,7 @@ class IntegratedNameClassLoader extends Specification {
def classNames = loader.load()
then: "Checking that the test result is expected"
3 == classNames.size()
5 == classNames.size()
}
}
@@ -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
}
}
@@ -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"
}
}
@@ -1,6 +1,8 @@
package space.kklochko.simple_jbdd.tests.runners
import space.kklochko.simple_jbdd.test_examples.tests.SimpleEmptyTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleFailedThenTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleGivenGivenWhenWhenThenThenTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleGivenWhenThenTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleThenTest
import space.kklochko.simple_jbdd.test_examples.tests.SimpleThenTestWithoutTitle
@@ -33,10 +35,12 @@ class IntegratedSimpleTestRunnerSpec extends Specification {
result == expectedResult
where: "Possible variants of tests"
testObject || expectedResult
new SimpleThenTest() || true
new SimpleGivenWhenThenTest() || true
new SimpleThenTestWithoutTitle() || true
new SimpleFailedThenTest() || false
testObject || expectedResult
new SimpleThenTest() || true
new SimpleGivenWhenThenTest() || true
new SimpleThenTestWithoutTitle() || true
new SimpleFailedThenTest() || false
new SimpleGivenGivenWhenWhenThenThenTest() || true
new SimpleEmptyTest() || false
}
}