如何设计一个只占代码部分时间的测试界面?

时间:2016-05-14 02:21:30

标签: java unit-testing benchmarking

我希望计算我的代码在多次测试运行中平均执行的时间。在每次测试运行中,doWork()执行我想要的时间工作。但我也想在每次测试中checkWork(),而不计算时间。我将有许多类似的练习#类,所以我想通过TestInterface抽象测试。我目前的方式是否合理?或者是否有更好的设计模式/标准方法?提前谢谢。

@FunctionalInterface
public interface TestInterface {
  void test(final int NUM_TESTS);
}

public class TimeTests {
  public static void test(TestInterface ti, final int NUM_TESTS, String testName) {
    DecimalFormat df = new DecimalFormat("#.####");
    long start = System.nanoTime();
    ti.test(NUM_TESTS);
    System.out.println("DEBUG: " + testName + " took "
              + df.format((System.nanoTime() - start) * 1.0 / NUM_TESTS)
              + " nanoseconds on average for " + NUM_TESTS + " tests");

  }
}

public class Exercise1 {
  private static final int NUM_TESTS = (int) Math.pow(10, 6);

  private static void mainWork(List<Integer> A) {
    // do stuff and time it
  }

  private static void checkWork(List<Integer> A) {
    // do stuff but don't count it towards the time
  }

  public static void main(String[] args) {

    TimeTests.test((NUM_TESTS_LOCAL) -> {
      for (int i = 0; i < NUM_TESTS_LOCAL; ++i) {
        List<Integer> A = new ArrayList<>();
        // add random elements to A
        mainWork(A);
        checkWork(A);
      }
    }, NUM_TESTS, "Exercise1");
  }
}

1 个答案:

答案 0 :(得分:0)

好吧,我认为我设法为这项任务整理了一个体面的框架(这是正确的词吗?)。如果有人可以插手让我知道我的方法是否有用,我真的很感激。

