zoukankan      html  css  js  c++  java
  • Google单元测试框架gtest--值参数测试

    测试一个方法,需要较多个参数进行测试,比如最大值、最小值、异常值和正常值。这中间会有较多重复代码工作,而值参数测试就是避免这种重复性工作,并且不会损失测试的便利性和准确性。

    例如测试一个函数,需要些各种参数进行边界测试,下面案例测试一个数是否为素数,需要测试各种参数。

    方法1

    class Prime {
     public:
      bool IsPrime(int n) {
        if (n <= 1) return false;
    ​
        for (int i = 2; i * i <= n; i++) {
          // n is divisible by an integer other than 1 and itself.
          if ((n % i) == 0) return false;
        }
    ​
        return true;
      }
    };
    ​
    TEST(IsPrimeTest, Negative) {
      Prime prime;
      EXPECT_FALSE(prime.IsPrime(-5));
      EXPECT_FALSE(prime.IsPrime(-1));
    }
    ​
    TEST(IsPrimeTest, Trivial) {
      Prime prime;
      EXPECT_FALSE(prime.IsPrime(0));
      EXPECT_FALSE(prime.IsPrime(1));
      EXPECT_TRUE(prime.IsPrime(2));
      EXPECT_TRUE(prime.IsPrime(3));
    }

    输出结果为:

    上述测试并不为完整,但这其中就出现了很多重复的代码,比如每个test case都需要创建prime和写EXPECT语句。

    方法2:使用testfixture,可以减少创建prime类的操作。上面的测试创建prime就是待测数据初始化,如果准备初始化环境复杂,使用test fixtrue可以极大提高效率且保证每个test的运行条件一样。

    class PrimeTest : public ::testing::Test {
     protected:
      Prime prime;
    };
    ​
    TEST_F(PrimeTest, Negative) {
      EXPECT_FALSE(prime.IsPrime(-5));
      EXPECT_FALSE(prime.IsPrime(-1));
    }
    ​
    TEST_F(PrimeTest, Trivial) {
      EXPECT_FALSE(prime.IsPrime(0));
      EXPECT_FALSE(prime.IsPrime(1));
      EXPECT_TRUE(prime.IsPrime(2));
      EXPECT_TRUE(prime.IsPrime(3));
    }

    输出结果为:

    方法2比方法1改进很多,但是依然没有改变代码重复的问题,继续改进,使用循环。

    方法3:使用循环消除重复代码。

    class PrimeTest : public ::testing::Test {
     protected:
      Prime prime;
    };
    ​
    TEST_F(PrimeTest, Negative) {
      auto vec = std::vector<int>{-5, -1, 0, 1};
      for (auto v : vec) {
        EXPECT_FALSE(prime.IsPrime(v));
      }
    }
    ​
     TEST_F(PrimeTest, Trivial) {
      auto vec2 = std::vector<int>{2, 3, 5, 7};
      for_each(vec2.begin(), vec2.end(), [&](int a) { 
          EXPECT_TRUE(prime.IsPrime(a));
          }
        );
    }

    输出结果为:

    方法3消除了复制代码语句,可以完成同类型参数的测试,但是有一个大问题,很多参数公用一个测试用例,如果某个参数出错,代码不能指示出是哪个test失败了。

    // 例如第一个用例有个参数写错了
    TEST_F(PrimeTest, Negative) {
      auto vec = std::vector<int>{-5, -1, 0, 2};
      for (auto v : vec) {
        EXPECT_FALSE(prime.IsPrime(v));
      }
    }

    输出结果是第一个test出错,只能提示到 PrimeTest.Negative失败了。倘若参数列表有很多参数,那么就不容易排查哪里失败了,这违背了测试的基本原则。

    [ RUN      ] PrimeTest.Negative
    D:PROJECTSgoogletestgoogletestsamplessample6_unittest.cc(297): error: Value of: prime.IsPrime(v)
      Actual: true
    Expected: false
    [  FAILED  ] PrimeTest.Negative (1 ms)

    比如方法1和方法2中的测试,同样的错误可以给出如下详细的错误提示。

    [ RUN      ] PrimeTest.Negative
    D:PROJECTSgoogletestgoogletestsamplessample6_unittest.cc(277): error: Value of: prime.IsPrime(2)
      Actual: true
    Expected: false
    [  FAILED  ] PrimeTest.Negative (1 ms)

    方法4:幸好gtest给出了解决方案,既能避免重复代码,又能每个测试单独运行每个参数的测试用例,出错后能准确的报告错误的位置。

    class PrimeTest : public ::testing::TestWithParam<int> {
     protected:
      Prime prime;
    };
    ​
    TEST_P(PrimeTest, ReturnsFalseForNonPrimes) {
      int n = GetParam();
      EXPECT_FALSE(this->prime.IsPrime(n));
    }
    ​
    INSTANTIATE_TEST_CASE_P(myParmTest,    // Instance name
                            PrimeTest,     // Test case name
                            testing::Values(-5,0,1,4));  // Type list

    第一,PrimeTest继承于TestWithParm<int>; 相当于创建了一个test suite。

    第二,使用Test_P创建test case,第一个参数是test fixture类名,第二个参数test case 名。在测试case里,可以使用GetParam获取每个参数,使用this指针使用Prime类实例。

    第三,注册测试case,INSTANTIATE_TEST_CASE_P 宏第一个参数是测试的名字,第二个参数是测试fixtue名字或test case名字。第三个参数是需要输入到case里运行的参数列表,使用values接收列表数据。

    输出结果如下图,values(-5,0,1,4)合计4个参数,运行4个tests。

    对比方法3,如果某个数据测试出错,可以准备报告错误信息。比如testing::Values(-5,0,1,5)),最后一个参数写成5,输出如下。提示最后一个用例test3,参数为5运行失败.

    • myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3, where GetParam() = 5 (1 ms)

    [==========] Running 4 tests from 1 test case.
    [----------] Global test environment set-up.
    [----------] 4 tests from myParmTest/PrimeTest
    [ RUN      ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/0
    [       OK ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/0 (0 ms)
    [ RUN      ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/1
    [       OK ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/1 (0 ms)
    [ RUN      ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/2
    [       OK ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/2 (0 ms)
    [ RUN      ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3
    D:PROJECTSgoogletestgoogletestsamplessample6_unittest.cc(320): error: Value of: this->prime.IsPrime(n)
      Actual: true
    Expected: false
    [  FAILED  ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3, where GetParam() = 5 (1 ms)
    ​

    方法5:上面方法4有个明显缺点,test::Values列表的参数只能是符合test case条件的数,即全是测试false的数,如果需要一些素数测试结果也是true的测试,那么就需要再写个test case,这显然不是很好的设计,那么可以考虑把测试的结果true或false也当做参数传递个测试用例,这样就可以在一个test case里实现素数和非素数的测试工作。具体实现也很简单,TestWithParam<T>,当T是一个组合数时,就实现了上述目标。

    class PrimeTest : public ::testing::TestWithParam<std::pair<int, bool>>{
     protected:
      Prime prime;
    };
    ​
    TEST_P(PrimeTest, ReturnsFalseForNonPrimes) {
      auto parm = GetParam();
      ASSERT_EQ(this->prime.IsPrime(parm.first), parm.second);
    }
    ​
    INSTANTIATE_TEST_CASE_P(myParmTest,                     
                            PrimeTest,                     
                            testing::Values(std::make_pair(-5, false),
                                std::make_pair(-5, false),
                                std::make_pair(0, false),
                                std::make_pair(1, false),
                                std::make_pair(4, false),
                                std::make_pair(2, true),
                                std::make_pair(3, true),
                                std::make_pair(5, true)
                                ));

    输出结果如下,8个测试用例,5个false和3个true的测试:

    如果某个参数测试失败,可以清晰的输出测试错误信息。

    [ RUN      ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3
    D:PROJECTSgoogletestgoogletestsamplessample6_unittest.cc(342): error: Expected equality of these values:
      this->prime.IsPrime(parm.first)
        Which is: true
      parm.second
        Which is: false
    [  FAILED  ] myParmTest/PrimeTest.ReturnsFalseForNonPrimes/3, where GetParam() = (11, false) (1 ms)

     

    尊重技术文章,转载请注明!

    Google单元测试框架gtest--值参数测试

    https://www.cnblogs.com/pingwen/p/14459873.html

  • 相关阅读:
    两数相加[链表加法] LeetCode.2
    无重复字符的最长子串[双指针+哈希表] LeetCode.3
    Rikka with Game[技巧]----2019 杭电多校第九场:1005
    度度熊与排列[搜索+剪枝]----2019 年百度之星·程序设计大赛
    度度熊与数字[公因数]----2019 年百度之星·程序设计大赛
    最大层内元素和----leetcode周赛150_1002
    拼写单词[哈希表]----leetcode周赛150_1001
    Seq[找规律]----2019 年百度之星·程序设计大赛
    实验三
    实验二
  • 原文地址:https://www.cnblogs.com/pingwen/p/14459873.html
Copyright © 2011-2022 走看看