如何为数据类/ POJO(在Kotlin/Java中)生成默认值?

6qfn3psc  于 2023-09-29  发布在  Java
关注(0)|答案(7)|浏览(123)

备注:我在Kotlin中提出了这个问题,但它也适用于Java。
例如,我有一个POJO / data类:

data class Example(
    val someInt: Int,
    val someString: String,
    val someObject: Any,
    ... say it has 10 fields ...
)

我需要将它传递给测试中的一些代码,而这些代码只关心someString字段。因此,我希望我不需要关心那些其他字段,而只是用垃圾(可能只是像0和“”这样的东西)填充它们。另外,我不想为构造函数使用默认值,因为对于正常的业务代码,该字段应该是必需的,并且只有在测试代码中,我才想填充虚拟值。
因此,我想知道如何为数据类/ POJO(在Kotlin/Java中)生成默认值?
我天真的想法:使用反射并调用构造函数。但我希望已经有一个库,或者有一个更简单的解决方案。谢谢你,谢谢

编辑

关于为什么我需要这个:在以前使用Java POJO时,我可以通过将Example类设置为如下形式来解决这个问题:

class Example {
  int someInt;
  String someString;
  ...
}

然后我就像

new Example()
  .setSomeInt(42)
  .setSomeString("hello")
  // I do not care about all other fields in this specific test case, so I just leave them null

但在Kotlin中,惯用的方法是将data class定义为这个问题开头的代码示例。然后,我们看到(1)字段是final的(2)不可为空的。那么旧的方法就行不通了。

sdnqo3pr

sdnqo3pr1#

您只需要在测试的设置代码中使用一些默认值来初始化数据对象。人们想知道为什么他们的价值观不重要。也许这本身就表明代码设计得不好。
如果某些字段确实对测试无关紧要,只需传递null或0或“”(任何简单的默认值)。
为了避免重复,您可以为此设置一个方法,并在整个测试过程中使用。

20jt8wwn

20jt8wwn2#

在单元测试中为类生成数据的一个好方法是使用kotlinfixture库。
它可以为模型中的所有字段生成随机数据,包括所有子类。kotlinFixture如何解决这个问题:

val fixture = kotlinFixture()
val example = fixture<Example> {
    property(Example::someString) { "mySpecificValue" }
}

这里example将是一个填充了随机数据的Example对象,someString将被设置为mySpecificValue

额外

使用生成的数据时的一个重要注意事项:您需要确保代码即使在数据更改时也能保持相同的行为!Kotlinfixture默认生成不同的随机值。要获得一些控制,您可能需要设置空性策略和可选策略,因为null和可选参数经常更改代码流。您可以通过设置种子使生成的数据完全确定。
如果你使用的是Java,我发现Instancio是一个替代品。

ebdffaop

ebdffaop3#

您可以给予Instancio(免责声明:我是这个图书馆的作者)。
默认情况下,它使用非空值、非空字符串和正数填充所有字段。

data class Example(
    val someInt: Int,
    val someString: String,
    val someObject: Any,
)

val result = Instancio.create(Example::class.java)

// Sample output
// Example(someInt=9737, someString=QXUIJKX, someObject=java.lang.Object@b3ca52e)

也可以覆盖生成的值。查看user guide以了解详细信息。

bkhjykvo

bkhjykvo4#

我不确定我是否正确地理解了这个问题,但也许这有帮助。
我会创建一个类

public class YourClass{
int something = 0;
}

然后没有arg构造函数-这应该构造一个YourClass对象,其中有= 0。
然后构造函数with(int something)-这应该可以处理需要覆盖默认值的情况。

zvms9eto

zvms9eto5#

当你提到你自己的时候,反思是一个解决方案。这里有一个快速组合的解决方案,可以给予您大致了解:

data class Example(
  val someInt: Int,
  val someString: String,
  val someObject: Any,
  val someList: MutableList<Any>
)

