如何使用 googletest 捕获 stdout/stderr?

发布于 2024-09-24 22:14:00 字数 227 浏览 6 评论 0原文

使用 googletest 框架时是否可以捕获 stdout 和 stderr?

例如,我想调用一个将错误写入控制台(stderr)的函数。 现在,当在测试中调用该函数时,我想断言那里没有输出。

或者,也许我想测试错误行为,并想断言当我(故意)产生错误时会打印某个字符串。

Is it possible to capture the stdout and stderr when using the googletest framework?

For example, I would like to call a function that writes errors to the console (stderr).
Now, when calling the function in the tests, I want to assert that no output appears there.

Or, maybe I want to test the error behaviour and want to assert that a certain string gets printed when I (deliberately) produce an error.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(8

花伊自在美 2024-10-01 22:14:00

Googletest 为此提供了函数:

testing::internal::CaptureStdout();
std::cout << "My test";
std::string output = testing::internal::GetCapturedStdout();

Googletest offers functions for this:

testing::internal::CaptureStdout();
std::cout << "My test";
std::string output = testing::internal::GetCapturedStdout();
只有影子陪我不离不弃 2024-10-01 22:14:00

我之前曾在测试输出时使用此代码片段将 cout 调用重定向到字符串流。希望它能激发一些想法。我以前从未使用过 googletest。

// This can be an ofstream as well or any other ostream
std::stringstream buffer;

// Save cout's buffer here
std::streambuf *sbuf = std::cout.rdbuf();

// Redirect cout to our stringstream buffer or any other ostream
std::cout.rdbuf(buffer.rdbuf());

// Use cout as usual
std::cout << "Hello World";

// When done redirect cout to its old self
std::cout.rdbuf(sbuf);

在重定向回原始输出之前,请使用谷歌测试检查缓冲区中的输出。

I have used this snippet before to redirect cout calls to a stringstream when testing output. Hopefully it might spark some ideas. I've never used googletest before.

// This can be an ofstream as well or any other ostream
std::stringstream buffer;

// Save cout's buffer here
std::streambuf *sbuf = std::cout.rdbuf();

// Redirect cout to our stringstream buffer or any other ostream
std::cout.rdbuf(buffer.rdbuf());

// Use cout as usual
std::cout << "Hello World";

// When done redirect cout to its old self
std::cout.rdbuf(sbuf);

Before redirecting back to the original output use your google test to check the output in buffer.

心碎的声音 2024-10-01 22:14:00

避免这样做始终是一个好的设计理念。如果您确实想这样做,请执行以下操作:

#include <cstdio>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>

int main() {
   int fd = open("my_file.log", O_WRONLY|O_CREAT|O_TRUNC, 0660);
   assert(fd >= 0);
   int ret = dup2(fd, 1);
   assert(ret >= 0);
   printf("This is stdout now!\n");
   std::cout << "This is C++ iostream cout now!" << std::endl;
   close(fd);
}

要使用 stderr 而不是 stdout,请将 dup2 的第二个参数更改为 2。为了不通过文件进行捕获,您可以使用管道对。

Avoiding having to do this is always a good design idea. If you really want to do it the following works:

#include <cstdio>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iostream>

int main() {
   int fd = open("my_file.log", O_WRONLY|O_CREAT|O_TRUNC, 0660);
   assert(fd >= 0);
   int ret = dup2(fd, 1);
   assert(ret >= 0);
   printf("This is stdout now!\n");
   std::cout << "This is C++ iostream cout now!" << std::endl;
   close(fd);
}

To use stderr instead of stdout change the second argument to dup2 to be 2. For capturing without going via a file you could use a pipe pair instead.

贩梦商人 2024-10-01 22:14:00

不要这样做,而是使用依赖项注入来删除对 std::cout 的直接使用。在您的测试代码中,使用 std:ostringstream 类的模拟对象作为模拟对象,而不是真正的 std::cout

所以代替这个:

 void func() {
    ...
    std::cout << "message";
    ...
 }

 int main (int argc, char **argv) {
    ...
    func();
    ...
 }

有这个:

 void func(std::ostream &out) {
    ...
    out << "message";
    ...
 }

 int main(int argc, char **argv) {
    ...
    func(std::cout);
    ...
 }

Rather than do this, use dependency injection to remove the direct use of std::cout. In your test code use a mock object of class std:ostringstream as a mock object instead of the real std::cout.

So instead of this:

 void func() {
    ...
    std::cout << "message";
    ...
 }

 int main (int argc, char **argv) {
    ...
    func();
    ...
 }

have this:

 void func(std::ostream &out) {
    ...
    out << "message";
    ...
 }

 int main(int argc, char **argv) {
    ...
    func(std::cout);
    ...
 }
傲鸠 2024-10-01 22:14:00

将 Wgaffa 的建议(我喜欢)放入 Google 测试装置中,人们可能会这样写:

namespace {

    class MyTestFixture : public ::testing::Test {
    protected:
        MyTestFixture() : sbuf{nullptr} {
            // intentionally empty
        }

        ~MyTestFixture() override = default;

        // Called before each unit test
        void SetUp() override {
            // Save cout's buffer...
            sbuf = std::cout.rdbuf();
            // Redirect cout to our stringstream buffer or any other ostream
            std::cout.rdbuf(buffer.rdbuf());
        }

        // Called after each unit test
        void TearDown() override {
            // When done redirect cout to its old self
            std::cout.rdbuf(sbuf);
            sbuf = nullptr;
        }

        // The following objects can be reused in each unit test

        // This can be an ofstream as well or any other ostream
        std::stringstream buffer{};
        // Save cout's buffer here
        std::streambuf *sbuf;
    };

    TEST_F(MyTestFixture, StackOverflowTest) {
        std::string expected{"Hello"};
        // Use cout as usual
        std::cout << expected;
        std::string actual{buffer.str()};
        EXPECT_EQ(expected, actual);
    }
} // end namespace

Putting Wgaffa's suggestion (which I like) to a Google Test fixture, one might write:

namespace {

    class MyTestFixture : public ::testing::Test {
    protected:
        MyTestFixture() : sbuf{nullptr} {
            // intentionally empty
        }

        ~MyTestFixture() override = default;

        // Called before each unit test
        void SetUp() override {
            // Save cout's buffer...
            sbuf = std::cout.rdbuf();
            // Redirect cout to our stringstream buffer or any other ostream
            std::cout.rdbuf(buffer.rdbuf());
        }

        // Called after each unit test
        void TearDown() override {
            // When done redirect cout to its old self
            std::cout.rdbuf(sbuf);
            sbuf = nullptr;
        }

        // The following objects can be reused in each unit test

        // This can be an ofstream as well or any other ostream
        std::stringstream buffer{};
        // Save cout's buffer here
        std::streambuf *sbuf;
    };

    TEST_F(MyTestFixture, StackOverflowTest) {
        std::string expected{"Hello"};
        // Use cout as usual
        std::cout << expected;
        std::string actual{buffer.str()};
        EXPECT_EQ(expected, actual);
    }
} // end namespace

成熟的代价 2024-10-01 22:14:00

根据 Wgaffa 的答案,我制作了这个辅助类,可以使用 std::coutstd::cerr 构造:

class CaptureHelper
{
public:
  CaptureHelper(std::ostream& ioStream)
    : mStream(ioStream),
    mIsCapturing(false)
  { }

  ~CaptureHelper()
  {
    release();
  }

  void capture()
  {
    if (!mIsCapturing)
    {
      mOriginalBuffer = mStream.rdbuf();
      mStream.rdbuf(mRedirectStream.rdbuf());
      mIsCapturing = true;
    }
  }

  std::string release()
  {
    if (mIsCapturing)
    {
      std::string wOutput = mRedirectStream.str();
      mStream.rdbuf(mOriginalBuffer);
      mIsCapturing = false;
      return wOutput;
    }
  }

private:
  std::ostream& mStream;
  bool mIsCapturing;
  std::stringstream mRedirectStream;
  std::streambuf* mOriginalBuffer;

};

Based on the answer of Wgaffa I made this helper class which can be constructed with either std::cout or std::cerr:

class CaptureHelper
{
public:
  CaptureHelper(std::ostream& ioStream)
    : mStream(ioStream),
    mIsCapturing(false)
  { }

  ~CaptureHelper()
  {
    release();
  }

  void capture()
  {
    if (!mIsCapturing)
    {
      mOriginalBuffer = mStream.rdbuf();
      mStream.rdbuf(mRedirectStream.rdbuf());
      mIsCapturing = true;
    }
  }

  std::string release()
  {
    if (mIsCapturing)
    {
      std::string wOutput = mRedirectStream.str();
      mStream.rdbuf(mOriginalBuffer);
      mIsCapturing = false;
      return wOutput;
    }
  }

private:
  std::ostream& mStream;
  bool mIsCapturing;
  std::stringstream mRedirectStream;
  std::streambuf* mOriginalBuffer;

};
灵芸 2024-10-01 22:14:00

这是另一个实用程序类,它执行接受的答案建议的操作:

class ScopedOStreamCapture {
 public:
  explicit ScopedOStreamCapture(std::ostream& stream)
      : stream_(stream), old_(stream.rdbuf()) {
    stream_.rdbuf(buffer_.rdbuf());
  }

  ~ScopedOStreamCapture() { stream_.rdbuf(old_); }

  ScopedOStreamCapture(const ScopedOStreamCapture&) = delete;
  ScopedOStreamCapture(ScopedOStreamCapture&&) = delete;
  ScopedOStreamCapture& operator=(const ScopedOStreamCapture&) = delete;
  ScopedOStreamCapture& operator=(ScopedOStreamCapture&&) = delete;

  std::string Consume() {
    std::stringstream tmp;
    tmp.rdbuf()->swap(*buffer_.rdbuf());
    return tmp.str();
  }

  std::string Peek() {
    return buffer_.str();
  }

 private:
  std::ostream& stream_;
  std::streambuf* old_;
  std::stringstream buffer_;
};

用法:

TEST(ScopedOStreamCaptureTest, CapturesErr) {
  std::cerr << "Not captured before!" << std::endl;
  {
    ScopedOStreamCapture capture(std::cerr);
    std::cerr << "hello";
    EXPECT_EQ(capture.Peek(), "hello");
    std::cerr << " world";
    EXPECT_EQ(capture.Peek(), "hello world");
    EXPECT_EQ(capture.Consume(), "hello world");
    EXPECT_EQ(capture.Peek(), "");
  }
  std::cerr << "Not captured after!" << std::endl;
}

在 Godbolt 中运行

Here is a yet another utility class doing what the accepted answer suggests:

class ScopedOStreamCapture {
 public:
  explicit ScopedOStreamCapture(std::ostream& stream)
      : stream_(stream), old_(stream.rdbuf()) {
    stream_.rdbuf(buffer_.rdbuf());
  }

  ~ScopedOStreamCapture() { stream_.rdbuf(old_); }

  ScopedOStreamCapture(const ScopedOStreamCapture&) = delete;
  ScopedOStreamCapture(ScopedOStreamCapture&&) = delete;
  ScopedOStreamCapture& operator=(const ScopedOStreamCapture&) = delete;
  ScopedOStreamCapture& operator=(ScopedOStreamCapture&&) = delete;

  std::string Consume() {
    std::stringstream tmp;
    tmp.rdbuf()->swap(*buffer_.rdbuf());
    return tmp.str();
  }

  std::string Peek() {
    return buffer_.str();
  }

 private:
  std::ostream& stream_;
  std::streambuf* old_;
  std::stringstream buffer_;
};

Usage:

TEST(ScopedOStreamCaptureTest, CapturesErr) {
  std::cerr << "Not captured before!" << std::endl;
  {
    ScopedOStreamCapture capture(std::cerr);
    std::cerr << "hello";
    EXPECT_EQ(capture.Peek(), "hello");
    std::cerr << " world";
    EXPECT_EQ(capture.Peek(), "hello world");
    EXPECT_EQ(capture.Consume(), "hello world");
    EXPECT_EQ(capture.Peek(), "");
  }
  std::cerr << "Not captured after!" << std::endl;
}

Run in Godbolt

奶茶白久 2024-10-01 22:14:00

我们正在做的正是你所指的。

首先我们创建了一些宏:

    #define CAPTURE_STDOUT StdoutRedirect::instance().redirect();
    #define RELEASE_STDOUT StdoutRedirect::instance().reset();
    #define ASSERT_INFO( COUNT, TARGET )   \
      ASSERT_PRED_FORMAT2(OurTestPredicates::AssertInfoMsgOutput, TARGET, COUNT );

请参阅此答案以捕获 stdout 和 stderr:
https://stackoverflow.com/a/5419409/9796918
只需使用它们的BeginCapture()、EndCapture() 代替我们的redirect() 和reset()。

在 AssertInfoMsgOutput 方法中:

    AssertionResult OurTestPredicates::AssertInfoMsgOutput( const char* TARGET,
        const char* d1,
        const char* d2,
        int         COUNT )
    {
      int count = 0;
      bool match = false;
      std::string StdOutMessagge = GetCapture();
      // Here is where you process the stdout/stderr info for the TARGET, and for
      // COUNT instances of that TARGET message, and set count and match
      // appropriately
      ...
      if (( count == COUNT ) && match )
      {
        return ::testing::AssertionSuccess();
      }
      return :: testing::AssertionFailure() << "not found";
    }

现在,在单元测试中,只需将要捕获 stdout/stderr 的调用包装起来:

    CAPTURE_STDOUT
    // Make your call to your code to test / capture here
    ASSERT_INFO( 1, "Foo bar" );
    RELEASE_STDOUT

We are doing exactly what you are referring to.

First we created some macros:

    #define CAPTURE_STDOUT StdoutRedirect::instance().redirect();
    #define RELEASE_STDOUT StdoutRedirect::instance().reset();
    #define ASSERT_INFO( COUNT, TARGET )   \
      ASSERT_PRED_FORMAT2(OurTestPredicates::AssertInfoMsgOutput, TARGET, COUNT );

See this answer for capturing stdout and stderr:
https://stackoverflow.com/a/5419409/9796918
Just use their BeginCapture(), EndCapture() in place of our redirect() and reset().

In the AssertInfoMsgOutput method:

    AssertionResult OurTestPredicates::AssertInfoMsgOutput( const char* TARGET,
        const char* d1,
        const char* d2,
        int         COUNT )
    {
      int count = 0;
      bool match = false;
      std::string StdOutMessagge = GetCapture();
      // Here is where you process the stdout/stderr info for the TARGET, and for
      // COUNT instances of that TARGET message, and set count and match
      // appropriately
      ...
      if (( count == COUNT ) && match )
      {
        return ::testing::AssertionSuccess();
      }
      return :: testing::AssertionFailure() << "not found";
    }

Now in your unit test just wrap your calls that you want to capture stdout/stderr with:

    CAPTURE_STDOUT
    // Make your call to your code to test / capture here
    ASSERT_INFO( 1, "Foo bar" );
    RELEASE_STDOUT
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文