【问题标题】:How to achieve function unit testing during compilation?编译时如何实现功能单元测试?
【发布时间】:2014-01-24 21:30:29
【问题描述】:

我正在编写一个函数,它将一个复杂的公式应用于一堆整数。当我进行大量更改时,我总是编译然后在运行时执行一堆测试用例以确保我没有破坏任何东西,所以我想“好吧,我将一些测试用例放入我的代码中”。

但这当然是不可取的,因为该函数需要一段时间才能完成它的工作,并且每次执行程序时都会运行测试。是否有任何简单的方法可以自动执行此操作,以便在编译时检查某些情况?这是一个小例子,展示了我当前(不太理想)的方法。

#include <iostream>

int binom(int, int);

int main() {
  //I don't want these tests to happen at runtime...
  bool test1 = (binom(5,2)!=10); //5 choose 2 should be 10
  bool test2 = (binom(7,4)!=35); //7 choose 4 should be 35
  if (test1 || test2) {
    std::cout << "Your algorithm has an error!";
    return 0;
  }

  int n, k;
  std::cout << "n: "; std::cin >> n;
  std::cout << "k: "; std::cin >> k;
  std::cout << n << " choose "
            << k << " is " << binom(n,k);
  return 0;
}

int binom(int n, int k) {
  if (k > n/2)
    k = n-k;
  int nCk = 1, i=1;
  for ( ; i<=k; ++i) {
    nCk *= n-k+i;
    nCk /= i;
  }
  return nCk;
}

(我正在编写的函数比 n 选择 k 更复杂,我只是想举一个简单的例子来说明我正在尝试做的事情。)我已经阅读了有关单元测试和名为“编译时函数”的内容执行”,但我认为前者不适用,我也不太了解后者,所以我想知道是否有一种简单的方法可以理解这些或实现类似的目标。

【问题讨论】:

  • 听起来你需要考虑单元测试。
  • Boah,我完全误解了标题!已编辑...
  • @πάνταῥεῖ 我不确定我是否理解;这似乎是我的问题“你如何在编译时测试函数?”的一个答案。是“通过单元测试”。但我的问题并不是专门关于单元测试的。我觉得这种改变类似于改变“什么是杀死东西的最好方法?”到“你怎么用枪?”
  • @Joseph 然后指定您希望在编译时检查的具体内容:由模板化签名和具体模板参数提供的特定运算符/函数/typedef 成员?? (这就是我一直认为你要求的第一名,但阅读你的问题我怀疑这是你想要的!)。
  • 作为替代方案,我建议使用assert(),它会在调试模式下运行代码时退出,但在运行发布模式软件时无效。

标签: c++ unit-testing compilation


【解决方案1】:

您可以在编译时使用constexpr 评估binom 并使用static_assert 对其进行测试。请注意,您还需要用尾递归来表示迭代。对于binom 示例,这很简单:

#include <iostream>

constexpr int binom_loop(int n, int k, int i, int nCk) {
  return i <= k
    ? binom_loop(n, k, i + 1, nCk * (n-k+i) / i)
    : nCk;
}

constexpr int binom(int n, int k) {
  return binom_loop(n, k > n/2 ? n-k : k, 1, 1);
}

// assert that the calculation works - at compile-time
static_assert (binom(5, 2) == 10,
               "5 choose 2 should be 10");

int main() {
  // enum demonstrates that binom() can be evaluated at compile-time
  // if called with literal args
  enum {
    X = binom(5, 2),
  };
  std::cout << int(X) << '\n';   // prints 10
}

【讨论】:

  • +1 我不知道这个。遗憾的是我的编译器还不支持,否则我会尝试一下。不确定我是否会在实践中大量使用它,但知道它在那里很有趣。
  • @acarlon 如果您使用的是g++,请使用-std=c++11 调用它(需要最新版本)。为了玩耍和学习,您可以随时使用ideone online compiler.
【解决方案2】:

一般来说,我建议您将编译和运行单独的单元测试(在这种情况下,单元是一个函数)作为发布程序的两个强制性步骤。

