我们正在寻找一种可行的设计模式来构建Gen
可以产生正面和负面测试场景的 Scalacheck(生成器)。这将允许我们运行测试来验证功能(正面案例),并通过在所有无效数据组合上forAll
失败来验证我们的案例类验证是否正常工作。
制作一个简单的、参数化Gen
的一次性完成此操作非常容易。例如:
def idGen(valid: Boolean = true): Gen[String] = Gen.oneOf(ID.values.toList).map(s => if (valid) s else Gen.oneOf(simpleRandomCode(4), "").sample.get)
通过以上,我可以得到一个有效或无效的 ID 用于测试目的。有效的,我用来确保业务逻辑成功。无效的,我用来确保我们的验证逻辑拒绝案例类。
好的,所以 - 问题是,在很大程度上,这变得非常笨拙。假设我有一个包含 100 个不同元素的数据容器。生成一个“好”的很容易。但是现在,我想生成一个“坏”的,此外:
我想为每个数据元素生成一个错误的数据元素,其中单个数据元素是错误的(因此至少有 100 个错误实例,测试每个无效参数是否被验证逻辑捕获)。
我希望能够覆盖特定元素,例如输入错误的 ID 或错误的“foobar”。不管那是什么。
我们可以寻找灵感的一种模式是apply
and copy
,它允许我们在指定覆盖值的同时轻松组合新对象。例如:
val f = Foo("a", "b") // f: Foo = Foo(a,b)
val t = Foo.unapply(f) // t: Option[(String, String)] = Some((a,b))
Foo(t.get._1, "c") // res0: Foo = Foo(a,c)
上面我们看到了从另一个对象的模板创建变异对象的基本思想。这在 Scala 中更容易表达为:
val f = someFoo copy(b = "c")
以此为灵感,我们可以思考我们的目标。有几点需要考虑:
首先,我们可以为数据元素和生成的值定义一个映射或键/值容器。这可以用来代替元组来支持命名值突变。
给定一个键/值对容器,我们可以轻松地随机选择一个(或多个)对并更改一个值。这支持生成一个数据集的目标,其中一个值被更改以产生故障。
给定这样一个容器,我们可以轻松地从无效的值集合中创建一个新对象(使用其中一种
apply()
或其他技术)。或者,也许我们可以开发一种使用元组然后只使用
apply()
它的模式,有点像copy
方法,只要我们仍然可以随机更改一个或多个值。
我们或许可以探索开发一种可重复使用的模式,它可以执行以下操作:
def thingGen(invalidValueCount: Int): Gen[Thing] = ???
def someTest = forAll(thingGen) { v => invalidV = v.invalidate(1); validate(invalidV) must beFalse }
在上面的代码中,我们有一个thingGen
返回 (valid)的生成器Things
。然后对于返回的所有实例,我们调用一个通用方法invalidate(count: Int)
,该方法将随机使count
值无效,返回一个无效对象。然后我们可以使用它来确定我们的验证逻辑是否正常工作。
这将需要定义一个invalidate()
函数,给定一个参数(按名称或按位置),然后可以将识别的参数替换为已知错误的值。这意味着对特定值有一个“反生成器”,例如,如果一个 ID 必须是 3 个字符,那么它知道创建一个长度不超过 3 个字符的字符串。
当然,要使已知的单个参数无效(将不良数据注入测试条件),我们可以简单地使用复制方法:
def thingGen(invalidValueCount: Int): Gen[Thing] = ???
def someTest = forAll(thingGen) { v => v2 = v copy(id = "xxx"); validate(v2) must beFalse }
这是我迄今为止的想法的总和。我在吠叫错误的树吗?是否有处理这种测试的好模式?关于如何最好地解决这个测试我们的验证逻辑的问题的任何评论或建议?