我应该编写直接将输入映射到输出的测试,还是动态生成结果?

发布于 2024-10-12 15:56:02 字数 430 浏览 3 评论 0原文

在我看来,有两种编写测试用例的方法:

  1. 将固定输入映射到固定输出并断言它们匹配
  2. 将生成或固定输入映射到动态输出并断言它们匹配

# Static testing:
assert_equal "test".upcase, "TEST"

# Dynamic testing:
assert_equal person.full_name, "#{person.title} #{person.first_name} #{person.last_name}"

当然每种方法都有优点和缺点。复制实现细节感觉不对,但允许我生成示例数据并对其运行测试。硬编码值使正确的输出非常明确,但似乎并不适合重用代码。

前者是编写测试的常规方法吗?你会混合搭配方法吗?是否出于我没有想到的充分理由而避免使用后一种方法?

It seems to me that there are two ways to write test cases:

  1. Map fixed inputs to fixed outputs and assert that they match
  2. Map generated or fixed inputs to dynamic outputs and assert that they match

# Static testing:
assert_equal "test".upcase, "TEST"

# Dynamic testing:
assert_equal person.full_name, "#{person.title} #{person.first_name} #{person.last_name}"

Of course there are pros and cons to each approach. Duplicating implementation details feels wrong, but allows me to generate sample data and run tests on it. Hard coding values makes the correct output very explicit, but doesn't seem to lend it self to reusing code.

Is the former the conventional way to write tests? Do you mix and match approaches? Is the latter method avoided for a good reason that I haven't thought of?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

伤感在游骋 2024-10-19 15:56:02

第二种方法,使用 checkquickcheck,但是...

复制实施细节
感觉不对

如果你重复,那么你就做错了。在代码中,您可以精确地编写任务是什么以及如何执行任务。在测试中,您给出了一些关于结果的不变量。

编辑

关于已知答案测试 (KAT) 的说明

与大多数概括一样,在某些情况下这并不适用。 KAT 相对于随机测试向量占主导地位的一大领域是密码学(例如分组密码),因为除了大多数类型系统强制执行的内容(例如:块大小)之外,不应该有许多可见的不变量。要检查的一个属性是 decrypt(key,encrypt(key,msg)) == msg

简单的几何有一个稍微不同的问题,因为没有一组不变量确实是一个好的检查 - 你可以说 0 0 0 0 0 0 0 0 0 0 0面积(三角形)< triangle.width * triangle.height 但这也同样糟糕。我在这里得到的是,您应该为稍高级别的代码编写测试 - 更复杂的代码,实际上很有可能发生更改或出现欺骗性错误。

随机测试向量的情况
表明快速检查属性的好地方的代码属性包括

  1. 确定性
  2. 非平凡的
  3. 明确不变量

使用串联的简单示例(串联组合两个列表以形成一个新列表):

假设我有一个函数 concat(xs,ys) ) = xs ++ ys。我可以检查什么?任何我期望的事情都是真的!长度?是的!元素?是的!

prop_len(xs,ys) = len(xs) + len(ys) = len(concat(xs,ys))
prop_elem(xs,ys) =
    let cs = concat(xs,ys)
    elem(head xs, cs) && elem(head ys, cs) && prop_elem(tail xs,ys) && prop_elem(xs,tail ys)
// Yes, I left out the error checking for empty list, sue me.

明白了吗?

The second approach, using tools like check or quickcheck, but...

Duplicating implementation details
feels wrong

If your duplicating then you're doing it wrong. In your code you write what the task is and how how the task is performed to a precise degree. In the test you give some invariants about the result.

EDIT

A note on Known Answer Tests (KATs)

Like most generalizations, there are situations where this doesn't apply. One big area where KATs are dominant over random test vectors is cryptography (e.g. block ciphers) because there aren't supposed to be many visible invariants outside of what most type systems enforce (ex: block size). One property to check would be decrypt(key,encrypt(key,msg)) == msg.

Simple geometry has a somewhat different problem in that no set of invariants is really a good check - you can say 0 < area(triangle) < triangle.width * triangle.height but that's just as bad. What I'm getting at here is you should be writing tests for a slightly higher level of code - something more complex that actually have a good chance of changing or being deceptively wrong.

Situations For Random Test Vectors
Some properties of code that indicate a good place for quick check properties include

  1. determinism
  2. Non-trivial
  3. clear invariants

Trivial example using concatenation (combining two lists in series to form one new list):

Say I have a function concat(xs,ys) = xs ++ ys. What can I check? Anything I expect to be true! Length? Yes! Elements? Yes!

prop_len(xs,ys) = len(xs) + len(ys) = len(concat(xs,ys))
prop_elem(xs,ys) =
    let cs = concat(xs,ys)
    elem(head xs, cs) && elem(head ys, cs) && prop_elem(tail xs,ys) && prop_elem(xs,tail ys)
// Yes, I left out the error checking for empty list, sue me.

Get the drift?

萌化 2024-10-19 15:56:02

我通常使用第二种方法,因为我在断言上方声明了一堆变量,有一天我可能想要更改它们。

我认为你必须问自己哪种方式可以更清楚地了解如何实现方法调用。对我来说,这很容易是第二种方法。对于单元测试来说,这通常是有意义的。

然而,在验收测试中,第一种方法似乎使用得更频繁。最近,我必须验证 API 调用返回的图像是否正确。我可以尝试根据给我的输入构建图像,或者我可以有一个参考图像。这意味着验收测试在代码之前尚未完成,并且我必须继续手动检查图像,直到开发工作完成。然而,一旦图像“正确”,我仍然有一些东西可以阻止回归。

一般来说,我认为由您决定哪种方法最适合您正在编写的测试。

I generally use the second approach as I'd have a bunch of variables declared above the assertions that I might want to change some day.

I think you have to ask yourself which way makes it clearer how to implement the method call. For me it's easily the second approach. For unit tests this usually makes sense.

In acceptance tests however the first approach seems to be used much more frequently. Most recently I have had to verify that an image coming back from an API call is correct. I could try to construct the image based on the inputs given to me, or I could have a reference image. This means that the acceptance test isn't finished before the code though, and I have to keep manually checking the image until dev work is finished. However once the image is 'correct' I still have something to stop regressions.

Generally I think it's up to you to decide which approach works well for the test you are writing.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文