从让测试在应用程序启动时运行的现状开始。除非您特别希望程序在每次运行时都进行自检,否则您希望将测试与程序的运行分开。您可以在每次编译代码时执行构建后步骤来运行测试。如果测试通过,它会将测试的(有时称为提升的)可执行文件发布到输出文件夹。这样,您始终知道您发布的可执行文件已经过良好测试。

希望在每次启动应用程序时都运行测试的情况是程序需要测试的操作环境是否存在 - 例如确保正确的第 3 方资源/实例/服务可用并受支持。即便如此,最好先在构建时测试中尽可能多地覆盖,以免在程序上线时出现意外。

关于“编译时函数执行”,它更多的是为已知参数的函数的某些输出设置一个常数值的方法。话虽如此,有一些新的 c++11 功能(如果您的编译器支持它们)将允许您在运行时检查 const 表达式(例如 static_assert),请参阅 user4815162342 答案例子。就个人而言,我对将其用于测试有一些保留,因为我会觉得测试和代码过于耦合,并且违反了“关注点分离”的原则。具体来说:-

  • 我会担心编译时间过长。
  • 有时我可能想运行我的程序,即使其中一个测试失败了。将测试注释掉可能会导致我忘记不要将它们放回原处。
  • 这仅适用于遵循非常严格约束的特定功能,因此仅适用于某些类型的测试方法。
  • 我可能想在不重新编译或注释掉测试的情况下运行一部分测试。
  • 我可能希望在完全相同的可执行文件上运行一组不同的测试,而不会有通过重新编译引入更改的风险。

这些与为什么测试不应与代码内联的论点相似(请参阅herehere 作为开始)。

另一方面,我可以看到非常简单的基本算法或数学函数的一些价值,这些函数可以非常快速地评估,例如您提供的那个。很高兴编译器可以为您检查这一点,并且您有一种很好的感觉,即任何可执行文件都通过了这些基本测试。这有点哲学上的困惑。如果它适合你的情况,那么我希望你有 C++11 的特性来使用它们。

【讨论】:

    【解决方案3】:

    人们通常会将他们的测试用例从他们的主程序中分离出来。但是,您将很难测试在 main 文件中声明和定义的函数。这些函数/类型最好在它们自己的头文件中声明并在它们自己的实现文件中定义。

    然后您可以编写第二个程序,该程序使用相同的标头并执行测试。所以..

    // binomial.h
    #ifndef BINOMIAL_H
    #define BINOMIAL_H
    
    int binom(int, int);
    
    #endif
    
    // binomial.cpp
    
    #include "binomial.h"
    
    int binom(int n, int k) {
      if (k > n/2)
        k = n-k;
      int nCk = 1, i=1;
      for ( ; i<=k; ++i) {
        nCk *= n-k+i;
        nCk /= i;
      }
      return nCk;
    }
    

    现在,您创建一个新项目,并使用它自己的main.hpp

    // unit test - main.cpp
    
    #include "binomial.h"
    #include <assert>
    
    int main() {
      bool test1 = binom(5,2) == 10; //5 choose 2 should be 10
      bool test2 = binom(7,4) == 35; //7 choose 4 should be 35
      assert(test1 && test2);
      return 0;
    }
    

    现在您每次构建时都运行它,并且您知道您的功能没有损坏(好吧,这些测试可能更全面,但您明白了)。还有一些专门用于单元测试的库,可以让您的生活更轻松。

    【讨论】:

    • 是的,所以在我的程序中,函数在whatever.hpp中,所以你说最好的方法是编写包含whatever.hpp的test.cpp并编译这两个文件同一时间?
    • @Joseph 如果您的 *.hpp 文件是标题,那是正确的。 Ed S 在他的示例中使用了不同的语义。
    • @Joseph:对不起,我一开始看错了。头,将你的函数声明保存在头文件中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 2016-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-11
    • 2014-07-05
    相关资源
    最近更新 更多