fun initializeDataClass(cls: Class<*>): Any {
  val constructor = cls.constructors[0]
  val arguments = mutableListOf<Any>()
  for (type in constructor.parameterTypes) {
    arguments.add(
      when (type) {
        Boolean::class.java      -> false
        Char::class.java         -> ' '
        Byte::class.java         -> 0
        Short::class.java        -> 0
        Int::class.java          -> 0
        Long::class.java         -> 0
        Float::class.java        -> 0.0
        Double::class.java       -> 0.0
        String::class.java       -> ""
        Array::class.java        -> arrayOf<Any>()
        BooleanArray::class.java -> arrayOf<Boolean>()
        CharArray::class.java    -> arrayOf<Char>()
        ByteArray::class.java    -> arrayOf<Byte>()
        ShortArray::class.java   -> arrayOf<Short>()
        IntArray::class.java     -> arrayOf<Int>()
        LongArray::class.java    -> arrayOf<Long>()
        FloatArray::class.java   -> arrayOf<Float>()
        DoubleArray::class.java  -> arrayOf<Double>()
        List::class.java         -> listOf<Any>()
        Map::class.java          -> mapOf<Any, Any>()
        Set::class.java          -> setOf<Any>()
        else                     -> Any()
      }
    )
  }
  return constructor.newInstance(*arguments.toTypedArray())
}

val example = (initializeDataClass(Example::class.java) as Example)
  .copy(someString = "abc", someList = mutableListOf(1, 2))

最后一行包含三个步骤:

  • 创建示例(使用initializeDataClass)
  • 将其转换为示例
  • 创建一个副本,并将所需字段设置为对测试有影响的值
mftmpeh8

mftmpeh86#

我基于反思的天真解决方案:
使用方法:

fake<Book>().copy(name="hello")

验证码:

package com.cjy.yplusplus.util

inline fun <reified T : Any> fake() = Fakes.fakeImpl(T::class.java) as T

object Fakes {
    fun <T : Any> fakeImpl(clazz: Class<T>) = createFakeObject(clazz)

    private val INFOS = listOf(
        Info(listOf(Int::class.java, java.lang.Integer::class.java)) { 0 },
        Info(listOf(Long::class.java, java.lang.Long::class.java)) { 0L },
        Info(listOf(Double::class.java, java.lang.Double::class.java)) { 0.0 },
        Info(listOf(Float::class.java, java.lang.Float::class.java)) { 0.0f },
        Info(listOf(String::class.java, java.lang.String::class.java)) { "" },
        Info(listOf(List::class.java, MutableList::class.java, java.util.List::class.java)) { ArrayList<Any?>() },
        Info(listOf(Map::class.java, MutableMap::class.java, java.util.Map::class.java)) { HashMap<Any?, Any?>() },
    )

    private fun createFakeObject(type: Class<*>): Any {
        println("createFakeObject type=$type")

        // special check, notice it is "==" not "isAssignableFrom"
        if (type == Any::class.java || type == Object::class.java) {
            return Object()
        }

        return INFOS
            .firstOrNull { info -> info.classes.any { infoClazz -> infoClazz.isAssignableFrom(type) } }
            ?.defaultValue?.invoke()
            ?: createFakeObjectByConstructor(type)
    }

    private fun createFakeObjectByConstructor(clazz: Class<*>): Any {
        println("createFakeObjectByConstructor clazz=$clazz")
        val ctor = clazz.constructors.first()
        val args = ctor.parameterTypes.map { createFakeObject(it) }.toTypedArray()
        println("createFakeObjectByConstructor ctor=$ctor args=$args")
        return ctor.newInstance(*args)
    }

    private data class Info(
        val classes: List<Class<*>>,
        val defaultValue: () -> Any,
    )
}
hujrc8aj

hujrc8aj7#

如果你使用IntelliJ IDEA,你可以定义live template

现在,在Kotlin变量创建(实际上是任何表达式创建)的上下文中,您可以智能完成“example”缩写:

不幸的是,它不能被限制在Tests Files的范围内(这个活动模板将为所有Kotlin代码显示)。

相关问题