junit 使用提供的字段的笛卡尔积创建对象

ia2d9nvy  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(106)

假设我有一个这样的类:

class User{

   String name;
   String email;
   int age;
   Role[] role;
   // other fields.

   // constructors, getters , setters , ...

}

字符串
其中“Role”是另一个定义的类(或枚举,无所谓)。
现在假设我有一个名字列表,一个年龄列表,一个角色列表(或者一个角色数组列表),...(其他可能的字段),我想创建这些字段的所有可能组合来创建所有用户组合(字段的笛卡尔乘积)。

List<User> factory(Map<String , List> arguments){
   List<User> result = new ArrayList<>();

   // add all possible combination of created Users using "arguments" paramether.

   return result;
}


问题是提供的参数的数量是不确定的,例如,在我的应用程序的一部分,我不想提供电子邮件列表,所以所有创建的用户都有“空”电子邮件字段。
我怎样才能有效地获得适当的结果?(有图书馆之类的吗?)如果有必要,可以随意更改“工厂”方法的输入参数或输出。
P.s:我们正在开发一个Sping Boot Web应用程序,我们用Junit和Mockito测试了我们的方法。我们想要用大量的数据测试不同层的方法,但是我们不想用重复的代码来生成样本。我们需要一个类似于“工厂”的方法来生成一个类的所有可能的对象。

h22fl7wq

h22fl7wq1#

一般来说,形成你所描述的那种笛卡尔积的正常方法是使用一组嵌套循环或类似的浅递归。
问题是提供的参数的数量是不确定的,例如,在我的应用程序的一部分,我不想提供电子邮件列表,所以所有创建的用户都有“空”电子邮件字段。
我怎样才能有效地获得适当的结果?(有图书馆之类的吗?)如果有必要,可以随意更改“工厂”方法的输入参数或输出。
您的选择多少取决于User的细节。例如,它有什么构造函数,或者你愿意给予它什么构造函数?示例初始化后,其(所有)字段是否可修改?
一种灵活的方法,适用于几乎任何细节,将涉及一个有状态的工厂类,类似于这样:

public class UserFactory {
   // Fields corresponding to those of User
   String name;
   String email;
   int age;
   Role[] role;
   // ...

   // constructor(s), maybe ...

   // setters ...

   /**
    * Instantiate and return a User with properties as presently configured on
    * this factory
    */
   public User createUser() {
       // ...
   }

   // ... see also below ...
}

字符串
这样的工厂可以帮助您以灵活的方式为User构建一组属性,并抽象出实际创建具有所需属性的User示例的细节。
但是,如果您打算通过Map或类似的方式指定属性值列表,那么您需要一种方法来使用该Map的键来设置工厂(或User)属性。对于我们的目的,将其放在工厂类中是很方便的,并且这还有保持User更干净的额外好处:

// in class UserFactory:

   /**
    * Sets the property of this factory specified by the given name to the
    * given value, which must be of an appropriate type.
    *
    * @throws IllegalArgumentException if the specified property name is not
    *         among the allowed property names
    *
    * @throws ClassCastException if the type of the specified value is not
    *         appropriate for the specified property
    */
   void setPropertyByName(String name, Object value) {
       // ...
   }


有各种各样的方法可以实现它,但重要的是要认识到,使用该签名,它必须依赖于值对象的类型转换。有一些方法可以从这样的东西中提取类型安全,但是它们会增加更多的复杂性,而对于测试支持实用程序来说,这已经非常复杂了。我觉得不值得。
有了这些,工厂类也是一个合理的地方,可以挂起一个生成你想要的User列表的方法,可能是这样的:

// in class UserFactory:

    /**
     * Forms a Cartesian product of the property values given by the value lists
     * in the specified map.  Uses each of the resulting property sets together
     * with any other properties already set on the specified factory to create
     * a User, and returns a List of the resulting users.
     *
     * @param properties a Map from property names to lists of objects suitable
     *     for use as values of the designated property
     *
     * @param factory a UserFactory to use for User creation.  Any User
     *     properties whose values are not specified via the provided
     *     property map will take values as configured on this factory
     *
     * @return an unmodifiable List of User instances having combinations of
     *     property values drawn from the specified Map
     */
    static List<User> createUsers(Map<String, List<?>> properties,
            UserFactory factory) {
        // Extract the first property list, if any, ignoring any empty ones
        Optional<Map.Entry<String, List<?>>> firstEntry =
                properties.entrySet().stream()
                    .filter((x) -> !x.getValue().isEmpty())
                    .findFirst();

        if (firstEntry.isEmpty()) {
            // There are no (more) non-empty property lists.
            // Use the properties configured on the factory to create a single
            // User.
            return List.of(factory.createUser());
        } else {
            // Extract the selected property name, and form a Map containing the
            // properties other than the selected one.
            Map<String, List<?>> submap = new HashMap<>(properties);
            String propertyName = firstEntry.get().getKey();

            submap.remove(propertyName);

            // Set the selected property in the factory to each specified value
            // in turn, and generate User objects that can then be formed by the
            // factory with combinations of the remaining properties.
            return firstEntry.get().getValue().stream()
                    .flatMap((x) -> {
                            factory.setPropertyByName(propertyName, x);
                            return createUsers(submap, factory).stream();
                        })
                    .toList();
        }
    }


它使用递归(每个属性名一个级别)并结合属性值列表上的迭代,最终为每个属性值组合生成一个User。如果在传递给顶层调用的Map中没有列出任何属性,则在结果User s上设置的这些属性的值将由提供的UserFactory示例确定。
但请注意:

  • 如果属性列表包含重复的值,则会得到重复的User。可能很多人都是。如果你想避免这种可能性,那么你可以考虑修改它,使用Set s而不是List s作为属性,但这只对某些类型的属性值有帮助(特别是,not 用于数组)。
  • 因为你至少有一个属性的值是数组,所以上面的代码希望你在相应的属性值列表中提供所有想要的 * 数组 *--而不是它们的单个元素。也有多种方法可以用来生成元素组合的数组,我将其作为练习。
  • 如果任何User属性采用可变类型的值(例如,数组,可能还有Role s或类似的),则需要复制这些值,可能在UserFactory.createUser()中,否则接受User对象将具有共享的可变状态。后者很容易给你带来不愉快的惊喜。

相关问题