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

Running Quickcheck without it being part of a test. #282

Open
jparsert opened this issue Nov 2, 2020 · 10 comments
Open

Running Quickcheck without it being part of a test. #282

jparsert opened this issue Nov 2, 2020 · 10 comments

Comments

@jparsert
Copy link

jparsert commented Nov 2, 2020

I have a scenario where I have a class file (maybe without the source file) and a method and class name of a method/class that is implemented in this class file. With this I have the signature of the method (class constructor) etc. I would like to run this method (if it is static) with quickcheck or instantiate the class with a constructor and run the method (both with quickcheck).

So to summarize, I want to do what the "@Property public void ..." methods do (as described in the tutorial) programmatically, by doing program analysis on class files. That is obtaining required signatures etc. automatically.

Is this possible? And if so, can junit-quickcheck be used for this task?

The idea I have now is: Using reflect and other standard java to get a java.reflect.Method then look at the argument types and generating random values for each of these types using junit-quickcheck, rinse and repeat.

@pholser
Copy link
Owner

pholser commented Nov 4, 2020

@jparsert Thanks for your interest! Are there characteristics of the classes/methods that hold across many kinds? If so, this is where junit-quickcheck's "sweet spot" is -- a property is a set of assertions about its arguments that should hold true for lots of values (subject to constraints). Perhaps such an assertion might be "this method has a cyclomatic complexity of < 5".

You'd need a generator for java.lang.reflect.Method type, and a way to decide the method's parameter types, and then generate random values for each parameter....you'd also need generators for the type that is the receiver of the method call...sounds like you'll use reflection heavily in this.

I'd be happy to review a work-in-progress solution, if you have one. It'd be interesting to see how good of a fit junit-quickcheck is for the problem you're trying to solve.

@jparsert
Copy link
Author

jparsert commented Nov 5, 2020

Thanks for the reply. I honestly think that what I had in mind is a lot simpler than what you are alluding to. Although what you suggest is quite interesting and I will be looking into that. However, here is something simple I had in mind to convey what I was thinking of:

        java.lang.reflect.Method method = getMethod(classname, methodname); // this is done with simple class loading and reflection
        Class<?>[] params = method.getParameterTypes(); // This provides the types for the parameters
        // Now that we know the types of the parametes we want to generate random values for each parameter using quickcheck
        // In pseudocdoe this would be something like this:
        Object[] args = params.map(type -> getRandomQuickcheckValueOfType(type));  // this is the important part
        method.invoke(null, args); // Invoke the function with the quickcheck generated values

Essentially I was wondering if a function like getRandomQuickcheckValueOfType in line 6 existed. Essentially, one that given a Class<> returns a "random" object of this class, assuming of course that that class is a supported type by junit-quickcheck.

@pholser
Copy link
Owner

pholser commented Nov 6, 2020

@jparsert Ah, I see. You want to be able to generate random instances of a type given the type, but not necessarily in a test or a property method. https://github.com/j-easy/easy-random might be a good bet in the interim...otherwise, see what we come up with to resolve #211 ...?

@pholser
Copy link
Owner

pholser commented Nov 6, 2020

@jparsert Quick question also: suppose you wanted a random value of a type whose generator(s) respect the size or status parameters that junit-quickcheck gives to a generator. How should those be communicated through the API? What about types whose generators can be influenced by annotations...should there be a way of allowing such configurations independent from the junit-quickcheck runner?

@jparsert
Copy link
Author

jparsert commented Nov 9, 2020

Hi. I had a look at easy-random. This was a good hint but the API still forces me to do too much "by hand". I.e. To make it "more generic" I would still have to have a big if-else branch that where I would have to instantiate things like ListRandomizer, IntegerRandomizer etc. for each combination of List/Type etc. that could occur. For "simple" (shallow) objects easy-random seems to work "well enough", but for Containers etc. I think I would still have to write a lot of custom generators.

My immediate suggestion would be to communicate those parameters through the constructor of the generator?

The same holds for generators that can be influenced through annotations. All these could (as an alternative to annotations) also be added by constructor.

In my opinion, a good alternative in case the constructors would become too big, would be to make use of the builder pattern which is fairly common.

@pholser
Copy link
Owner

pholser commented Nov 13, 2020

@jparsert Got it. Would you be willing to cook up a small example that uses all the API you'd like to see? I can do the same, and perhaps we can compare notes.

@jparsert
Copy link
Author

jparsert commented Nov 16, 2020

Not sure how detailed of an example you would expect. This is a very rudimentary thing that I "just" came up with.

public static Object createRandomObject(Type t) {
    QCObjectGenerator objGen = new QCObjectGenerator.Builder()
            .maxInt(20)
            .minInt(-20)
            ...
            .minDouble(0.4)
            .maxDouble(1.3)
            .collectionsMaxLength(10)
            .listMaxLength(15)
            .setMaxSize(200)
            .generator(someType, func)  // setting generatores like with gen()
            .nullable(type1) // 
            .nullable(type2)
            .build()
    Object obj = objGen.getObjectFromType(t);

    return obj;

Also a small side question not necessarily related to this but what is the default behaviour if the type is an abstract type such as List instead of ArrayList. Will it automatically generate an instance of arraylist?

@pholser
Copy link
Owner

pholser commented Nov 16, 2020

@jparsert Thanks for this. Will take into consideration.

To answer your other question: if asked to produce a value of an abstract type, junit-quickcheck chooses on each generation one of the suitable generators at random, with (approximately) equal probability. So if there were an ArrayList generator and a LinkedList generator, you might get an ArrayList; you might get a LinkedList.

@jparsert
Copy link
Author

Are you working on something with regards to this? If so, could you make a branch public so that I can take a look and maybe I can contribute a little some base boilerplate that helps me orient myself is done.

@pholser
Copy link
Owner

pholser commented Nov 20, 2020

@jparsert Currently, no. I think soon I'm going to bite the bullet and declare a 1.0 version; after that, I'll make a feature branch to start exploration of this request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants