はじめに
【C++言語】 は、C言語を拡張して1980年代に登場したプログラミング言語です。
特徴としては、C言語の「速さ・低レベル制御」を残しつつ、「オブジェクト指向」「例外処理」「標準ライブラリの拡充」などが加わりました。
【注意】このメモは、C++言語を全くしらない人間が、ChatGPTに聞いて適当に書いている、C++言語の基本的な記載方法のまとめです。
(内容は確認しながら書いているつもりですが、間違ってたらごめんなさい)
🟦 リンカの --wrap で関数を乗っ取る(おすすめ・Linux/ELF系)
「本番ソースを一切変更せずに」
外部 API(iconv / syslog など)をテストから差し替える方法
🟢 --wrap とは何か?
--wrap=symbol は、リンカ(ld)の機能で、
指定した関数呼び出しを、別名関数にすり替える
iconv_open() → __wrap_iconv_open()
同時に、元の本物関数は
__real_iconv_open()
として呼べるようになります。
🟢 なぜ --wrap がテストに向いているのか?
この方法の最大の利点は以下です。
✅ 本番ソースを一切変更しない
✅ private / static 関数の中で呼ばれていても関係ない
✅ C API(iconv, syslog など)も確実に差し替えられる
✅ テストビルドだけで有効にできる
👉
「iconv を直接呼んでいるからテストできない」
という問題を、設計変更なしで解決できます。
🟦 wrap の有効化(Makefile)
テストビルド時の LDFLAGS に、以下を追加します。
LDFLAGS = -Wl,--wrap=syslog
override LDFLAGS += \
-Wl,--wrap=iconv_open \
-Wl,--wrap=iconv \
-Wl,--wrap=iconv_close
ポイント
override を付けることで
✔ make LDFLAGS=... で上書きされても 必ず有効
✔ += なので既存の LDFLAGS を壊さない
✔ wrap は テストターゲットだけに指定するのが基本
🟦 wrap 関数の基本構造
wrap を使う場合、次の 3 点が重要です。
・ __wrap_xxx を 自分で定義
・ __real_xxx は extern 宣言だけ
・ 必要なら テスト用フックで挙動を切り替える
🟦 フック構造体(テストから差し替える窓口)
// tests/wrap_capi.h
#pragma once
#include <iconv.h>
#include <cstdarg>
struct IconvHook {
iconv_t (*open)(const char* to, const char* from) = nullptr;
size_t (*conv)(iconv_t cd, char** inbuf, size_t* inbytes,
char** outbuf, size_t* outbytes) = nullptr;
int (*close)(iconv_t cd) = nullptr;
};
struct SyslogHook {
void (*syslog_v)(int pri, const char* fmt, va_list ap) = nullptr;
};
extern IconvHook g_iconv_hook;
extern SyslogHook g_syslog_hook;
👉
ここでは 関数ポインタだけを持つ構造体を用意し、
テスト側から差し替えられるようにしています。
🟦 wrap 実装(iconv / syslog の呼び出しを横取りする)
// tests/wrap_capi.cpp
#include "wrap_capi.h"
#include <syslog.h>
#include <cstdio>
IconvHook g_iconv_hook;
SyslogHook g_syslog_hook;
// 本物(リンカが用意する)
extern "C" {
iconv_t __real_iconv_open(const char* to, const char* from);
size_t __real_iconv(iconv_t cd, char** inbuf, size_t* inbytes,
char** outbuf, size_t* outbytes);
int __real_iconv_close(iconv_t cd);
void __real_syslog(int pri, const char* fmt, ...);
}
extern "C" {
iconv_t __wrap_iconv_open(const char* to, const char* from) {
if (g_iconv_hook.open) return g_iconv_hook.open(to, from);
return __real_iconv_open(to, from);
}
size_t __wrap_iconv(iconv_t cd, char** inbuf, size_t* inbytes,
char** outbuf, size_t* outbytes) {
if (g_iconv_hook.conv)
return g_iconv_hook.conv(cd, inbuf, inbytes, outbuf, outbytes);
return __real_iconv(cd, inbuf, inbytes, outbuf, outbytes);
}
int __wrap_iconv_close(iconv_t cd) {
if (g_iconv_hook.close) return g_iconv_hook.close(cd);
return __real_iconv_close(cd);
}
// syslog は可変長引数なので、必要なら wrap 側で文字列化して扱う
void __wrap_syslog(int pri, const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
// テスト用フックがあれば、そちらへ渡す(本物の syslog は呼ばない)
if (g_syslog_hook.syslog_v) {
g_syslog_hook.syslog_v(pri, fmt, ap);
va_end(ap);
return;
}
// フックがない場合だけ、本物へフォールバック
char buf[1024];
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
__real_syslog(pri, "%s", buf);
}
} // extern "C"
重要な考え方
✔ フックが設定されていれば テスト用処理
✔ 未設定なら 必ず本物にフォールバック
✔ 本番挙動を壊さない設計
🟦 方針の使い分け(ここが混乱ポイントなので明記)
このメモでは、用途に応じて 2 つの方針を紹介します。
【方針A】iconv / syslog を両方 Mock 化(環境に依存せず完全に制御したい)
【方針B】正常系は本物 iconv、syslog だけフェイク(実環境で動作確認したい)
以降、両方の例を載せます。
🟦 方針A:gmock で iconv / syslog を両方 Mock にする
🟦 モックをつなぐための準備(Test 側の構造)
ここまでで、
--wrap により iconv / syslog の呼び出し自体は横取りできる
フック構造体 (IconvHook / SyslogHook) を介してテストから挙動を差し替えられる
ところまで整いました。
次に必要なのは、
👉 C の関数ポインタと、C++ の Mock(gmock)をつなぐ「橋渡し」
です。
🟦 なぜ「橋渡し」が必要なのか?
__wrap_iconv_open などは C 関数です。
iconv_t __wrap_iconv_open(const char* to, const char* from);
一方、gmock は C++ のクラスメソッドです。
MOCK_METHOD(iconv_t, open, (const char*, const char*));
👉
そのままでは 直接つなげられないため、
・ テスト専用の グローバルポインタ
・ それを呼び出す static 関数
を用意して橋渡しします。
🟦 Mock クラス定義(iconv / syslog)
#include <gmock/gmock.h>
class MockIconv {
public:
MOCK_METHOD(iconv_t, open, (const char*, const char*));
MOCK_METHOD(size_t, conv,
(iconv_t, char**, size_t*, char**, size_t*));
MOCK_METHOD(int, close, (iconv_t));
};
class MockSyslog {
public:
// 可変長引数の syslog は wrap 側で文字列化して受ける想定
MOCK_METHOD(void, syslogV,
(int, const char*, const std::string&));
};
🟦 テスト専用グローバル(C 関数から呼ぶため)
static MockIconv* g_mock_iconv = nullptr;
static MockSyslog* g_mock_syslog = nullptr;
👉 __wrap_xxx は C 関数なので this ポインタを持てません。
そのため、Test Fixture の SetUp() で代入し、TearDown() で解除します。
🟦 フック用の static 関数(橋渡し)
static iconv_t HookOpen(const char* to, const char* from) {
return g_mock_iconv->open(to, from);
}
static size_t HookConv(iconv_t cd, char** inbuf, size_t* inbytes,
char** outbuf, size_t* outbytes) {
return g_mock_iconv->conv(cd, inbuf, inbytes, outbuf, outbytes);
}
static int HookClose(iconv_t cd) {
return g_mock_iconv->close(cd);
}
static void HookSyslogV(int pri, const char* fmt, va_list ap) {
char buf[1024];
vsnprintf(buf, sizeof(buf), fmt, ap);
g_mock_syslog->syslogV(pri, fmt, std::string(buf));
}
🟦 Test Fixture でフックを接続する(方針A)
class EncodingConverterTest : public ::testing::Test {
protected:
MockIconv iconv;
MockSyslog syslog;
void SetUp() override {
g_mock_iconv = &iconv;
g_mock_syslog = &syslog;
g_iconv_hook.open = &HookOpen;
g_iconv_hook.conv = &HookConv;
g_iconv_hook.close = &HookClose;
g_syslog_hook.syslog_v = &HookSyslogV;
}
void TearDown() override {
g_iconv_hook = {};
g_syslog_hook = {};
g_mock_iconv = nullptr;
g_mock_syslog = nullptr;
}
};
SetUp / TearDown の責務は「グローバル状態の支配」です。
・ テスト開始時に必ずフックを設定
・ テスト終了時に必ず元に戻す
・ テスト間の汚染を防止
🟦 方針B:正常系は本物 iconv、syslog だけフェイクにする
目的:
正常系で「実環境の iconv が動くこと」を確認したい
syslog は副作用なので、テストでは常にフェイクで受ける
🟦 Test Fixture(方針B:syslog だけ差し替える)
class EncodingConverterRealIconvTest : public ::testing::Test {
protected:
static inline int syslog_calls = 0;
static void FakeSyslogV(int /*pri*/, const char* /*fmt*/, va_list /*ap*/) {
// 本物の syslog は呼ばず、呼ばれた事実だけ記録
++syslog_calls;
}
void SetUp() override {
// iconv は差し替えない(=本物にフォールバック)
g_iconv_hook = {};
// syslog だけフェイク
syslog_calls = 0;
g_syslog_hook.syslog_v = &FakeSyslogV;
}
void TearDown() override {
g_iconv_hook = {};
g_syslog_hook = {};
}
};
✅ 同じテストファイルに追記するコード例(Test Fixture は 1つだけ)
// 方針:
// - syslog は常にフェイク(副作用を出さない&呼ばれた回数を記録)
// - iconv は基本「本物」(g_iconv_hook 未設定=__real_* にフォールバック)
// - 異常系だけ、失敗させたい関数だけを g_iconv_hook で一時的に差し替える
//
// これにより、以下が可能:
// - iconv_open だけ失敗(偽物)
// - iconv_open 成功(本物)、iconv だけ失敗(偽物)
// - iconv_open 成功(本物)、iconv 成功(本物)、iconv_close だけ失敗(偽物)
class EncodingConverterRealIconvTest : public ::testing::Test {
protected:
// syslog が呼ばれた回数(フェイクの記録)
static inline int syslog_calls = 0;
static void FakeSyslogV(int /*pri*/, const char* /*fmt*/, va_list /*ap*/) {
++syslog_calls; // 本物syslogは呼ばない(副作用を消す)
}
// 差し替えたフックをテスト終了時に確実に戻すための退避領域
IconvHook saved_iconv_hook{};
void SetUp() override {
// syslog は常にフェイク
syslog_calls = 0;
g_syslog_hook.syslog_v = &FakeSyslogV;
// iconv は原則「本物」→ フック未設定で __real_* にフォールバック
saved_iconv_hook = g_iconv_hook;
g_iconv_hook = {};
}
void TearDown() override {
// テスト汚染防止(必ず元に戻す)
g_iconv_hook = saved_iconv_hook;
g_syslog_hook = {};
}
// ここから下が「必要なときだけ」偽物にするためのヘルパ
void FakeIconvOpenFail() {
g_iconv_hook.open = [](const char*, const char*) -> iconv_t {
return (iconv_t)-1; // iconv_open 失敗
};
}
void FakeIconvFail() {
g_iconv_hook.conv = [](iconv_t, char**, size_t*, char**, size_t*) -> size_t {
return (size_t)-1; // iconv 失敗(典型的には (size_t)-1)
};
}
void FakeIconvCloseFail() {
g_iconv_hook.close = [](iconv_t) -> int {
return -1; // iconv_close 失敗
};
}
};
🟦 テスト例①:正常系(iconv 成功)※本物iconvを使う
TEST_F(EncodingConverterRealIconvTest, ConvertSjis_Success_RealIconv)
{
EncodingConverter conv;
// iconv_open/iconv/iconv_close は本物が呼ばれる
EXPECT_NO_THROW({
(void)conv.convert(DataType::Sjis, "test");
});
// 正常系なのでエラーログが出ない想定なら 0
EXPECT_EQ(syslog_calls, 0);
}
🟦 テスト例②:異常系(iconv_open 失敗)※ここだけフェイクで失敗を作る
✅ 同じテストファイルに追記するコード例
TEST_F(EncodingConverterRealIconvTest, IconvOpenFail_Throws)
{
EncodingConverter conv;
// iconv_open だけ偽物にして失敗させる
FakeIconvOpenFail();
EXPECT_THROW(
conv.convert(DataType::Sjis, "test"),
std::runtime_error
);
// 失敗時にログを出す設計なら、ここは 1以上 を期待に変える
// EXPECT_GE(syslog_calls, 1);
}
🟦 追加例:iconv_open 成功(本物)、iconv 失敗(偽物)
TEST_F(EncodingConverterRealIconvTest, IconvConvFail_Throws)
{
EncodingConverter conv;
// iconv(変換)だけ偽物にして失敗させる
FakeIconvFail();
EXPECT_THROW(
conv.convert(DataType::Sjis, "test"),
std::runtime_error
);
// 失敗時ログを出す設計なら
// EXPECT_GE(syslog_calls, 1);
}
🟦 追加例:iconv_open 成功(本物)、iconv 成功(本物)、iconv_close 失敗(偽物)
TEST_F(EncodingConverterRealIconvTest, IconvCloseFail_Throws)
{
EncodingConverter conv;
// close だけ偽物にして失敗させる
FakeIconvCloseFail();
EXPECT_THROW(
conv.convert(DataType::Sjis, "test"),
std::runtime_error
);
// 失敗時ログを出す設計なら
// EXPECT_GE(syslog_calls, 1);
}
こうしておくと何が嬉しい?
・ Fixture は 1つだけ(重複なし)
・ 正常系は iconv を一切いじらない(=本物)
・ 異常系は「失敗させたい関数だけ」を FakeIconvXxx() で差し替えるだけ
・ TearDown() が必ず元に戻すので テスト汚染しない
🌟iconv の失敗時に errno をセットしたいケース(EILSEQ など)
conv() が失敗したときに errno をセットして挙動を作りたいなら、
フェイク側で errno に値を入れてから (size_t)-1 を返すだけです。
(iconv の仕様上、失敗は (size_t)-1、理由は errno で見るのが基本)
よく使う errno はこのあたり:
・ E2BIG : 出力バッファが足りない
・ EILSEQ: 不正なバイト列(入力が壊れてる等)
・ EINVAL: 入力が途中で切れている(マルチバイトの途中で終わった等)
- フェイクで errno をセットする(conv 用)
Fixture に「errno 付き失敗」を作るヘルパを追加します。
#include <cerrno> // errno
// iconv(conv) を errno 付きで失敗させる
void FakeIconvFailWithErrno(int e) {
g_iconv_hook.conv = [e](iconv_t, char**, size_t*, char**, size_t*) -> size_t {
errno = e; // ★ここがポイント:失敗理由を設定
return (size_t)-1; // iconv の失敗戻り値
};
}
- テスト例:EILSEQ(不正シーケンス)で失敗させる
「例外を投げる」設計なら EXPECT_THROW、さらに例外メッセージに errno を入れてるなら中身も確認できます。
TEST_F(EncodingConverterRealIconvTest, IconvFail_EILSEQ_Throws)
{
EncodingConverter conv;
FakeIconvFailWithErrno(EILSEQ);
try {
(void)conv.convert(DataType::Sjis, "test");
FAIL() << "Expected std::runtime_error";
} catch (const std::runtime_error& e) {
// 実装が errno をログ/メッセージに含めるなら、ここで確認できる
// EXPECT_THAT(std::string(e.what()), testing::HasSubstr("EILSEQ"));
}
}
- テスト例:E2BIG(出力バッファ不足)で失敗させる
同様に:
TEST_F(EncodingConverterRealIconvTest, IconvFail_E2BIG_Throws)
{
EncodingConverter conv;
FakeIconvFailWithErrno(E2BIG);
EXPECT_THROW(
(void)conv.convert(DataType::Sjis, "test"),
std::runtime_error
);
}
- 注意点(地味に重要)
・ errno は スレッドローカルですが、他の呼び出しで上書きされうるので、
実装側が iconv() 失敗直後に errno を読んで保存している前提だと
テストが安定します。
・ テスト側で errno の値を直接 EXPECT_EQ(errno, EILSEQ) みたいに見るのは、
convert() の中で別の libc 呼び出しが走ると変わることがあるので、
例外/ログに反映されているかを見る方が堅いです。
・ もし「失敗時はリトライして2回 iconv() を呼ぶ」みたいな実装なら、
フェイクも呼ばれる回数に合わせて作る必要があります
(static inline int count で分岐など)。
💠C++まとめ💠
💠Cまとめ💠