【问题标题】:How to capture stdout/stderr with googletest?如何使用 googletest 捕获标准输出/标准错误?
【发布时间】:2011-04-17 17:41:19
【问题描述】:

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

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

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

【问题讨论】:

  • 从设计的角度来看,我建议修改实现以减少切换到日志文件的痛苦。例如,使用ostream 接口会更容易。

标签: c++ unit-testing stdout stderr googletest


【解决方案1】:

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

#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。对于不通过文件进行捕获,您可以使用管道对代替。

【讨论】:

    【解决方案2】:

    我之前使用过这个 sn-p 在测试输出时将 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);
    

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

    【讨论】:

    • 这对 googletest 不起作用,因为 gtest 使用 printf 直接进入标准输出,绕过你的重定向。但如果你想截取cout &lt;&lt; ... 的输出,这是一个很好的解决方案。我会创建一个辅助类来自动恢复析构函数中的原始streambuf ...
    • 我添加了一种方法,可以将其与 Google 测试一起使用,除非出现上述情况。
    【解决方案3】:

    Googletest 为此提供了功能:

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

    【讨论】:

    • 可能是最简单的解决方案
    • testing::internal::CaptureStderr() 也存在。此处使用例如:googletest.googlecode.com/svn/trunk/test/…
    • 应该知道这是一个私有 API,因此不受支持。我建议不要这样做
    • 我发现如果多次调用,即使在不同的测试中,也会出现段错误,因此毫无价值。
    • @NirFriedman 似乎必须在每个捕获之前致电::testing::internal::CaptureStdout()。
    【解决方案4】:

    与其这样做,不如使用依赖注入来去除对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);
        ...
     }
    

    【讨论】:

    • 虽然这通常是一个好主意,但在他的情况下它不起作用,因为 gtest 是一个外部库(从他的角度来看),他想在不修改源的情况下捕获框架的输出代码。
    【解决方案5】:

    我们所做的正是您所指的。

    首先我们创建了一些宏:

        #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
    

    【讨论】:

      【解决方案6】:

      根据 Wgaffa 的回答,我制作了这个辅助类,可以使用 std::cout 或 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;
      
      };
      

      【讨论】:

        【解决方案7】:

        将 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
        
        

        【讨论】:

          猜你喜欢
          • 2013-05-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2018-11-25
          • 1970-01-01
          • 2018-08-10
          相关资源
          最近更新 更多