hryuk / jtheories Goto Github PK
View Code? Open in Web Editor NEWProperty based testing library for java
License: GNU Lesser General Public License v3.0
Property based testing library for java
License: GNU Lesser General Public License v3.0
Currently two generators can be declared such as:
@Generator
public interface AGenerator {
default A generate(B b) {
return new A(b);
}
}
@Generator
public interface BGenerator {
default B generate(A a) {
return new B(a);
}
}
Creation of such generators will, at best, result in a StackOverflowException
.
Precautions should be taken to prevent this situations at compile time and force users to modify such paradoxical declarations.
@RepeatedTest(200)
void productsHavePriceAndName(final List<Product> products) {
products.forEach(
product -> {
Assertions.assertTrue(product.getPrice() > 0);
}
);
}
Error examples:
[ERROR] com.jtheories.examples.ProductTest.productsHavePriceAndName(List)[195] Time elapsed: 0.001 s <<< ERROR!
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [java.util.List<com.jtheories.examples.Product> arg0] in method [void com.jtheories.examples.ProductTest.productsHavePriceAndName(java.util.List<com.jtheories.examples.Product>)]: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: com.jtheories.core.generator.exceptions.GenerationRuntimeException: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: java.lang.reflect.InvocationTargetException
Caused by: java.lang.ClassCastException: class com.jtheories.examples.Product cannot be cast to class java.lang.Comparable (com.jtheories.examples.Product is in unnamed module of loader 'app'; java.lang.Comparable is in module java.base of loader 'bootstrap')
[ERROR] com.jtheories.examples.ProductTest.productsHavePriceAndName(List)[173] Time elapsed: 0 s <<< ERROR!
org.junit.jupiter.api.extension.ParameterResolutionException: Failed to resolve parameter [java.util.List<com.jtheories.examples.Product> arg0] in method [void com.jtheories.examples.ProductTest.productsHavePriceAndName(java.util.List<com.jtheories.examples.Product>)]: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: com.jtheories.core.generator.exceptions.GenerationRuntimeException: Method generate() on class com.jtheories.generators.collections.GenericListGenerator threw an exception
Caused by: java.lang.reflect.InvocationTargetException
Caused by: java.lang.IllegalStateException: Queue full
@Test
void testParameterized(Collection<Product> products) {
products.forEach(product -> {
Assertions.assertEquals(product.getId(), UUID.fromString(product.getId().toString()));
Assertions.assertTrue(product.getPrice() > 0);
Assertions.assertNotEquals(product.getPrice() % 10, 0);
});
}
@Test
void someTest(@Price(42) Product product) {
Assertions.assertEquals(42L, product.getPrice());
}
Create a runner through JUnit 5 extension API that runs every test annotated with @Property
multiple times with random generared data that would allow eventually to support shrinking.
Currently only one parameter is working with generics. Change the current implementation to take into account all parameters defined by the generator.
@Generator
public interface OrderGenerator {
default Order generate(List<Product> items) {
return new Order(items);
}
}
@Test
void productsHavePriceAndName(@Empty Collection<Product> products) {
...
}
Currently, when a generator needs to produce generics, implementation details of the library need to be exposed to the user, who needs to receive a Class<T> type
parameter (plus constraining annotations) to be able to manually call Generators.gen
.
Instead of having the user receive Class<T> class
and Class<?> annotations
in user-defined generate()
methods add support for using type parameters directly:
At present time, our generator implementation for generic classes look something like this:
@Generated("com.jtheories.core.generator.processor.GeneratorProcessor")
public final class GenericThingGenerator<T> implements ThingGenerator, Generator<Thing> {
public GenericThing generateConstrained(Class type, Class... annotations) {
return GenericThing.super.generate(type, annotations);
}
}
Internally, they are called with the type parameter of the generated class. They go on to pass that class to the user's default implementation. The user then uses that received type to manually call Generators
or another specific generator like this:
default List<T> generate(Class<T> type, Class<T>... constrains) {
var elementGenerator = Generators.gen(type, constrains);
...
}
This is a clear schism in the usual API we are usually trying to provide for our users, in which they just ask for the parameters they need to build their target class, such as this:
default Short generate(SourceOfRandom random) {...}
or this:
default Product generate(UUID id, String name, @Positive @NotMultipleOf10 Long price) {...}
To solve this problem and hide specific parameterizing types from the user, it'd be better if we did the following instead:
In the user-defined generator the user declares parameters as usual. If they don't know the specific type of a parameter, they introduce a variable-type parameter in their generator function:
default <A, B> MyParameterizedType<A, B> generate(A a, B b, String string, Long aLong) {
return MyParameterizedType.builder()
.setA(a)
.setB(b)
.setString(string)
.setLong(aLong)
.build();
}
Internally, when a test or otherwise needs to generate a value of MyParameterizedType<SpecificX, SpecificY>
, the type parameters will be resolved and the implementation will be called. The implementation receives those type parameters as input, resolves them to specific values, just the same as any other type, and then calls the user code:
@Generated("com.jtheories.core.generator.processor.GeneratorProcessor")
public final class MyParameterizedType<A, B> implements MyParameterizedGenerator, Generator<MyParameterizedType<A, B>> {
public <A, B> MyParameterizedType<A, B> generateConstrained(
Class<A> typeA, Class[] annotationsA,
Class<B> typeB, Class[] annotationsB,
Class[] annotationsString, Class[] annotationsLong) {
var random_A = Generators.gen(typeA, annotationsA);
var random_B = Generators.gen(typeB, annotationsB);
var random_string = Generators.gen(String.class, annotationsString);
var random_long = Generators.gen(Long.class, annotationsLong);
return MyParameterizedType.super.generate(random_A, random_B, random_string, random_long);
}
}
In this way, generics are handled just like any other type.
Generator instances are created when the generator is called instead of being created at startup a single time. This, besides performance decay, creates another an issue with SourceOfRandomGenerator, because a new SourceOfRandom with a different seed is created for every other generator, making the tests not reproductibles.
Tests runs are too slow, probably becasue generators are created recursively for every test instead of being created once and being saved to a list of already created generators.
@generated annotation supports an optional argument for date. It'd be good to add the value of the generation date to all generated classes.
Currently a user can annotate generator method values to constrain the generated value, for example:
default Product generate(UUID id, String name, @Positive @NotMultipleOf10 Long price) {
return new Product(id, name, price);
}
But a user can not annotate test parameters to constrain the generaed value:
void doSomethingTest(/* DOESN'T WORK*/ @Free Product product) {
Assertions.assertEquals(product.getId(), UUID.fromString(product.getId().toString()));
Assertions.assertTrue(product.getPrice() == 0);
}
Currently the library supports the generation of an arbitrary number of parameters via the same mechanism that it uses to support generics: the injection of a variable of type Class<T>
that allows the user to manually call the Generators
class.
This approach, although effective, is limited to only one arbitrary-amount parameter.
To solve this we could implement a new kind of parameter that allows for arbitrary generation of values, the Supplier.
Suppliers were added in Java 8. They are the functional-interface equivalent of a factory pattern. Since generators are essentially object factories, it is easy to think that suppliers make a great match for wrapping entire generators as parameters.
The proposed approach would look like this on the user side:
default MyStringContainer generate(Supplier<String> stringSupplier) {
// User can call stringSupplier::get as many times as they want to obtain any number of values.
}
The implementation would do something like this this for every needed Supplier<> parameter.
public MyStringContainer generateConstrained() {
var random_string_supplier = () -> Generators.gen(String.class);
return ListGenerator.super.generate(random_string_supplier);
}
If we combine this with #26 we could also easily provide support for an arbitrary number of generic parameters.
public MyStringContainer generateConstrained(Class<T> type_t, Class<?>... annotationsT) {
var random_string_supplier = () -> Generators.gen(String.class, annotations);
return ListGenerator.super.generate(random_string_supplier);
}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.