具有随机性的 JUnit 测试方法

发布于 2024-09-03 16:08:02 字数 487 浏览 4 评论 0原文

我目前正在为自己开发一个小项目,我将其作为熟悉单元测试和维护适当文档的机会。

我有一个 Deck 类,代表一副纸牌(它非常简单,说实话,我可以确定它无需单元测试即可工作,但就像我说的那样,我已经习惯了使用单元测试),它有一个 shuffle() 方法,可以改变牌组中卡片的顺序。

实现非常简单并且肯定会起作用:

public void shuffle()
{
    Collections.shuffle(this.cards);
}

但是,我如何实现此方法的单元测试。我的第一个想法是在调用 shuffle() 后检查这副牌的顶牌是否不同,但当然也有可能是相同的。我的第二个想法是检查卡片的整个顺序是否已更改,但它们可能再次处于相同的顺序。那么,我如何编写一个测试来确保该方法在所有情况下都有效?而且,一般来说,如何对结果取决于某种随机性的方法进行单元测试?

干杯,

皮特

I'm working on a small project for myself at the moment and I'm using it as an opportunity to get acquainted with unit testing and maintaining proper documentation.

I have a Deck class with represents a deck of cards (it's very simple and, to be honest, I can be sure that it works without a unit test, but like I said I'm getting used to using unit tests) and it has a shuffle() method which changes the order of the cards in the deck.

The implementation is very simple and will certainly work:

public void shuffle()
{
    Collections.shuffle(this.cards);
}

But, how could I implement a unit test for this method. My first thought was to check if the top card of the deck was different after calling shuffle() but there is of course the possibility that it would be the same. My second thought was to check if the entire order of cards has changed, but again they could possibly be in the same order. So, how could I write a test that ensures this method works in all cases? And, in general, how can you unit test methods for which the outcome depends on some randomness?

Cheers,

Pete

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

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

发布评论

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

