DrogonTest 是一个内置在 Drogon 中的最小测试框架,可实现简单的异步测试和同步测试。 它用于 Drogon 自己的单元测试和集成测试。 但也可用于测试使用 Drogon 构建的应用程序。 DrogonTest 的语法受到 GTestCatch2 的启发。

您不必为应用程序使用 DrgonTest。您可以使用喜欢的任何东西。但它是一个选择。

基本测试

让我们从一个简单的例子开始。有一个函数,可以计算至某数为止之自然数之和,并想测试它的正确性。

  1. // Tell DrogonTest to generate `test::run()`. Only defined this in the main file
  2. #define DROGON_TEST_MAIN
  3. #include <drogon/drogon_test.h>
  4. int sum_all(int n)
  5. {
  6. int result = 1;
  7. for(int i=2;i<n;i++) result += i;
  8. return result;
  9. }
  10. DROGON_TEST(Sum)
  11. {
  12. CHECK(sum_all(1) == 1);
  13. CHECK(sum_all(2) == 3);
  14. CHECK(sum_all(3) == 6);
  15. }
  16. int main(int argc, char** argv)
  17. {
  18. return drogon::test::run(argc, argv);
  19. }

编译并运行…好吧,它通过了,但有一个明显的错误。 sum_all(0) 应该是 0。我们可以将它添加到我们的测试中

  1. DROGON_TEST(Sum)
  2. {
  3. CHECK(sum_all(0) == 0);
  4. CHECK(sum_all(1) == 1);
  5. CHECK(sum_all(2) == 3);
  6. CHECK(sum_all(3) == 6);
  7. }

现在测试失败了:

  1. In test case Sum
  2. /path/to/your/test/main.cc:47 FAILED:
  3. CHECK(sum_all(0) == 0)
  4. With expansion
  5. 1 == 0

注意到框架在表达式的两端打印了实际值。 让我们可以立即看到发生了什么。 解决方法很简单:

  1. int sum_all(int n)
  2. {
  3. int result = 0;
  4. for(int i=1;i<n;i++) result += i;
  5. return result;
  6. }

测试的类型

DrogonTest 带有各种类型的测试和操作。 基本的 CHECK() 只检查表达式的计算结果是否为真。 如果没有,它会打印到控制台。 CHECK_THROWS() 检查表达式是否抛出异常。诸如此类。另一方面REQUIRE()检查表达式是否为真。 如果没有则从函数返回。

失败后/表达式 为真 抛出异常 没有 抛出异常 抛出特定异常
不做任何事 CHECK CHECK_THROWS CHECK_NOTHROW CHECK_THROWS_AS
返回 REQUIRE REQUIRE_THROWS REQUIRE_NOTHROW REQUIRE_THROWS_AS
自协程返回 CO_REQUIRE CO_REQUIRE_THROWS CO_REQUIRE_NOTHROW CO_REQUIRE_THROWS_AS
杀死进程 MANDATE MANDATE_THROWS MANDATE_NOTHROW MANDATE_THROWS_AS

让我们尝试一个稍微实际的例子。 假设我们正在测试文件的内容是否符合预期。 若程序无法打开文件则没有必要进一步检查。 因此,我们可以使用 REQUIRE 来缩短和减少重复代码。

  1. DROGON_TEST(TestContent)
  2. {
  3. std::ifstream in("data.txt");
  4. REQUIRE(in.is_open());
  5. // Instead of
  6. // CHECK(in.is_open() == true);
  7. // if(in.is_open() == false)
  8. // return;
  9. ...
  10. }

同样,CO_REQUIRE 就像 REQUIRE。 但是用于协程。 当操作修改且不可恢复的全局状态时,可以使用MANDATE。 唯一合乎逻辑的做法是停止测试。

异步测试

Drogon 是一个异步网站框架。 它仅遵循因此 DrogonTest 支持测试异步。 DrogonTest 通过TEST_CTX变量跟踪测试上下文。 只需按捕获变量。 例如,测试远程 API 是否成功并返回 JSON:

  1. DROGON_TEST(RemoteAPITest)
  2. {
  3. auto client = HttpClient::newHttpClient("http://localhost:8848");
  4. auto req = HttpRequest::newHttpRequest();
  5. req->setPath("/");
  6. client->sendRequest(req, [TEST_CTX](ReqResult res, const HttpResponsePtr& resp) {
  7. // There's nothing we can do if the request didn't reach the server
  8. // or the server generated garbage.
  9. REQUIRE(res == ReqResult::Ok);
  10. REQUIRE(resp != nullptr);
  11. CHECK(resp->getStatusCode == k200Ok);
  12. CHECK(resp->contentType() == CT_APPLICATION_JSON);
  13. });
  14. }

由于测试框架支持 C++14/17 兼容性,协程必须包装在 AsyncTask 中或通过 sync_wait 调用。

  1. DROGON_TEST(RemoteAPITestCoro)
  2. {
  3. auto api_test = [TEST_CTX]() {
  4. auto client = HttpClient::newHttpClient("http://localhost:8848");
  5. auto req = HttpRequest::newHttpRequest();
  6. req->setPath("/");
  7. auto resp = co_await client->sendRequestCoro(req);
  8. CO_REQUIRE(resp != nullptr);
  9. CHECK(resp->getStatusCode == k200Ok);
  10. CHECK(resp->contentType() == CT_APPLICATION_JSON);
  11. };
  12. sync_wait(api_test());
  13. }

启动 Drogon 的事件循环

一些测试需要 Drogon 的事件循环运行。 例如,除非特别指定,否则HTTP客户端运行在Drogon的全局事件循环上。 以下样板处理了许多边缘情况,并确保事件循环在任何测试开始之前运行:

  1. int main()
  2. {
  3. std::promise<void> p1;
  4. std::future<void> f1 = p1.get_future();
  5. // Start the main loop on another thread
  6. std::thread thr([&]() {
  7. // Queues the promise to be fulfilled after starting the loop
  8. app().getLoop()->queueInLoop([&p1]() { p1.set_value(); });
  9. app().run();
  10. });
  11. // The future is only satisfied after the event loop started
  12. f1.get();
  13. int status = test::run(argc, argv);
  14. // Ask the event loop to shutdown and wait
  15. app().getLoop()->queueInLoop([]() { app().quit(); });
  16. thr.join();
  17. return status;
  18. }

CMake 集成

与大多数测试框架一样,DrgonTest 可以将自身集成到 CMake 中。 ParseAndAddDrogonTests 函数将它在源文件中看到的测试添加到 CMake 的 CTest 框架中。

  1. include(ParseAndAddDrogonTest) # Also loads ParseAndAddDrogonTests
  2. add_executable(mytest main.cpp)
  3. target_link_libraries(mytest PRIVATE Drogon::Drogon)
  4. ParseAndAddDrogonTests(mytest)

现在可以通过构建系统(在本例中为 Makefile)运行测试。

  1. make test
  2. Running tests...
  3. Test project path/to/your/test/build/
  4. Start 1: Sum
  5. 1/1 Test #1: Sum .................................... Passed 0.00 sec