国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

C++ 測試框架 GoogleTest 初學(xué)者入門篇 丙

這篇具有很好參考價(jià)值的文章主要介紹了C++ 測試框架 GoogleTest 初學(xué)者入門篇 丙。希望對大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

*以下內(nèi)容為本人的學(xué)習(xí)筆記,如需要轉(zhuǎn)載,請聲明原文鏈接 微信公眾號「ENG八戒」https://mp.weixin.qq.com/s/RIztusI3uKRnoHVf0sloeg

C++ 測試框架 GoogleTest 初學(xué)者入門篇 丙

開發(fā)者雖然主要負(fù)責(zé)工程里的開發(fā)任務(wù),但是每個(gè)開發(fā)完畢的功能都是需要開發(fā)者自測通過的,所以經(jīng)常會聽到開發(fā)者提起單元測試的話題。那么今天我就帶大伙一起來看看大名鼎鼎的谷歌 C++ 測試框架 GoogleTest。

本文上接《C++ 測試框架 GoogleTest 初學(xué)者入門篇 乙》,歡迎關(guān)注公眾號【ENG八戒】查看更多精彩內(nèi)容。


斷言

什么是斷言?斷言是用來對表達(dá)式執(zhí)行比較的代碼塊,調(diào)用時(shí)類似函數(shù)。當(dāng)表達(dá)式一致時(shí),斷言返回成功,否則失敗。

googletest 的斷言是一組宏定義。分為 ASSERT_* 和 EXPECT_* 兩種。

比如

ASSERT_EQ(1, 2);

EXPECT_EQ(1, 2);

上面用到的兩個(gè)斷言都是比較輸入的數(shù)據(jù)是否相等。主要區(qū)別是,ASSERT_* 在失敗時(shí)終止程序運(yùn)行,EXPECT_* 在失敗時(shí)不會終止程序運(yùn)行,但是都會返回錯(cuò)誤信息。因而測試使用 EXPECT_* 可以發(fā)現(xiàn)更多的問題而不會打斷測試流程。

那么 ASSERT_* 斷言失敗時(shí),跟在其后的語句會被忽略執(zhí)行,如果其中包含對資源的釋放,那么就有會出現(xiàn)資源泄漏的問題,斷言失敗報(bào)錯(cuò)信息會附帶有堆檢查錯(cuò)誤。這時(shí)出現(xiàn)的資源泄漏問題,真的有必要修復(fù)碼?看具體情況而定。

另外,googletest 在斷言失敗后除了可以返回標(biāo)準(zhǔn)錯(cuò)誤信息,還可以附帶返回自定義錯(cuò)誤信息,使用操作符 << 添加自定義錯(cuò)誤信息。

ASSERT_EQ(1, 2) << "1 is not equal to 2";

EXPECT_EQ(1, 2) << "1 is not equal to 2";

任何可以傳遞給 ostream 的數(shù)據(jù)都可以作為自定義錯(cuò)誤信息傳遞給斷言,比如 C 字符串、string對象。

那么,測試的基本手段就是利用斷言,除了判斷型的斷言之外,googletest 還提供了其它類型的斷言用于協(xié)助測試,比如顯式成功或失敗、布爾類型斷言、字符串比較斷言等,詳情可以前往官網(wǎng)查看手冊。

https://google.github.io/googletest/reference/assertions.html

如何測試

前面提到在 googletest 中,測試的范圍分為測試套件和單個(gè)測試。測試程序可以包含多個(gè)測試套件,一個(gè)測試套件可以包含多個(gè)測試。

簡單的測試一般推薦使用 TEST 宏來定義單個(gè)測試。

一般的使用方式如下

TEST(test_suite_name, test_name) {
  // test body
}

test_suite_name 是測試套件名,test_name 是單個(gè)測試的名稱,書寫時(shí)都應(yīng)該符合 C++ 的標(biāo)識符規(guī)范,而且不能包含有下劃線_。更詳細(xì)的命名規(guī)范可以查看下面的鏈接