评论(9

姐不稀罕 2024-09-10 16:08:02

断言你的洗牌方法是否真的洗牌是非常困难的,即使不是不可能。默认随机数生成器仅在一定程度上是随机的。无法测试您是否对这种程度的随机性感到满意,因为这会花费太多时间。您实际测试的是随机数生成器本身,这没有多大意义。

然而,您可以测试的是此方法的不变量

  • 如果退出该方法,牌组中的牌数应与您进入该方法时的牌数完全相同。
  • shuffle 方法不应引入重复项。

您当然可以创建一个测试来检查在 n 洗牌序列中是否没有返回重复的牌组。但偶尔这个测试可能会失败(尽管不太可能,正如其他答案中已经指出的那样)。

其他需要考虑的是随机数生成器本身。如果这只是一个玩具项目,java.util.Random 就足够了。如果您打算创建一些在线纸牌游戏,请考虑使用java.security.SecureRandom

Asserting whether your shuffle method actually shuffles the cards is very hard if not impossible. Default random number generators are only random to a certain degree. It's impossible to test whether you're satisfied with this degree of randomness because it would take too much time. What you're actually testing is the random number generator itself which doesn't make much sense.

What you can test however, are the invariants of this method.

  • If you exit the method, there should be the exact same number of cards in the deck as when you entered it.
  • The shuffle method should not introduce duplicates.

You can of course create a test that checks that in a sequence of n shuffles there are no duplicate decks returned. But once in a while this test may fail (however unlikely, as already stated in other answers).

Something else to take into account is the random number generator itself. If this is just a toy project, java.util.Random is sufficient. If you intend to create some online card game, consider using java.security.SecureRandom.

玩物 2024-09-10 16:08:02

首先,让我们考虑一下所涉及的概率:

  1. 您无法保证洗牌时不会将牌按正确的顺序放置。然而,用一副 52 张牌做到这一点的概率是 1 / 52! (即,它是最小的,可能不值得担心。)

  2. 您肯定需要检查整副牌,但因为顶牌与洗牌前相同的概率是 1 / 52。

对于一般情况,假设您使用的是 java. util.Random 数字生成器,只需使用相同的种子对其进行初始化即可。那么预定输入的输出应该是可重复的。

但是,特别是对于这种情况,假设您还没有实现自己的 List 我真的没有看到测试 Collections.shuffle(List list) 的意义> 或 Collections.shuffle(List list, Random rnd) (API 链接),因为这些只是 Java API 的一部分。

Firstly, let's think about the probabilities involved:

  1. You can't guarantee that the shuffle won't place the cards in exact order. However, the probability of doing this with a 52-card deck is 1 / 52! (i.e. it's minimal and probably not worth worrying about.)

  2. You definitely will need to check the whole deck, though because the probability of the top card being the same as it was before the shuffle is 1 / 52.

For the general case, and assuming you're using the java.util.Random number generator, just initialise it with the same seed. Then the output for a pre-determined input should then be repeatable.

However, specifically for this case, assuming you haven't implemented your own List I don't really see the point in testing Collections.shuffle(List<?> list) or Collections.shuffle(List<?> list, Random rnd) (API link) as these are just part of the Java API.

揽月 2024-09-10 16:08:02

另一种方法是使用 shuffle(Listlist, Random random) 方法并注入一个以常量作为种子的 Random 实例。

这样,您的 JUnit 测试就可以运行一系列调用并检查输出是否是预期的输出。

类的正常实现将创建一个无种子​​的 Random 实例。

Another approach would be to use the shuffle(List<?> list, Random random) method and to inject a Random instance seeded with a constant.

That way your JUnit test can run a series of calls and check the output to be the expected output.

The normal implementation of your class would create a Random instance which is unseeded.

(り薆情海 2024-09-10 16:08:02

实际上,您将所有艰苦的工作委托给了 java.util.Collections 类。这是 Java 集合 API 中的一个中心类,您应该假设它的工作方式与您使用 java.lang.String 类的方式类似。

我宁愿建议针对接口进行编码,并使用 shuffle() 方法模拟/存根您的实现类。然后,您可以断言对 shuffle() 方法的调用实际上是从您的测试中调用的,而不是像 Sun/Oracle 人员之前彻底测试的那样进行测试。

这使您能够更加专注于测试您自己的代码,其中 99.9% 的错误可能都位于其中。例如,如果您将 java.util.Collections.shuffle() 方法替换为另一个框架中的方法或您自己的实现,您的集成测试仍然可以工作!

我知道您这样做是因为您想学习,并且我相信有关从其他框架中存根/模拟逻辑的知识作为您的测试知识的一部分非常有用。

You're actually delegating away all the hard work to the java.util.Collections class. This is a central class in Java's collection API and you should just assume that it works like you probably do with the java.lang.String class.

I would rather recommend to code against interfaces and mock/stub away your implementation class with the shuffle() method. Then you can just assert that your calls on the shuffle() method are actually called from your test instead of testing exactly the same as the Sun/Oracle guys have tested thorough before.

This enables you to focus more on testing your own code where 99.9% of all the bugs probably are located. And if you for example replace java.util.Collections.shuffle() method with one from another framework or your own implementation, your integration test will still work!

I understand that you're doing this because you want to learn and I believe the knowledge about stubbing/mocking away logic from other frameworks are very useful as part of your testing knowledge.

沫离伤花 2024-09-10 16:08:02

我猜你的牌组里有 52 张牌。在随后的两次调用中获得相同订单的可能性极低,因此我不会太在意它。但是,如果您确实多次开始获得类似的套牌,我认为可以肯定地说您的随机数生成器存在一些问题。

所以,答案是:检查整副牌的顺序是否不同。

另外,我认为您可以安全地要求 shuffle() 方法不要连续两次以相同的顺序返回卡片。如果您想绝对确保遵循该要求,您可以检查方法实现中的相似性。

I suppose you have 52 cards in your deck. The possibility of getting the same order in two subsequent calls is extremely low, so I wouldn't bother about it too much. But, if you do start getting similar decks multiple times, I think it's safe to say you have some problems with your random number generator.

So, the answer: check that the order is different for the whole deck.

Also, I think that you can safely make it a requirement for your shuffle() method not to return the cards in the same order twice in a row. And if you want to absolutely make sure to follow that requirement, you can check for similarity in the method implementation.

命硬 2024-09-10 16:08:02

有趣的问题。在我看来,最好的方法是将每个“洗牌”存储在一个集合中,然后在每次洗牌后进行比较,如果您的牌组与该集合中的任何先前“牌组”相匹配。

根据您需要的“随机性”数量,您将增加在该单元测试中存储的洗牌牌组数量,即经过 50 次洗牌后,您将拥有 50 个“牌组”的集合

Interesting question. In my opinion the best way would be to store each "shuffle" in a collection, then compare after each shuffle if your deck matches any of the previous "decks" in the collection.

Depending on the ammount of "Randomness" you require you will increase the ammount of shuffled decks you store in that unit test i.e. after 50 shuffles you would have a collection of 50 "decks"

↘紸啶 2024-09-10 16:08:02

大多数人似乎认为您应该测试您正在测试的内容。我的意思是您正在构建的内容(或集成,当您确保第三方库实际上按照它所说的那样做时)。

但您不应该测试 Java 语言本身。

应该有一些测试原则,例如“不要测试 PlusEquals”。

Most people seem to be of the opinion that you should test what you're testing. By that I mean what you're building (or integrating, when you're making sure a third-party library actually does what it says it does).

But you should not test the Java language itself.

There should be some testing principle like "Don't Test PlusEquals".

恰似旧人归 2024-09-10 16:08:02

我在建模和模拟框架中研究过随机数,并遇到了类似的问题:如何真正对我们的 PRNG 实现进行单元测试。最后我其实没有这么做。相反,我所做的是执行一些健全性检查。例如,我们的 PRNG 都通告它们生成了多少位,因此我检查了这些位是否确实发生了变化(经过 10k 次迭代左右),并且所有其他位均为 0。我检查了有关种子的正确行为(使用相同的值初始化 PRNG)然后,我决定将实际的随机性测试放入交互式 UI 中,以便可以在需要时进行测试,但我认为,对于单元测试来说,非确定性结果并不是那么好。

I've worked on random numbers in a modeling and simulations framework and stood before a similar problem: How can I actually unit-test our PRNG implementations. In the end I actually didn't do it. What I did instead was to perform a few sanity checks. For example, our PRNGs all advertise how many bits they generate, so I checked whether those bits actually did change (with 10k iterations or so) and all other bits were 0. I checked for proper behavior concerning seeds (initializing the PRNG with the same seed must produce the same sequence of numbers), etc. I then decided to put the actual randomness tests into an interactive UI so they can be tested whenever desired but for unit tests a non-deterministic outcome isn't that nice, I thought.

决绝 2024-09-10 16:08:02

您可以反复洗牌,记录黑桃 A(或其他牌或所有其他牌)最终成为牌堆中第一张牌的次数。理论上,52 次洗牌中,大约有 1 次该牌应该位于最上面。收集完所有数据后,将实际频率与数字 1/52 进行比较,并检查差异(绝对值)是否低于某个选定的 epsilon 值。洗牌次数越多,epsilon 值就越小。如果您的 shuffle() 方法将卡片放在 epsilon 阈值内的顶部,您可以确定它会按照您的意愿随机化卡片。

而且您不必只停在顶牌上。您可以测试牌组中的每个位置是否给出相同的结果。用一张卡做,用所有卡做,可能都没关系。这可能有点矫枉过正,但它可以保证你的 shuffle() 正常工作。

You could shuffle repeatedly, keeping track of how many times the Ace of spades (or some other card, or all other cards) ends up as the first card in the deck. Theoretically the card should end up on top about 1 out of 52 shuffles. After all the data has been gathered, compare the actual frequency to the number 1/52 and check if the difference (absolute value) is lower than some chosen epsilon value. The more you shuffle, the smaller your epsilon value can be. If your shuffle() method puts the card on the top within your epsilon threshold, you can be sure it is randomizing the cards as you would like.

And you don't have to stop just at the top card. You can test if each location in the deck gives the same results. Do it with one card, do it will all cards, it probably doesn't matter. It might be overkill, but it would guarantee your shuffle() is working correctly.

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