【问题标题】:Test Probabilistic Functions测试概率函数
【发布时间】:2010-10-13 01:58:50
【问题描述】:

我需要一个以随机顺序返回数组的函数。我想确保它是随机的,但我不知道如何编写测试以确保数组确实是随机的。我可以多次运行代码,看看我是否不止一次得到相同的答案。虽然大型阵列不太可能发生冲突,但小型阵列(例如两个元素)很可能发生冲突。

我应该怎么做?

【问题讨论】:

标签: unit-testing testing tdd probability shuffle


【解决方案1】:

Cedric 推荐一种方法,您可以在该方法中运行足够多次的函数以获得具有统计意义的样本并验证样本的属性。

因此,对于洗牌,您可能想要验证元素之间的关系具有非常小的协方差,每个元素的预期位置是 N/2,等等。

【讨论】:

  • +1 绝对正确,这几乎是测试所谓随机过程是否足够随机的唯一方法。
  • 有趣的是,100 次运行得到相同的结果实际上可能是随机的。这就是随机性的全部意义所在。你掷硬币 10 次,每次都正面朝上。第 11 次翻转正面的概率仍然是 50%。但实际上你很少会得到这个结果。
  • 你遇到的问题是你现在有一个可能被破坏的测试!
  • 连续抛100次同一枚硬币的概率约为8*10^-31。相比之下,硬盘驱动器出现未检测到的位翻转错误的概率为 3*10^-8。所以是的,我会说,出于所有实际目的,你不会得到那个结果:-)
【解决方案2】:

基本上,诀窍是从您正在测试的类中提取随机性。 这将允许您通过注入测试中的随机性公式来测试该类,这当然根本不是随机的。

C# 示例:

public static List<int> Randomise(List<int> list, Func<bool> randomSwap)
{
    foreach(int i in list)
    {
        if (randomSwap)
        {
            //swap i and i+1;
        }
    }
    return list;
}

伪用法:

list = Randomise(list, return new Random(0, 1));