虽然到目前为止我的代码似乎对我的用例很好,但我有几个问题:

  1. public interface CloneableTestInput<T extends CloneableTestInput<T>>的接口定义中,类型模板<T extends CloneableTestInput<T>如何不是循环定义?我不确定我完全理解该类型模板的用语。
  2. 有没有办法制作实现CloneableList的通用CloneableTestInput<List>类?目前,我需要为每个Collection类型(例如ArrayList,LinkedList,...)单独实现。同样,是否可以创建实现CloneableSet
  3. 的通用CloneableTestInput<Set>

    提前致谢:)

    测试框架

    第I部分 - 测试输入的接口

    这允许TimeTests.java适用于通用输入类型。

    public interface CloneableTestInput<T extends CloneableTestInput<T>> extends Cloneable {
      T clone();
    }
    
    public class CloneableString implements CloneableTestInput<CloneableString> {
      public String data;
      public CloneableString() {}
      public CloneableString(String input) { data = input; }
    
      public CloneableString clone() { return new CloneableString(String.valueOf(data)); }
    }
    
    public class CloneableArrayList extends ArrayList implements CloneableTestInput<CloneableArrayList> {
      public CloneableArrayList(ArrayList input) {
        this.addAll(input);
      }
    
      @Override
      public CloneableArrayList clone() {
        return new CloneableArrayList(this);
      }
    }
    

    第II部分 - 定时测试的接口

    @FunctionalInterface
    public interface TimeTestsInterface<outputType> {
      void test(Callable<CloneableTestInput> formInput
              , Function<CloneableTestInput, outputType> runAlgorithm
              , Function<CloneableTestInput, outputType> getKnownOutput
              , BiFunction<outputType, outputType, Boolean> checkResults
              , final int NUM_TESTS, String testName);
    }
    
    public class TimeTests<outputType> implements TimeTestsInterface<outputType> {
      public void test(Callable<CloneableTestInput> formInput
              , Function<CloneableTestInput, outputType> runAlgorithm
              , Function<CloneableTestInput, outputType> getKnownOutput
              , BiFunction<outputType, outputType, Boolean> checkResults
              , final int NUM_TESTS, String testName) {
        try {
          DecimalFormat df = new DecimalFormat("#.####");
          long total = 0, start;
          for (int i=0; i < NUM_TESTS; ++i) {
            CloneableTestInput input = formInput.call();
    
            CloneableTestInput orig_input = input.clone();
            start = System.nanoTime();
            outputType algorithmResult = runAlgorithm.apply(input);
            total += System.nanoTime() - start;
            outputType expectedResult = getKnownOutput.apply(orig_input);
            assert(checkResults.apply(algorithmResult, expectedResult));
          }
          System.out.println("DEBUG: " + testName + " took "
                  + df.format(total * 1.0 / NUM_TESTS)
                  + " nanoseconds on average for " + NUM_TESTS + " tests");
        } catch (Exception|AssertionError e) {
          System.out.println(e.toString() + " - " + e.getMessage() + " - ");
          e.printStackTrace();
        }
    
      }
    }
    

    示例用法

    增加BigInteger(使用CloneableArrayList)

    /**
     * Problem 6.2 from EPI
     * Given an array A of digits encodiing a decimal number D,
     * with MSD at A[0]. Update A to hold D + 1.
     */
    public class PlusOne {
      private static final int NUM_TESTS = (int) Math.pow(10, 5);
      private static final int ARR_LENGTH = (int) Math.pow(10, 2);
    
      private static ArrayList<Integer> plusOne(ArrayList<Integer> A) {
        int n = A.size() - 1;
        A.set(n, A.get(n) + 1);
        for (int i = n; i > 0 && A.get(i) == 10; --i) {
          A.set(i, 0);
          A.set(i-1, A.get(i-1) + 1);
        }
        if (A.get(0) == 10) {
          // Need additional digit up front as MSD
          A.set(0,0);
          A.add(0,1);
        }
        return A;
      }
    
      private static ArrayList<Integer> randArray(int len) {
        ArrayList<Integer> A = new ArrayList<>();
        if (len == 0) return A;
        Random rgen = new Random();
        A.add(rgen.nextInt(9) + 1);
        --len;
        while (len != 0) {
          A.add(rgen.nextInt(10));
          --len;
        }
        return A;
      }
    
      public static void main(String[] args) {
        Callable<CloneableTestInput> formInput = () -> new CloneableArrayList(randArray(ARR_LENGTH));
        Function<CloneableTestInput, ArrayList<Integer>> runAlgorithm =
            (input) -> plusOne((ArrayList<Integer>) input);
        Function<CloneableTestInput, ArrayList<Integer>> getKnownOutput = 
            (orig_input) -> {
          BigInteger B = new BigInteger(Joiner.on("").join((ArrayList<Integer>) orig_input));
          B = B.add(BigInteger.valueOf(1));
          ArrayList<Integer> expectedOutput = new ArrayList<>();
          while (B.compareTo(BigInteger.valueOf(0)) > 0) {
            expectedOutput.add(0, B.mod(BigInteger.valueOf(10)).intValue());
            B = B.divide(BigInteger.valueOf(10));
          }
          return expectedOutput;
        };
        BiFunction<ArrayList<Integer>, ArrayList<Integer>, Boolean> checkResults = List::equals;
        TimeTests<ArrayList<Integer>> algTimer = new TimeTests<>();
        algTimer.test(formInput, runAlgorithm, getKnownOutput, checkResults, NUM_TESTS, "PlusOne");
      }
    }
    

    可以将字符串重新排列为回文吗? (使用CloneableString)

    public class CanStringBePalindrome {
      private static final int INPUT_STRING_LENGTH = (int) Math.pow(10, 2);
      private static final int NUM_TESTS = (int) Math.pow(10, 6);
    
      private static boolean canFormPalindromeHash(final String s) {
        Map<Character, Integer> charFreqs = new HashMap<>();
        for (int i = 0; i < s.length(); ++i) {
          char c = s.charAt(i);
          if (!charFreqs.containsKey(c))
            charFreqs.put(c, 1);
          else
            charFreqs.put(c, charFreqs.get(c) + 1);
        }
    
        int oddFreqCount = 0;
        for (Map.Entry<Character, Integer> entry : charFreqs.entrySet()) {
          if ((entry.getValue() % 2) != 0 && (++oddFreqCount > 1))
            return false;
        }
        return true;
      }
    
    
      private static boolean canFormPalindromeSorting(final String s) {
        // TODO : find faster/simpler way of getting frequency counts
        char[] a = s.toCharArray();
        Arrays.sort(a);
        int oddFreqCount = 0;
        int numCurrChar =1;
    
        for (int i = 1; i < a.length && oddFreqCount <= 1; ++i) {
          if(a[i] != a[i-1]) {
            if ((numCurrChar & 1) != 0)
              ++oddFreqCount;
            numCurrChar = 1;
          } else
            ++numCurrChar;
        }
        if ((numCurrChar & 1) != 0)
          ++oddFreqCount;
        return oddFreqCount <= 1;
      }
    
      private static String randString(int len) {
        StringBuilder sb = new StringBuilder();
        Random rgen = new Random();
        while (len-- > 0)
          sb.append((char)(rgen.nextInt(26) + 'A'));
        return sb.toString();
      }
    
      public static void main(String[] args) {
        Callable<CloneableTestInput> formInput = () -> new CloneableString(randString(INPUT_STRING_LENGTH));
        Function<CloneableTestInput, Boolean > runAlgorithm =
                (input) -> canFormPalindromeHash(((CloneableString)input).data);
        Function<CloneableTestInput, Boolean> getKnownOutput =
                (orig_input) -> canFormPalindromeSorting(((CloneableString)orig_input).data);
        BiFunction<Boolean, Boolean, Boolean> checkResults = Boolean::equals;
        TimeTests<Boolean> algTimer = new TimeTests<>();
        algTimer.test(formInput, runAlgorithm, getKnownOutput, checkResults
                , NUM_TESTS, "CanStringBePalindrome");
      }
    }