我有一个类PlayCard代表一个特定的扑克牌。我有另一个类Deck,它包含一个PlayingCard对象列表。 Deck有shuffle()
方法随机化卡片顺序。
我想为shuffle()方法编写一些单元测试,但我有点不知所措。我更倾向于测试不关心洗牌的内部结构,但我希望它们是好的测试。
如果涉及随机性,我如何进行最佳单元测试?
答案 0 :(得分:10)
一种方法是进行统计检验;每次洗牌后检查正确性(设置的卡片不得更改,只有订单),并收集有关一些随机变量的统计数据(“7颗钻石的位置”,“是5通过Student's t-test和其他统计假设检验方法进行适当数量的改组之后,在心脏8之前或之后的俱乐部等等进行测试。
答案 1 :(得分:5)
我对单元测试没有特别的想法,但是关于你使用的算法的快速说明。很容易天真地在不知不觉中创建一个有偏差的混洗算法。无需重新发明轮子 - 如果正确实施,Fisher-Yates shuffle将保证无偏见的随机播放。
如果你没有正确地进行风险,你可能会遇到一些容易陷入的陷阱:
mod 52
的输出以获得随机卡位置。也导致轻微的偏见。答案 2 :(得分:5)
你的目标是测试shuffle()。既然你知道如何构造shuffle(),那么如果你能够知道生成的一系列数字,它将是你的初始套牌与洗牌套牌的确定性单位测试。
在测试期间将方法注入Deck()类可以使您的shuffle函数具有确定性。
默认情况下构建类以使用random()函数,但在注入时使用预定的数字生成函数。例如,在Python中你可以这样做:
class Deck():
def __init__(self, rand_func = random.random):
self._rand = rand_func
def rand(self):
return self._rand()
当简单地使用没有参数的Deck时,您会得到预期的随机数。但是如果你制作自己的随机数函数,你可以生成预定的数字序列。
通过这种结构,您现在可以构建一个初始牌组(无论您想要的大小)和随机数列表(同样,您需要的任何大小),您将知道输出的内容。因为shuffle()在注入版本和真正随机版本之间不会发生变化,所以您可以确定性地对shuffle()进行单元测试,并且在运行时具有随机行为。如果有要测试的极端情况,您甚至可以生成多个不同的数字序列。
关于涉及统计建模的其他答案:我认为这些是接受程度测试以证明“shuffle”算法的正确性,但它没有确定性地对函数shuffle()的实现进行单元测试。
答案 3 :(得分:1)
一个好问题。首先,绝对通过/失败测试:在洗牌之后,多重集(例如排序后的比较)必须保持不变。
为了测试随机性,你需要进行足够的改组,使得错误的“不充分随机”失败的概率非常小。例如:
在10000次洗牌中有一个.0000001%的可能性,特定的牌位于给定的52个小区中的一个小于(1-e)/ 52或大于(1 + e)/ 52。 (对于一些小e,我不知道如何计算它)。
正确的程序可能会“失败”这样的测试,但不应经常失败。
关于改组;一个常见的失败是这样做:
for i from 1..52:
从 1 ...中选择随机j ... 52并将卡i与卡j交换(错误)
这不会给你任何相同概率的排列;但是,这样做:
for i from 1..52:
从我 ...中选择一个随机j ... 52并将卡i与卡j交换(右)
答案 4 :(得分:1)
几年前,Yahoo Groups TDD列表中有一个详尽(或令人筋疲力尽)的主题。 Ron Jeffries有一些useful insights,但最好从the top开始。
答案 5 :(得分:0)
根据需要添加更多描述性测试。洗牌的质量,随机性等......
正如Alex Martelli指出的那样,您可以对排序进行统计分析,以确认其排序达到您期望的程度。
最好的结果是每张牌位都发生了变化。意思是,52张牌中的每张牌现在都处于新的位置。您可以采用我的上述方法,记录不同元素的数量并设置测试阈值。
预计至少有20张牌处于新位置。创建列表,复制,排序然后进行比较。如果结果小于20,则失败,否则通过。
答案 6 :(得分:-2)
由于我没有尝试过这个,我不能立刻说它有多实用,但是应该可以用小型甲板和一个特殊的确定性随机数发生器进行单元测试,彻底运行以便洗牌器应该产生一次可能的排列。