【讨论】:

    【解决方案3】:

    其他文章建议使用固定种子作为随机数生成器,模拟随机数生成器。这些都是很好的建议,我经常遵循它们。但是,有时我会改为测试随机性。

    如果您希望从源数组中随机填充一个目标数组,请考虑执行以下操作。使用连续整数加载源数组。创建第三个名为“sum”的数组并用零加载它。现在随机填充目标,然后将目标的每个元素添加到 sum 的相应元素。再做一千次。如果分布真的是随机的,那么总和应该都大致相同。您可以对 sum 数组的每个元素进行简单的 -delta

    您还可以对 sum 数组的元素进行均值和标准差,并对它们进行增量比较。

    如果您设置正确的限制,并进行足够的迭代,这就足够了。你可能会认为它会给你一个错误的否定,但如果你正确设置了限制,宇宙射线更有可能改变程序的执行。

    【讨论】:

    【解决方案4】:

    首先,您应该为随机数生成器使用固定种子,否则测试可能会随机失败(即有时它们可​​能是有序的 - that's the problem with randomness)。然后您可以做一些简单的检查,例如值是否按顺序排列,并且每次运行时值都不同。

    这是我为自己的 shuffle bag 实现编写的测试示例。

    import jdave.Specification;
    import jdave.junit4.JDaveRunner;
    import org.junit.runner.RunWith;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Random;
    
    /**
     * @author Esko Luontola
     * @since 25.2.2008
     */
    @RunWith(JDaveRunner.class)
    public class ShuffleBagSpec extends Specification<ShuffleBag<?>> {
    
        public class AShuffleBagWithOneOfEachValue {
    
            private ShuffleBag<Integer> bag;
            private List<Integer> expectedValues = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
    
            public ShuffleBag<Integer> create() {
                bag = new ShuffleBag<Integer>(new Random(123L));
                for (Integer value : expectedValues) {
                    bag.add(value);
                }
                return bag;
            }
    
            public void onFirstRunAllValuesAreReturnedOnce() {
                List<Integer> values = bag.getMany(10);
                specify(values, does.containExactly(expectedValues));
            }
    
            public void onFirstRunTheValuesAreInRandomOrder() {
                List<Integer> values = bag.getMany(10);
                specify(values.get(0), does.not().equal(0));
                specify(values.get(0), does.not().equal(1));
                specify(values.get(0), does.not().equal(9));
                specify(values, does.not().containInOrder(expectedValues));
                specify(values, does.not().containInPartialOrder(1, 2, 3));
                specify(values, does.not().containInPartialOrder(4, 5, 6));
                specify(values, does.not().containInPartialOrder(7, 8, 9));
                specify(values, does.not().containInPartialOrder(3, 2, 1));
                specify(values, does.not().containInPartialOrder(6, 5, 4));
                specify(values, does.not().containInPartialOrder(9, 8, 7));
            }
    
            public void onFollowingRunsAllValuesAreReturnedOnce() {
                List<Integer> run1 = bag.getMany(10);
                List<Integer> run2 = bag.getMany(10);
                List<Integer> run3 = bag.getMany(10);
                specify(run1, does.containExactly(expectedValues));
                specify(run2, does.containExactly(expectedValues));
                specify(run3, does.containExactly(expectedValues));
            }
    
            public void onFollowingRunsTheValuesAreInADifferentRandomOrderThanBefore() {
                List<Integer> run1 = bag.getMany(10);
                List<Integer> run2 = bag.getMany(10);
                List<Integer> run3 = bag.getMany(10);
                specify(run1, does.not().containInOrder(run2));
                specify(run1, does.not().containInOrder(run3));
                specify(run2, does.not().containInOrder(run3));
            }
    
            public void valuesAddedDuringARunWillBeIncludedInTheFollowingRun() {
                List<Integer> additionalValues = Arrays.asList(10, 11, 12, 13, 14, 15);
                List<Integer> expectedValues2 = new ArrayList<Integer>();
                expectedValues2.addAll(expectedValues);
                expectedValues2.addAll(additionalValues);
    
                List<Integer> run1 = bag.getMany(5);
                for (Integer i : additionalValues) {
                    bag.add(i);
                }
                run1.addAll(bag.getMany(5));
                List<Integer> run2 = bag.getMany(16);
    
                specify(run1, does.containExactly(expectedValues));
                specify(run2, does.containExactly(expectedValues2));
            }
        }
    
        public class AShuffleBagWithManyOfTheSameValue {
    
            private ShuffleBag<Character> bag;
            private List<Character> expectedValues = Arrays.asList('a', 'b', 'b', 'c', 'c', 'c');
    
            public ShuffleBag<Character> create() {
                bag = new ShuffleBag<Character>(new Random(123L));
                bag.addMany('a', 1);
                bag.addMany('b', 2);
                bag.addMany('c', 3);
                return bag;
            }
    
            public void allValuesAreReturnedTheSpecifiedNumberOfTimes() {
                List<Character> values = bag.getMany(6);
                specify(values, does.containExactly(expectedValues));
            }
        }
    
        public class AnEmptyShuffleBag {
    
            private ShuffleBag<Object> bag;
    
            public ShuffleBag<Object> create() {
                bag = new ShuffleBag<Object>();
                return bag;
            }
    
            public void canNotBeUsed() {
                specify(new jdave.Block() {
                    public void run() throws Throwable {
                        bag.get();
                    }
                }, should.raise(IllegalStateException.class));
            }
        }
    }
    

    这里是实现,如果你也想看的话:

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Random;
    
    /**
     * @author Esko Luontola
     * @since 25.2.2008
     */
    public class ShuffleBag<T> {
    
        private final Random random;
    
        /**
         * Unused values are in the range {@code 0 <= index < cursor}.
         * Used values are in the range {@code cursor <= index < values.size()}.
         */
        private final List<T> values = new ArrayList<T>();
        private int cursor = 0;
    
        public ShuffleBag() {
            this(new Random());
        }
    
        public ShuffleBag(Random random) {
            this.random = random;
        }
    
        public void add(T value) {
            values.add(value);
        }
    
        public T get() {
            if (values.size() == 0) {
                throw new IllegalStateException("bag is empty");
            }
            int grab = randomUnused();
            T value = values.get(grab);
            markAsUsed(grab);
            return value;
        }
    
        private int randomUnused() {
            if (cursor <= 0) {
                cursor = values.size();
            }
            return random.nextInt(cursor);
        }
    
        private void markAsUsed(int indexOfUsed) {
            cursor--;
            swap(values, indexOfUsed, cursor);
        }
    
        private static <T> void swap(List<T> list, int x, int y) {
            T tmp = list.get(x);
            list.set(x, list.get(y));
            list.set(y, tmp);
        }
    
        public void addMany(T value, int quantity) {
            for (int i = 0; i < quantity; i++) {
                add(value);
            }
        }
    
        public List<T> getMany(int quantity) {
            List<T> results = new ArrayList<T>(quantity);
            for (int i = 0; i < quantity; i++) {
                results.add(get());
            }
            return results;
        }
    }
    

    【讨论】:

      【解决方案5】:

      无需测试随机性——这已经隐含在您选择的算法和随机数生成器中。使用 Fisher-Yates/Knuth 改组算法:

      http://en.wikipedia.org/wiki/Knuth_shuffle

      来自该维基百科页面的 Java 实现:

      public static void shuffle(int[] array) 
      {
          Random rng = new Random();       // java.util.Random.
          int n = array.length;            // The number of items left to shuffle (loop invariant).
          while (n > 1) 
          {
              n--;                         // n is now the last pertinent index
              int k = rng.nextInt(n + 1);  // 0 <= k <= n.
              // Simple swap of variables
              int tmp = array[k];
              array[k] = array[n];
              array[n] = tmp;
          }
      }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-03-31
        • 2018-01-18
        • 2015-05-14
        • 2013-11-18
        • 2012-11-21
        • 2016-05-23
        • 1970-01-01
        相关资源
        最近更新 更多