mtedone / podam

PODAM - POjo DAta Mocker
https://mtedone.github.io/podam
MIT License
326 stars 749 forks source link

when use AttributeStrategy or AbstractTypeManufacturer is not meet my expectations #288

Closed tanqiwei closed 3 years ago

tanqiwei commented 4 years ago

Question1 :

for example.

I have a Pojo,The definition is as follows,@Data is lombok annotation:

@Data
public class StrategyPojo implements Serializable {
    @PodamCollection(nbrElements = 2,mapKeyStrategy = I18nMapKeyStrategy.class)
    private Map<String,String> value;
}

my map key strategy definition is:

public class I18nMapKeyStrategy implements AttributeStrategy<String> {

    @Override
    public String getValue(Class<?> attrType, List<Annotation> attrAnnotations) {
        return GenerateUtil.generateBool() ? Locale.SIMPLIFIED_CHINESE.toString() : Locale.US.toString();
    }
}

but I find,when podam factory genenrate this pojo data.I found that the size of the map generated is not always a size of 2, but may be 1.

This behavior does not meet my expectations. I thought that when I specify the number of nbrElements, the generated data must be this number. I am not sure if this can be considered a bug.

Question2:

I defined an assembly structure, and then the structure is composed of some simple types. As follows:

@Data
public class StringI18n implements Serializable {
    private String defaultValue = null;
    private Map<String, String> i18nValue = new HashMap();
}

I have a pojo ,example:

@Data
public class Product implements Serializable {
    @PodamStringI18nValue
    private StringI18n name;
    private List<String> deviceIds;
}

I defined an annotation as follows:

@Documented
@PodamAnnotation
@Target(value = {ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface PodamStringI18nValue {
    String[] locales() default {"zh_CN", "en_US"};
    int stringValueLength() default PodamConstants.STR_DEFAULT_LENGTH;
    boolean generateI18nMap() default true;
    boolean mapKeyGenerateByLocales() default true;
}

I write a manufacturer as follows:

public class StringI18nManufacturerImpl extends AbstractTypeManufacturer<StringI18n> {

    private int stringLength = PodamConstants.STR_DEFAULT_LENGTH;
    private Set<Locale> locales;

    public StringI18nManufacturerImpl() {
        locales = Sets.newHashSet(Locale.SIMPLIFIED_CHINESE, Locale.US);
    }

    public StringI18nManufacturerImpl(Set<Locale> locales) {
        this.locales = locales;
    }

    public StringI18nManufacturerImpl(Set<Locale> locales, int stringLength) {
        this.locales = locales;
        this.stringLength = stringLength;
    }

    public StringI18nManufacturerImpl addLocaleGenerate(Locale locale) {
        if (CollectionUtils.isEmpty(locales)) {
            locales = Sets.newHashSet();
        }
        Optional.ofNullable(locale).filter(
                wantAdd -> !locales.contains(wantAdd)
        ).ifPresent(locales::add);
        return this;
    }

    @Override
    public StringI18n getType(DataProviderStrategy strategy, AttributeMetadata attributeMetadata, Map<String, Type> genericTypesArgumentsMap) {
        PodamStringI18nValue annotationStrategy = findElementOfType(
                attributeMetadata.getAttributeAnnotations(), PodamStringI18nValue.class);
        if (Objects.nonNull(annotationStrategy)) {
            return CommonStringI18nGenerateUtil.generateStringI18n(annotationStrategy, locales);
        }
        return generateI18nByDefault(attributeMetadata);
    }

    private StringI18n generateI18nByDefault(AttributeMetadata attributeMetadata) {
        return CommonStringI18nGenerateUtil.generateStringI18Default(stringLength, locales);
    }

}

I use it for example:

 PodamFactory factory = new PodamFactoryImpl();
factory.getStrategy().addOrReplaceTypeManufacturer(StringI18n.class, new StringI18nManufacturerImpl());
Product product = factory.manufacturePojo(Product.class);

I found that the map in the name field generated by the factory itself has a key length of 2 (in line with my expectations), but in the final generated data, the factory seems to have filled the Map data with additional three that I did not expect. Values.

This does not meet my expectations, and I am not sure whether it is a bug or a problem with my use?

daivanov commented 4 years ago

Hi,

For the question #1, you ask Podam to create a map with 2 elements. So Podam will call I18nMapKeyStrategy.getValue() twice, which in its turn will return 2 random strings from set of 2 strings. Sometimes strings will identical, sometimes not. One way to fix it is to make a counter and return values in loop.

For the question #2, I do not understand what phrase "the final generated data" refers to.

Thanks, Daniil

tanqiwei commented 4 years ago

What I mean is that my custom factory generates the value of the name field of the Product class, and the length of the map field of the i18nValue returned by this value is 2. But in the end, the data generated by the podam factory corresponds to the product variable obtained last in the example. The map length of the i18nValue field corresponding to the value of the name field of this variable is 5 (the default collection length of the podam factory). I found that the value generated by the custom factory still exists, but podam will fill the remaining length to the default specified length by default.

In the practice just now, I combined AttributeStrategy and annotations to achieve the generated data style I expect。But this annotation cannot be annotated by the PodamAnnotation annotation,Otherwise it will not take effect.

The podam tool is really easy to use, thank you for your team to develop this tool.

daivanov commented 4 years ago

As far as I get StringI18n is produced in the type manufacturer by CommonStringI18nGenerateUtil.generateStringI18Default() and CommonStringI18nGenerateUtil.generateStringI18n() so if you say i18nValue has 5 elements, it must be cause of something done in these methods. Since there is no listing for these methods I'm unable to say, why map size is 5.

You are welcome.

Thanks, Daniil

tanqiwei commented 4 years ago

this generate util code is,Logically there are only two:

public class CommonStringI18nGenerateUtil {

    public static StringI18n generateStringI18n(PodamStringI18nValue annotationStrategy, Set<Locale> locales) {
        StringI18n stringI18n = new StringI18n();
        stringI18n.setDefaultValue(generateString(annotationStrategy.stringValueLength()));
        if (annotationStrategy.generateI18nMap()) {
            if (annotationStrategy.mapKeyGenerateByLocales()) {
                List<String> localeString = Arrays.stream(annotationStrategy.locales()).collect(Collectors.toList());
                if (CollectionUtils.isNotEmpty(localeString)) {
                    Arrays.stream(annotationStrategy.locales())
                            .forEach(locale ->
                                    stringI18n.setLocalizedValue(locale, generateString(annotationStrategy.stringValueLength())));
                } else {
                    generateLocaleValueByLocales(locales, stringI18n, annotationStrategy.stringValueLength());
                }
            }
        }
        return stringI18n;
    }

    public static StringI18n generateStringI18Default(int stringLength, Set<Locale> locales) {
        StringI18n stringI18n = new StringI18n();
        stringI18n.setDefaultValue(CommonStringI18nGenerateUtil.generateString(stringLength));
        CommonStringI18nGenerateUtil.generateLocaleValueByLocales(locales, stringI18n, stringLength);
        return stringI18n;
    }

    public static void generateLocaleValueByLocales(Set<Locale> localeSet, StringI18n stringI18n, int stringLength) {
        Optional.ofNullable(localeSet)
                .filter(CollectionUtils::isNotEmpty)
                .map(Collection::stream)
                .ifPresent(
                        localeStream ->
                                localeStream.forEach(
                                        locale ->
                                                stringI18n.setLocalizedValue(locale.toString(),
                                                        generateString(stringLength))
                                )
                );
    }

    public static String generateString(int length) {
        StringBuilder buff = new StringBuilder();
        while (buff.length() < length) {
            buff.append(PodamUtils.getNiceCharacter());
        }
        return buff.toString();
    }
}

StringI18n add a function:

 public void setLocalizedValue(String locale, String localizedValue) {
        this.i18nValue.put(locale, localizedValue);
    }
daivanov commented 3 years ago

Sorry, I lost track of this. From you code it looks like you fill the map based on content of Set<Locale> locales, which comes from the constructor public StringI18nManufacturerImpl(Set<Locale> locales). So if map contains 5 elements, this means that the set also contains five elements.