Skip to content

Latest commit

 

History

History
185 lines (146 loc) · 6.24 KB

CHN-18-测试框架.md

File metadata and controls

185 lines (146 loc) · 6.24 KB

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

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

基本测试

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

// Tell DrogonTest to generate `test::run()`. Only defined this in the main file
#define DROGON_TEST_MAIN 
#include <drogon/drogon_test.h>

int sum_all(int n)
{
    int result = 1;
    for(int i=2;i<n;i++) result += i;
    return result;
}

DROGON_TEST(Sum)
{
    CHECK(sum_all(1) == 1);
    CHECK(sum_all(2) == 3);
    CHECK(sum_all(3) == 6);
}

int main(int argc, char** argv)
{
    return drogon::test::run(argc, argv);
}

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

DROGON_TEST(Sum)
{
    CHECK(sum_all(0) == 0);
    CHECK(sum_all(1) == 1);
    CHECK(sum_all(2) == 3);
    CHECK(sum_all(3) == 6);
}

现在测试失败了:

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

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

int sum_all(int n)
{
    int result = 0;
    for(int i=1;i<n;i++) result += i;
    return result;
}

测试的类型

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 来缩短和减少重复代码。

DROGON_TEST(TestContent)
{
    std::ifstream in("data.txt");
    REQUIRE(in.is_open());
    // Instead of
    // CHECK(in.is_open() == true);
    // if(in.is_open() == false)
    //    return;

    ...
}

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

异步测试

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

DROGON_TEST(RemoteAPITest)
{
    auto client = HttpClient::newHttpClient("http://localhost:8848");
    auto req = HttpReuqest::newHttpRequest();
    req->setPath("/");
    client->sendRequest(req, [TEST_CTX](ReqResuelt res, const HttpResponsePtr& resp) {
        // There's nothing we can do if the request didn't reach the server
        // or the server generated garbage.
        REQUIRE(res == ReqResult::Ok);
        REQUIRE(resp != nullptr);

        CHECK(resp->getStatusCode == k200Ok);
        CHECK(resp->contentType() == CT_APPLICATION_JSON);
    });
}

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

DROGON_TEST(RemoteAPITestCoro)
{
    auto api_test = [TEST_CTX]() {
        auto client = HttpClient::newHttpClient("http://localhost:8848");
        auto req = HttpReuqest::newHttpRequest();
        req->setPath("/");

        auto resp = co_await client->sendRequestCoro(req);
        CO_REQUIRE(resp != nullptr);
        CHECK(resp->getStatusCode == k200Ok);
        CHECK(resp->contentType() == CT_APPLICATION_JSON);
    };

    sync_wait(api_test());
}

启动 Drogon 的事件循环

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

int main() 
{
    std::promise<void> p1;
    std::future<void> f1 = p1.get_future();

    // Start the main loop on another thread
    std::thread thr([&]() {
        // Queues the promis to be fulfilled after starting the loop
        app().getLoop()->queueInLoop([&p1]() { p1.set_value(); });
        app().run();
    });

    // The future is only satisfied after the event loop started
    f1.get();
    int status = test::run(argc, argv);

    // Ask the event loop to shutdown and wait
    app().getLoop()->queueInLoop([]() { app().quit(); });
    thr.join();
    return status;
}

CMake 集成

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

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

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

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