https://google.github.io/styleguide/cppguide.html#Function_Names

那么 TEST 宏到底代表著什么?一起來看看 TEST 宏定義的源代碼

#define GTEST_STRINGIFY_HELPER_(name, ...) #name
#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, )

#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test

#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()

#define GTEST_TEST(test_suite_name, test_name)             \
  GTEST_TEST_(test_suite_name, test_name, ::testing::Test, \
              ::testing::internal::GetTestTypeId())

#define TEST(test_suite_name, test_name) GTEST_TEST(test_suite_name, test_name)

這么多預(yù)定義處理,不妨嘗試代入上面的一般使用方式,然后展開一下,展開如下

static_assert(sizeof("test_suite_name") > 1,
              "test_suite_name must not be empty");
static_assert(sizeof("test_name") > 1,
              "test_name must not be empty");
			  
class test_suite_name_test_name_Test : public ::testing::Test {
  public:
  test_suite_name_test_name_Test() = default;
  ~test_suite_name_test_name_Test() override = default;
  test_suite_name_test_name_Test(const test_suite_name_test_name_Test &) = delete;
  test_suite_name_test_name_Test & operator=(
      const test_suite_name_test_name_Test &) = delete; /* NOLINT */
  test_suite_name_test_name_Test
  (test_suite_name_test_name_Test &&) noexcept = delete;
  test_suite_name_test_name_Test & operator=(
      test_suite_name_test_name_Test &&) noexcept = delete; /* NOLINT */

  private:
  void TestBody() override;
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;
};

::testing::TestInfo* const test_suite_name_test_name_Test::test_info_ =
    ::testing::internal::MakeAndRegisterTestInfo(
        "test_suite_name", "test_name", nullptr, nullptr,
        ::testing::internal::CodeLocation(__FILE__, __LINE__),
        ::testing::internal::GetTestTypeId(),
        ::testing::internal::SuiteApiResolver<
            parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),
        ::testing::internal::SuiteApiResolver<
            parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),
        new ::testing::internal::TestFactoryImpl<test_suite_name_test_name_Test>);
		
void test_suite_name_test_name_Test::TestBody() {
  // test body
}

從展開后的代碼,可以看到有一堆代碼,最開始有兩個(gè)斷言 static_assert 用來判斷輸入的測試套件名和測試名長度是否大于1,所以要求 TEST 宏定義輸入的測試套件名和測試名都不能為空。

然后基于 ::testing::Test 派生了一個(gè)類,類名是測試套件名和測試名串接后再在末尾加上 _Test。類內(nèi)聲明重寫 TestBody() 方法。

TEST 宏定義后面的 {} 用于定義派生類的成員方法 TestBody() 的函數(shù)體,內(nèi)部填寫標(biāo)準(zhǔn) C++ 的有效語句作為測試主體,當(dāng)然也包含調(diào)用 googletest 提供的模塊內(nèi)容,注意這個(gè)代碼塊是沒有返回值的。代碼塊執(zhí)行的斷言失敗時(shí),或者代碼崩潰,則測試 test_name 失敗,否則成功。

再來看個(gè)例子

int square(const int a)
{
  // ...
}

TEST(SquareTest, PositiveNos) { 
    ASSERT_EQ(0, square(0));
    ASSERT_EQ(36, square(6));
    ASSERT_EQ(324, square(18));
}
 
TEST(SquareTest, NegativeNos) {
    ASSERT_EQ(1, square(-1));
    ASSERT_EQ(100, square(-10));
}

上面定義了兩個(gè)測試 PositiveNos 和 NegativeNos,都屬于測試套件 SquareTest。

googletest 在設(shè)計(jì)時(shí)就指定通過測試套件來匯總測試結(jié)果,所以驗(yàn)證同一個(gè)邏輯功能的測試應(yīng)該定義在同一個(gè)測試套件內(nèi)。

測試夾具

在 googletest 里什么是測試夾具?

測試夾具這個(gè)概念是為了解決當(dāng)你的同一個(gè)邏輯功能測試?yán)铮卸鄠€(gè)測試共用測試數(shù)據(jù)或者配置的問題。

