如何使用googletest捕获stdout/stderr输出?

75

在使用googletest框架时,是否有可能捕获标准输出(stdout)和标准错误(stderr)?

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

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


6
从设计角度出发,我建议修改实现方式,使得切换到日志文件更加轻松。例如使用ostream接口会更容易。 - Matthieu M.
7个回答

132

Googletest 提供了用于此目的的函数:

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

可能是最简单的解决方案 - Jan Tojnar
8
testing::internal::CaptureStderr()也存在。在这里被用作一个例子:https://googletest.googlecode.com/svn/trunk/test/gtest-death-test_test.cc - Heinzi
33
请注意,这是私有 API,因此不受支持。我建议不要使用它。 - Antoine
5
如果被调用超过一次,即使在不同的测试中,我发现��段代码会导致分段错误,因此毫无价值。 - Nir Friedman
2
@NirFriedman 看起来每次进行捕获前都需要调用::testing::internal::CaptureStdout() - andreee
显示剩余5条评论

42

我之前使用过这段代码片段,将cout调用重定向到stringstream以测试输出。希望这能启发一些灵感。我以前从未使用过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);

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


1
这种方法对于googletest无效,因为gtest使用printf直接输出到stdout,绕过了你的重定向。但如果你想拦截cout << ...的输出,这是一个不错的解决方案。我建议创建一个辅助类,在析构函数中自动恢复原始的streambuf... - Florin T.
我已经添加了一种使用这个方法的方式,可以在不考虑上面评论的情况下与Google测试一起使用。 - Jim Daehn

7
避免需要这样做总是一个好的设计思路。如果你真的想这样做,以下方法可以实现:
#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。如果不想通过文件进行捕获,则可以使用管道对。


4
与其这样做,不如使用依赖注入来删除直接使用 std::cout 的方法。在测试代码中,使用一个类为std:ostringstream的模拟对象作为替代真实的std::cout
因此,不要像这样做:
 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);
    ...
 }

尽管这通常是个好主意,但在他的情况下行不通,因为gtest是一个外部库(从他的角度来看),他希望在不修改源代码的情况下捕获框架的输出。 - Florin T.
如果他/她有许多打印一些错误消息或日志的函数怎么办?在这种情况下,添加额外的参数仅用于捕获流并不是一个好主意。他/她可以做的一件事是使用全局流对象,并根据需要更改/重定向它。std::cout、std::cerr和std::clog就是这样的全局对象。最好使用std::cout,并在需要时将其重定向到另一个流。 - Keshav Sahu
@KeshacSabu 如果代码中有很多这样的函数,那么需要修复的设计缺陷也会很多。修复它们是一个好主意。 - Raedwald

2

如果按照Wgaffa的建议(我很喜欢)来编写Google Test Fixture,可能会这样写:

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


0

基于 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;

};

-1

我们正在做你所提到的事情。

首先,我们创建了一些宏:

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

查看此答案以捕获标准输出和标准错误: https://dev59.com/52035IYBdhLWcg3wcviS#5419409 只需使用他们的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";
    }

现在,在您的单元测试中,只需使用以下代码包装您想捕获标准输出和标准错误流的调用:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接