Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generate unit tests for new implementations automagically. #67

Merged
merged 3 commits into from
Nov 15, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,18 @@ Adding a new validator implementation is relatively straight forward and very we
1. First, take a look at the [micro-site][micro-site], as it gives some explanation of what is being tested.
2. Clone the repo and pull it down locally, creating your own branch to work in.
3. Add necessary dependencies to [build.gradle.kts](build.gradle.kts).
4. Ensure GitHub's Dependabot will update the new dependency version.
4. Optional, If the previous step involved add a new repository to download the new dependency,
then ensure GitHub's Dependabot will update the new dependency version.
This will ensure the site updates when new versions are released.
See the [Dependabot version updates docs](https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/about-dependabot-version-updates).
5. Add a new implementation of [Implementation](src/main/java/org/creekservice/kafka/test/perf/implementations/Implementation.java)
to the [main implementations](src/main/java/org/creekservice/kafka/test/perf/implementations) package for the new validator library.
See JavaDocs and other implementations for help.
6. Add a unit test class for your new implementation to the [test implementations](src/test/java/org/creekservice/kafka/test/perf/implementations) package.
This should subtype [ImplementationTest.java](src/test/java/org/creekservice/kafka/test/perf/implementations/ImplementationTest.java).
The unit test class needs to content. See other implementations for examples.
Ensure tests pass!
7. Register your new Implementation type in [Implementations.java](src/main/java/org/creekservice/kafka/test/perf/implementations/Implementations.java).
6. Register your new Implementation type in [Implementations.java](src/main/java/org/creekservice/kafka/test/perf/implementations/Implementations.java).
This will ensure the new implementation is included in the docs and included in the functional test
7. Run [ImplementationTest.java](src/test/java/org/creekservice/kafka/test/perf/implementations/ImplementationTest.java).
This unit test will test each implementation, including yours.
Ensure tests pass!
8. Manually add appropriate benchmark methods to [JsonSerdeBenchmark.java](src/main/java/org/creekservice/kafka/test/perf/performance/JsonSerdeBenchmark.java)
and [JsonValidateBenchmark.java](src/main/java/org/creekservice/kafka/test/perf/performance/JsonValidateBenchmark.java).
This is currently manual as JMH library does provide a way to generate these automatically.
Expand Down

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Stream;
import org.creekservice.api.test.util.TestPaths;
import org.creekservice.kafka.test.perf.model.ModelState;
import org.creekservice.kafka.test.perf.model.PolyTypeA;
Expand All @@ -39,9 +40,10 @@
import org.creekservice.kafka.test.perf.testsuite.SchemaSpec;
import org.creekservice.kafka.test.perf.util.TestSchemas;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

