エンジニアのソフトウェア的愛情

または私は如何にして心配するのを止めてプログラムを・愛する・ようになったか

標準出力の出力先をすげかえる

Google Testが取り持つ縁で、このようなエントリに出会いました。

おぉ。Google Testで標準出力をテストする。ナイス!


…。でも。

名前空間を「internal」にしてあるように、そこは触れてはいけない空間ではないかという雰囲気です。ドキュメントに載ってないのもそのためかと。


とはいえ。Google Testで標準出力の内容をテストするというアイディアをほっとくのはもったいないので。記憶を頼りに標準出力について調べてみました。

と、いうわけで。仕様的にも問題ない操作で標準出力の内容を取得する方法です。

C++のストリームはstreamとstreambufからできている

C++のストリームは2段構えで、ユーザ側のインタフェースのstreamクラスと、デバイス側のインタフェースのstreambufクラスからなっています。実際にはそれぞれに対応する派生クラスが組になってストリームを実現しています。詳しくは「<iostream> - C++ Reference」などを参照してみてください。

具体的には。fstreamにはfilebufが、stringstreamにはstringbufが対応しています。標準入力・標準出力のばあいは実装が隠されているので具体的にこれとは指し示せませんが、標準入力・標準出力にも対応するstreambufの派生クラスがあります。

で。

ストリームクラスには[http://cplusplus.com/reference/iostream/ios/rdbuf/:title=rdbuf]という関数があって、そのストリームが扱っているstreambuf(の派生クラスのインスタンス)を直接操作することができます。このうち引数を指定できる形式のほうを使うと、引数で与えたstreambufをストリームに関連付けることができます。戻り値は現在関連付けられているstreambufです。


これらをふまえて。
std::coutstreambufstringstreamstreambufであるstringbufにすげかえてみます。

実装。

#include <gtest/gtest.h>

#include <iostream>
#include <sstream>

TEST(StdoutTest, test1)
{
    std::stringbuf  buf;                          // stringstream用のstreambuf
    std::streambuf* prev = std::cout.rdbuf(&buf); // streambufをすげかえる

    std::cout << "hoge" << std::flush;            // コンソールでなくbufに出力される

    std::cout.rdbuf(prev);                        // streambufを元に戻す

    ASSERT_EQ("hoge", buf.str());                 // 結果をテスト
}

実行結果。

$ g++ -o sample1 sample1.cpp gtest/gtest-all.cc gtest/gtest_main.cc 
$ ./sample1 
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from StdoutTest
[ RUN      ] StdoutTest.test1
[       OK ] StdoutTest.test1 (1 ms)
[----------] 1 test from StdoutTest (3 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (14 ms total)
[  PASSED  ] 1 test.

(コードの内容とかビルドの内容とかは「Google Testをインストール、しないですませる方法 - エンジニアのソフトウェア的愛情」なんかを見てみてください)。


無事通りました。


ためしにstd::coutに出力する文字列をhogeからfugaに変えて実行してみます。

実行結果。

$ ./sample1 
Running main() from gtest_main.cc
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from StdoutTest
[ RUN      ] StdoutTest.test1
sample1.cpp:16: Failure
Value of: buf.str()
  Actual: "fuga"
Expected: "hoge"
[  FAILED  ] StdoutTest.test1 (4 ms)
[----------] 1 test from StdoutTest (6 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (63 ms total)
[  PASSED  ] 0 tests.
[  FAILED  ] 1 test, listed below:
[  FAILED  ] StdoutTest.test1

 1 FAILED TEST


ASSERT_EQの引数にfugaがわたっている、つまり標準出力へ出力した内容が文字列として扱えているのがわかります。

Google Testでは…

ちなみに。Google Testの中身をのぞいてみたら。もっとプリミティブな方法を使っているようでした(ファイルディスクリプタを直接操作する、unistd.hで定義されている関数を使っている模様)。ご報告まで。