需要用到測試夾具的測試一般推薦使用 TEST_F 宏來定義單個(gè)測試。

一般的使用方式如下

TEST_F(FixtureTest, test_name) {
  // test body
}

不過,TEST_F 宏的第一個(gè)輸入?yún)?shù)不僅僅是測試套件名稱,同時(shí)也是測試夾具類名。這個(gè)測試夾具類需要自己基于類 ::testing::Test 派生實(shí)現(xiàn)。

class FixtureTest : public testing::Test {
protected:
void SetUp() override { ... }
void TearDown() override { ... }
// custom data
};

共用的測試數(shù)據(jù)或者配置就在這個(gè)派生類里添加即可。SetUp() 用于初始化數(shù)據(jù)和配置,TearDown() 用于卸載配置。

那么 TEST_F 宏到底代表著什么,和 TEST 宏的區(qū)別在哪?一起來看看 TEST_F 宏定義的源代碼

#define GTEST_STRINGIFY_HELPER_(name, ...) #name
#define GTEST_STRINGIFY_(...) GTEST_STRINGIFY_HELPER_(__VA_ARGS__, )

#define GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) \
  test_suite_name##_##test_name##_Test

#define GTEST_TEST_(test_suite_name, test_name, parent_class, parent_id)       \
  static_assert(sizeof(GTEST_STRINGIFY_(test_suite_name)) > 1,                 \
                "test_suite_name must not be empty");                          \
  static_assert(sizeof(GTEST_STRINGIFY_(test_name)) > 1,                       \
                "test_name must not be empty");                                \
  class GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                     \
      : public parent_class {                                                  \
   public:                                                                     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() = default;            \
    ~GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)() override = default;  \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (const GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &) = delete;     \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        const GTEST_TEST_CLASS_NAME_(test_suite_name,                          \
                                     test_name) &) = delete; /* NOLINT */      \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)                         \
    (GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) &&) noexcept = delete; \
    GTEST_TEST_CLASS_NAME_(test_suite_name, test_name) & operator=(            \
        GTEST_TEST_CLASS_NAME_(test_suite_name,                                \
                               test_name) &&) noexcept = delete; /* NOLINT */  \
                                                                               \
   private:                                                                    \
    void TestBody() override;                                                  \
    static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;      \
  };                                                                           \
                                                                               \
  ::testing::TestInfo* const GTEST_TEST_CLASS_NAME_(test_suite_name,           \
                                                    test_name)::test_info_ =   \
      ::testing::internal::MakeAndRegisterTestInfo(                            \
          #test_suite_name, #test_name, nullptr, nullptr,                      \
          ::testing::internal::CodeLocation(__FILE__, __LINE__), (parent_id),  \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetSetUpCaseOrSuite(__FILE__, __LINE__),          \
          ::testing::internal::SuiteApiResolver<                               \
              parent_class>::GetTearDownCaseOrSuite(__FILE__, __LINE__),       \
          new ::testing::internal::TestFactoryImpl<GTEST_TEST_CLASS_NAME_(     \
              test_suite_name, test_name)>);                                   \
  void GTEST_TEST_CLASS_NAME_(test_suite_name, test_name)::TestBody()

#define GTEST_TEST_F(test_fixture, test_name)        \
  GTEST_TEST_(test_fixture, test_name, test_fixture, \
              ::testing::internal::GetTypeId<test_fixture>())

#define TEST_F(test_fixture, test_name) GTEST_TEST_F(test_fixture, test_name)

這么多預(yù)定義處理,手癢代入一般的使用方式然后展開一下,展開如下

static_assert(sizeof("FixtureTest") > 1,
              "FixtureTest must not be empty");
static_assert(sizeof("test_name") > 1,
              "test_name must not be empty");