public abstract class ImplementationTest {
public class ImplementationTest {

private static final Path REMOTES_ROOT =
TestPaths.moduleRoot("json-schema-validation-comparison")
Expand All @@ -55,14 +57,10 @@ public abstract class ImplementationTest {
List.of("element"),
List.of(new PolyTypeA(UUID.randomUUID()), new PolyTypeB(0.0000000002d)));

private Implementation impl;
private TestData testData;
private AdditionalSchemas additionalSchemas;

@BeforeEach
void setUp() throws Exception {
this.impl = instantiateSerde();
this.testData = testData(impl);
void setUp() {
this.additionalSchemas =
new AdditionalSchemas(
Map.of(
Expand All @@ -75,25 +73,34 @@ void setUp() throws Exception {
REMOTES_ROOT);
}

@Test
void shouldReturnValueMetaData() {
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldReturnValueMetaData(final String shortName, final Implementation impl) {
assertThat(impl.metadata(), is(notNullValue()));
}

@Test
void shouldNotThrowPreparingValidSchema() {
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldNotThrowPreparingValidSchema(final String shortName, final Implementation impl) {
// Given:
final TestData testData = testData(impl);

// When:
impl.prepare(testData.schema, testData.spec, additionalSchemas);

// Then: did not throw.
}

@SuppressFBWarnings("RV_EXCEPTION_NOT_THROWN")
@Test
void shouldThrowValidatingInvalidJson() {
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldThrowValidatingInvalidJson(final String shortName, final Implementation impl) {
assumeFalse(
getClass().getName().contains("Jackson")
|| getClass().getName().contains("Confluent"),
shortName.equals("Jackson") || shortName.equals("Confluent"),
"Exclude impls that don't support this");

// Given:
final TestData testData = testData(impl);
final Implementation.JsonValidator validator =
impl.prepare(testData.schema, testData.spec, additionalSchemas);
final String badJson =
Expand All @@ -103,14 +110,15 @@ void shouldThrowValidatingInvalidJson() {
assertThrows(RuntimeException.class, () -> validator.validate(badJson));
}

@Test
void shouldNotThrowValidatingValidJson() {
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldNotThrowValidatingValidJson(final String shortName, final Implementation impl) {
assumeFalse(
getClass().getName().contains("Jackson")
|| getClass().getName().contains("Confluent"),
shortName.equals("Jackson") || shortName.equals("Confluent"),
"Exclude impls that don't support this");

// Given:
final TestData testData = testData(impl);
final Implementation.JsonValidator validator =
impl.prepare(testData.schema, testData.spec, additionalSchemas);
final String goodJson =
Expand All @@ -124,16 +132,18 @@ void shouldNotThrowValidatingValidJson() {
}

@SuppressFBWarnings("RV_EXCEPTION_NOT_THROWN")
@Test
void shouldHandleRemoteSchemas() {
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldHandleRemoteSchemas(final String shortName, final Implementation impl) {
assumeFalse(
getClass().getName().contains("Jackson")
|| getClass().getName().contains("Confluent")
|| getClass().getName().contains("Skema")
|| getClass().getName().contains("Vert"),
shortName.equals("Jackson")
|| shortName.equals("Confluent")
|| shortName.equals("Skema")
|| shortName.equals("Vertx"),
"Exclude impls that don't support this");

// Given:
final TestData testData = testData(impl);
final Implementation.JsonValidator validator =
impl.prepare(testData.remoteSchema, testData.spec, additionalSchemas);

Expand All @@ -142,9 +152,11 @@ void shouldHandleRemoteSchemas() {
assertThrows(RuntimeException.class, () -> validator.validate("abc"));
}

@Test
void shouldRoundTrip() {
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldRoundTrip(final String shortName, final Implementation impl) {
// Given:
final TestData testData = testData(impl);
final Implementation.JsonValidator validator =
impl.prepare(testData.schema, testData.spec, additionalSchemas);

Expand All @@ -157,21 +169,25 @@ void shouldRoundTrip() {
}

@SuppressFBWarnings("RV_EXCEPTION_NOT_THROWN")
@Test
void shouldValidateOnSerialize() {
assumeFalse(getClass().getName().contains("Jackson"), "Exclude the raw Jackson serde");
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldValidateOnSerialize(final String shortName, final Implementation impl) {
assumeFalse(shortName.equals("Jackson"), "Exclude the raw Jackson serde");

// Given:
final TestData testData = testData(impl);
final Implementation.JsonValidator validator =
impl.prepare(testData.schema, testData.spec, additionalSchemas);

// Then:
assertThrows(RuntimeException.class, () -> validator.serialize(BAD_DECIMAL, true));
}

@Test
void shouldNotValidateOnSerialize() {
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldNotValidateOnSerialize(final String shortName, final Implementation impl) {
// Given:
final TestData testData = testData(impl);
final Implementation.JsonValidator validator =
impl.prepare(testData.schema, testData.spec, additionalSchemas);

Expand All @@ -182,11 +198,13 @@ void shouldNotValidateOnSerialize() {
}

@SuppressFBWarnings("RV_EXCEPTION_NOT_THROWN")
@Test
void shouldValidateOnDeserialize() {
assumeFalse(getClass().getName().contains("Jackson"), "Exclude the raw Jackson serde");
@ParameterizedTest(name = "{0}")
@MethodSource("implementations")
void shouldValidateOnDeserialize(final String shortName, final Implementation impl) {
assumeFalse(shortName.equals("Jackson"), "Exclude the raw Jackson serde");

// Given:
final TestData testData = testData(impl);
final Implementation.JsonValidator validator =
impl.prepare(testData.schema, testData.spec, additionalSchemas);
final byte[] serialized = validator.serialize(BAD_DECIMAL, false);
Expand All @@ -195,14 +213,9 @@ void shouldValidateOnDeserialize() {
assertThrows(RuntimeException.class, () -> validator.deserialize(serialized));
}

private Implementation instantiateSerde() throws Exception {
final String testName = getClass().getName();
final String serdeName = testName.substring(0, testName.length() - "Test".length());
final Class<?> serdeType = getClass().getClassLoader().loadClass(serdeName);
if (!Implementation.class.isAssignableFrom(serdeType)) {
throw new AssertionError("Not a serde type: " + serdeType);
}
return (Implementation) serdeType.getDeclaredConstructor().newInstance();
private static Stream<Object[]> implementations() {
return Implementations.all().stream()
.map(impl -> new Object[] {impl.metadata().shortName(), impl});
}

private TestData testData(final Implementation impl) {
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading