Closed kirillyu closed 2 years ago
Hello, they sound as very interesting points, thank you for bringing them!
Can you give code examples to further understand your interest in each of them and review the proper solution for each scenario?
Regards
Yeah, sure. I will send it soon
Cloneable. Should allow the base element and its descendants to be cloned
public abstract class BaseTestElement implements DslTestElement, Cloneable {
...
@Override
public BaseTestElement clone() {
try {
// TODO: copy mutable state here, so the clone can't change the internals of the original
return (BaseTestElement) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
Serializable. For API/Converations/Json format visualizing/Storing
public abstract class BaseTestElement implements DslTestElement, Serializable{
...
@Override
public String toString() {
return "BaseTestElement {" +
"prop1=" + territoriesInfo +
", prop2=" + resourcesInfo +
", prop3=" + diplomacyInfo +
'}';
}
No-args constructors for DSL top-level objects. This is the first step towards creating test plan constructors that can be based on anything. It also makes it easier to work with reflection when calling newInstance
public abstract class DslJsr223TestElement extends BaseTestElement {
public DslJsr223TestElement() {
super(name != null ? name : defaultName, TestBeanGUI.class);
this.scriptBuilder = new DslScriptBuilder("");
}
Hello @kirillyu , thank you very much for the samples!
But could you give examples in code on how such features could be used, like client code example using cloinig, another one for serialization and another one for no args constructors, instead of prototypes of potential implementations, to better understand the need? My main interest is in understanding the need, and examples would greatly help :)
Best regards.
Oh, this is a feature that allows you to use dsl in any possible integration. If I give a specific example, it might be too artificial or not look useful/popular/attractive. I don't see how this will help us make a decision. What I mean: for example, there is a custom jmeter testplan builder written in python, which simply comes to the java jmeter DSL via api and requests the sampler's JSON Object from it, or vice versa sends it some kind of object based on which you need to build an object inside dsl. How to reflect such an example with a piece of code? A lot of contexts are also needed there, why this builder is needed, why it is in python, how interaction occurs ... and so on. When we talk about cloning, I can’t come up with a valid and presentable example here either, but for example, there is also a test plan builder who needs to make several from one test plan: a stability test, a maximum search test, a stress test, some other test. But in these tests, for example, you need to change the header / remove post processors / change csvdataset. If we simply multiply the testplan object, nothing will come of it - any change in one will change all the other entities. Therefore, a new clone entity is needed. I also don’t know how to reflect this advantageously with an example of code, and I want to emphasize that all these are only certain options, and you can come up with a lot of them. The most important thing is that now there are no options and you just need to use terrible volumes of reflections
Hello,
The serialization request, I think we need a more clear scenario or need to implement it. Is this python scenario something you are trying to do? What about jmx and using jmx2dsl to get it back? I know is not ideal (due to verbosity mainly, and jmeter low level), but is a serialization mechanism that is already in place, and compatible not only with the DSL but with other tools. Without any concrete example, is hard to identify a proper solution/serialization that covers all cases.
About the scenario you describe for cloning, there is more than just cloning, but also altering already set behavior in elements. In many cases we have built the API to incrementally build/set a test element, and some settings were not designed for being "reverted". If we want to be able to modify any property we would not only need something like clone but also revisit the API design (I am not saying is not a good thing, but is something we have to take into consideration). An alternative to cloning, that doesn't cover either the "modification" design issue, is to make builders immutable (ie: every time you modify something, a new instance is returned and previous one is unaltered). Initially when we designed the API we decided to make builders mutable since it required less code and less burden on GC than immutable alternative, but we know that immutable alternative would avoid potentially undesired side effects and in general are easer to reason and to work with. We might review that option. I think would be an interesting approach, and something worth trying before we release 1.0. As side note/workaround, you can create custom builder methods that encapsulate the basics fo the reusable elements you want, that is mainly the approach we generally encourage right now instead of reusing builder instances.
Maybe we can review, optionally in private, the scenarios you are using reflection and see if we can come up with alternatives to reflection and what options do we have, to see if we need to modify the DSL to support them?
Thank you very much as always to take the time to ask these things, that are really interesting and makes us think on alternatives and revisiting design decisions and options.
After some discussions, some prototyping, and thorough evaluation of pros and cons we have decided to not implement the immutable API for the time being, and provide easier means to extend existing classes (by making fields nonfinal and protected vs private) and adding inspection or modification capabilities to fields which can come in handy for developers of frameworks that want to use the DSL but need to be able to inspect or manipulate test plan after it's initial creation.
The recommended way for creating base/prototyping samplers is creating a properly named method, for example:
public DslHttpSampler baseSampler() {
return httpSampler("https://myService");
}
@Test
public void test() {
TestPlanStats stats = testPlan(
threadGroup(1, 1,
baseSampler(),
baseSampler()
.post("test", ContentType.TEXT_PLAIN)
)
).run();
A shorter alternative can be using lambdas, like this example:
Supplier<DslHttpSampler> baseSampler = () -> httpSampler("https://myService");
TestPlanStats stats = testPlan(
threadGroup(1, 1,
baseSampler.get(),
baseSampler.get()
.post("test", ContentType.TEXT_PLAIN)
)
).run();
Note that this is quite close to what you would get with an immutable API. Eg:
DslHttpSampler baseSampler = httpSampler("https://myService");
TestPlanStats stats = testPlan(
threadGroup(1, 1,
baseSampler,
baseSampler
.post("test", ContentType.TEXT_PLAIN)
)
).run();
And you can even create lambdas with parameterized required fields:
Function<String, DslHttpSampler> baseSampler = url -> httpSampler(url)
.post("test", ContentType.TEXT_PLAIN);
TestPlanStats stats = testPlan(
threadGroup(1, 1,
baseSampler.apply("https://myurl"),
baseSampler.apply("https://myOtherUrl")
)
).run();
Implementing an immutable API currently has the following downsides:
If anyone still thinks any of the points in this issue still makes sense or finds some scenario where the provided solution is not enough, please let us know, and we will re-evaluate this decision for future versions.
I am writing many things over dsl. And I think that it will be cool to provide greater integrability. What can be helpful IMHO:
I want to discuss it, and maybe somebody have more ideas how to make it easier to integrate somewhere