class FixtureTest_test_name_Test : public FixtureTest {
  public:
  FixtureTest_test_name_Test() = default;
  ~FixtureTest_test_name_Test() override = default;
  FixtureTest_test_name_Test(const FixtureTest_test_name_Test &) = delete;
  FixtureTest_test_name_Test & operator=(
      const FixtureTest_test_name_Test &) = delete; /* NOLINT */
  FixtureTest_test_name_Test
  (FixtureTest_test_name_Test &&) noexcept = delete;
  FixtureTest_test_name_Test & operator=(
      FixtureTest_test_name_Test &&) noexcept = delete; /* NOLINT */
  
  private:
  void TestBody() override;
  static ::testing::TestInfo* const test_info_ GTEST_ATTRIBUTE_UNUSED_;
};

::testing::TestInfo* const FixtureTest_test_name_Test::test_info_ =
    ::testing::internal::MakeAndRegisterTestInfo(
        #FixtureTest, #test_name, nullptr, nullptr,
        ::testing::internal::CodeLocation(__FILE__, __LINE__),
        ::testing::internal::GetTypeId<FixtureTest>(),
        ::testing::internal::SuiteApiResolver<
            FixtureTest>::GetSetUpCaseOrSuite(__FILE__, __LINE__),
        ::testing::internal::SuiteApiResolver<
            FixtureTest>::GetTearDownCaseOrSuite(__FILE__, __LINE__),
        new ::testing::internal::TestFactoryImpl<FixtureTest_test_name_Test>);
void FixtureTest_test_name_Test::TestBody() {
  // test body
}

從展開后的代碼來看,TEST_F 和 TEST 實(shí)現(xiàn)基本類似,那么使用時(shí)要遵循的規(guī)則也是一樣的,除了需要傳入自定義的基于 ::testing::Test 派生類,并且測試套件名就是測試夾具類名。

舉個(gè)例子,有個(gè)模板類 Queue 的邏輯功能需要測試,它實(shí)現(xiàn)了 FIFO 的數(shù)據(jù)隊(duì)列管理。

template <typename E>  // E 是元素類型
class Queue {
 public:
  Queue();
  void Enqueue(const E& element); // 數(shù)據(jù)入隊(duì)
  E* Dequeue();  // 數(shù)據(jù)出隊(duì),如果隊(duì)列為空則返回 NULL
  size_t size() const;  // 隊(duì)列數(shù)據(jù)長度
  ...
};

然后需要基于 ::testing::Test 派生一個(gè)測試夾具類 QueueTest

class QueueTest : public ::testing::Test {
 protected:
  void SetUp() override {
     q1_.Enqueue(1);
     q2_.Enqueue(2);
     q2_.Enqueue(3);
  }

  // void TearDown() override {}

  Queue<int> q0_;
  Queue<int> q1_;
  Queue<int> q2_;
};

夾具類 QueueTest 內(nèi)定義了三個(gè)隊(duì)列數(shù)據(jù)對象。SetUp() 內(nèi)對數(shù)據(jù)對象初始化,q0_ 保持為空,q1_ 入隊(duì)一個(gè)數(shù)據(jù),q2_ 入隊(duì)兩個(gè)數(shù)據(jù)。

為什么不實(shí)現(xiàn) TearDown() 呢?TearDown() 本來的設(shè)計(jì)意圖是卸載配置,不是剛好可以用來清理數(shù)據(jù)嗎?是的,的確可以,不過這里有個(gè)更好的選擇,就是使用類析構(gòu)函數(shù)來對隊(duì)列清空。這里有個(gè)建議就是,能用析構(gòu)函數(shù)處理的,盡量用析構(gòu)函數(shù)替代 TearDown()。因?yàn)橛梦鰳?gòu)函數(shù)可以確保被調(diào)用而且調(diào)用的順序不會亂,但不是說所有情況都建議用析構(gòu)函數(shù)替代 TearDown(),這里不展開了。

接著調(diào)用 TEST_F 定義兩個(gè)測試,基于測試夾具類 QueueTest,測試套件名也是 QueueTest,兩個(gè)測試名分別為 IsEmptyInitially 和 DequeueWorks。

TEST_F(QueueTest, IsEmptyInitially) {
  EXPECT_EQ(q0_.size(), 0);
}

TEST_F(QueueTest, DequeueWorks) {
  int* n = q0_.Dequeue();
  EXPECT_EQ(n, nullptr);

  n = q1_.Dequeue();
  ASSERT_NE(n, nullptr);
  EXPECT_EQ(*n, 1);
  EXPECT_EQ(q1_.size(), 0);
  delete n;

  n = q2_.Dequeue();
  ASSERT_NE(n, nullptr);
  EXPECT_EQ(*n, 2);
  EXPECT_EQ(q2_.size(), 1);
  delete n;
}

上面的這兩個(gè)測試定義,都會創(chuàng)建 QueueTest 類對象,分別創(chuàng)建而且不共用,所以數(shù)據(jù)不會相互影響。

第一個(gè)測試 IsEmptyInitially,googletest 框架會先創(chuàng)建 QueueTest 類對象 obj,調(diào)用 SetUp() 初始化數(shù)據(jù)和配置,執(zhí)行測試。這里只執(zhí)行了一個(gè) EXPECT_EQ 斷言,EXPECT_* 類型的斷言失敗后會返回失敗信息,不會終止測試程序,繼續(xù)下一步測試。然后調(diào)用 TearDown() 清理,最后執(zhí)行對象 obj 的析構(gòu)函數(shù)釋放資源并退出當(dāng)前測試。

第二個(gè)測試 DequeueWorks,執(zhí)行流程與上一個(gè)類似。其中測試內(nèi)容包含有 ASSERT_* 類別的斷言,這種斷言在失敗后除了會返回失敗信息,還會終止測試程序。如果斷言失敗之后的測試已沒有意義,那么適合使用 ASSERT_* 類別的斷言。

測試調(diào)用過程

其它 C++ 測試框架在測試開始前,需要你把測試排列出來,但是 googletest 不需要這么麻煩。 在 googletest 框架中,定義好測試后,只需要在 main 部分執(zhí)行如下代碼即可。

int main(int argc, char **argv)
{
    testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

InitGoogleTest() 可以對程序的輸入命令執(zhí)行解析,基于這點(diǎn)可以通過命令行的方式控制測試框架的運(yùn)行。

繼續(xù)以上面的代碼為例,大致流程如下

  1. InitGoogleTest() 初始化測試框架。
  2. RUN_ALL_TESTS() 啟動(dòng)測試。
  3. 查找測試套件內(nèi)的測試。
  4. 保存配置標(biāo)志。
  5. 創(chuàng)建 QueueTest 實(shí)例。
  6. 調(diào)用 QueueTest 實(shí)例的 SetUp() 初始化數(shù)據(jù)配置。
  7. 執(zhí)行測試。
  8. 調(diào)用 QueueTest 實(shí)例的 TearDown() 卸載數(shù)據(jù)配置。
  9. 恢復(fù)配置標(biāo)志。
  10. 重復(fù)第 3 步,直到所有測試執(zhí)行完畢,

RUN_ALL_TESTS() 返回 0 表示成功,否則失敗。只能在主線程里調(diào)用 RUN_ALL_TESTS()。

在一般的測試?yán)?,如果在測試運(yùn)行之前不需要做一些自定義的事情,而且這些事情無法在測試夾具和測試套件的框架中表達(dá)時(shí),main 函數(shù)這部分其實(shí)都一樣,那么 googletest 就在庫 gtest_main 里提供了一個(gè)很方便的入口點(diǎn),也就是幫你提前寫好了 main 函數(shù),你可以省去這部分,編譯的時(shí)候記得鏈接庫 gtest_main 即可。


好了,這個(gè)系列的文章就寫到這里啦。

學(xué)習(xí)可以等,時(shí)間不等人!

關(guān)注我,帶你學(xué)習(xí)編程領(lǐng)域更多核心技能!文章來源地址http://www.zghlxwxcb.cn/news/detail-414106.html

到了這里,關(guān)于C++ 測試框架 GoogleTest 初學(xué)者入門篇 丙的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包