Qt の JSON 機能を少し使ってみた。
今趣味で書いているプログラムの保存形式をどうしようか悩んでいたのだが、 XML は前使ってみたとき煩雑だったしもう少し簡単な形式はないものかとネットを見ていたら、どうも Qt5.0 以降から JSON をサポートしているらしい。
- QtCore 5.0: JSON Support in Qt
ということでこれを使うのが良いだろうということで使ってみた。
#include <QJsonObject> #include <QJsonValue> class Phoneme { public: QString filename; double msOffset; double msPreutterance; QJsonValue toJson() const { QJsonObject object; object["filename"] = filename; object["ms_offset"] = msOffset; object["ms_preutterance"] = msPreutterance; return QJsonValue(object); } };
これは便利すぎる。でもこれだと JSON の形式を Phoneme
オブジェクトが知らねばならないし、
そもそも Phoneme
がこの形式の JSON にのみ対応するとは限らないので以下の様なクラスを用意する。
class JsonSerializer { public: template<class T> static QJsonValue toJson(const T &t) { QJsonValue value; value << t; return value; } };
あとは <<
演算子をグローバルでオーバーロードしさえすればよいので、クラス内で toJson
を用意する必要はない。
QJonValue oprator <<(QJsonValue &left, const Phoneme &right) { QJsonObject object; object["filename"] = right.filename; object["ms_offset"] = right.msOffset; object["ms_preutterance"] = right.msPreutterance; return (left = QJsonValue(object)); } int main() { QJsonValue json(JsonSerializer::toJson(phoneme)); return 0; }
これで良い。あとは QJsonDocument
を使って Qt の JSON 系クラスと文字列を行ったり来たりできそう。
Google Mock で演算子オーバーロードしたクラスのモックを作る。
経緯
以前書いたGoogle Mock を導入してみた。に見るように 普通の仮想メンバ関数のモッキングは大変簡単なのだが、演算子のオーバーロードに対してはモッキングができないのかやり方を調べてみた。
問題
単純に仮想関数をモッキングするだけであれば、MOCK_METHOD*
を書けば良いだけなのだが以下の様なコードは許容されなかった。
どうやら単純にはできないらしい。
class MyList { public: virtual ~MyList(){ } virtual MyElement &operator[](int index) = 0; }; class MockedMyList : public MyList { public: MOCK_METHOD1(operator[], MyElement &(int index)); // こう書くことはできない。 };
解決法
operator[]
はオーバーライドし、モッキングしたメンバ関数に処理を委譲すれば良い。
すなわち以下のように定義する。
class MockedMyList : public MyList { public: MOCK_METHOD1(subscript, MyElement &(int index)); MyElement &operator[](int index) { return subscript(index); } };
あとはsubscript
の動作を定義すれば良い。
参考資料
http://stackoverflow.com/questions/6492664/how-to-create-a-mock-class-with-operator
Google Mock で参照を返す。
経緯
つい先程テストを書いていて、参照を返すタイプのメソッドをモッキングする際に失敗をしたのでメモしておく。
問題
こんな感じのクラスをモック化しようとする。
class MyClass { public: virtual ~MyClass(){ } virtual const Property &property() const; };
当然以下のようにモックを作る。
class MockMyClass : public MyClass { public: MOCK_CONST_METHOD0(proeprty, const Property &()); }
そして以下のようにモックを定義する。
MockMyClass mock; Property retval; EXPECT_CALL(mock, property()) .Times(1) .WillOnce(Return(retval));
一見うまく行きそうなのだが以下のようなエラーに遭遇する。
gmock/include/gmock/gmock-actions.h:498:5: error: size of array is negative GTEST_COMPILE_ASSERT_(!internal::is_reference<Result>::value, ^
C++ のコンパイルエラーはなかなか趣深いが何を言っているか分からなくて困る。Google 先生に聞いてみたら以下の解決策を見つけた。
解決策
参照を返すには ReturnRef
を使えばよいらしい。以下のようにして解決。
MockMyClass mock; Property reval; EXPECT_CALL(mock, property()) .Times(1) .WillOnce(ReturnRef(retval));
参考資料
https://groups.google.com/forum/#!topic/googlemock/UM6dgKz3rQQ
UTAU 音源の仕様
経緯
UTAU 音源を読み込むプログラムを何回か書いているのだが、都度都度調べなおしているのが面倒になったので記事にまとめる。音源やプラグインを作るための情報ではなく、プログラムから音源を利用する際に必要な情報であることに注意。
構成
音源はディレクトリに含まれる以下のファイルからなる。波形ファイル以外のファイルは全てオプショナルなもので存在しなくても良い。ディレクトリ内に含まれる波形ファイルは、サブディレクトリ内まで再帰的に読み込まれる。サブディレクトリまで読み込まれる。(@namiyome さん、@maruLoop さんからご指摘がありました。ありがとうございます。)
- /root/
- oto.ini
- 波形のラベリング情報。
- character.txt
- キャラクター情報
- prefix.map
- 音高によるディレクトリ指定とサフィックス指定。
- subdirs/
- oto.ini
- oto.ini
oto.ini
音の切り出しとラベリング情報、および配置位置の情報が含まれる。形式は以下。改行することで複数の指定が可能である。時間指定は浮動小数点で単位はミリ秒。
ファイル名=エイリアス,左ブランク,固定長,右ブランク,先行発声,オーバラップ
記述例は以下。
_ああいあうえあ.wav=- あ,500,125,-500,500,250
ファイル名の前にはアンダースコアがつけられることが多い。これはエイリアスによる検索性をよくするためか。
それぞれの値の意味合いは検索してください。
character.txt
UTAU 音源のキャラクター情報。形式は以下。ただし日本語名で設定している音源も少なくないが、当面面倒がないのでこの形にしておく。
name=音源名 image=音源アイコン名 sample=サンプルボイスへのファイルパス author=製作者 web=製作者の web サイト
= 前後のスペースが無視されるかは未確認。
prefix.map
音の高さと、読み替え先の情報のペア。読み替え先はディレクトリ名とサフィックスのペア。以下の形式。
音高名\tディレクトリ名\tサフィックス
設定例は以下。
C5\t↑\t↑
音高はノート名とオクターブの組。ノート名は以下から選択する。
C, C#, D, D#, E, F, F#, G, G#, A, A#, B
Qt で複数のテストを同時に実行する。
経緯
Qt には QTest というテスト用ライブラリがついてくる。
- QTestLib Manual
が、しかしデフォルトだと1プロジェクトに対して1テストだけしか実行できない。 Jenkins に Qt 用のプラグインあるので別に CI していれば関係なくね、とも言えるが手元でテストするのにあまり優しくない。結局手元で実行するならテストはまとまっていて一回で済んだ方が嬉しいので、一つにまとめる方法があればそれにこしたことはない。
やり方
そんなわけでややこしい Qt のテストライブラリだが、頑張ればまとめることができる。結局テスト用の QObject の private slots を叩きまくるだけなので、 QObject の列がありさえすればテストの実行はできる。そこでこんなソースコードを用意する。
#ifndef AUTOTEST_H #define AUTOTEST_H #include <QTest> #include <QHash> #include <QString> #include <QSharedPointer> namespace QAutoTest { QHash<QString, QObject *> &tests(); int run(int argc, char *argv[]); } template <class T> class Test { public: QSharedPointer<T> test; Test(const QString &name) : test(new T) { if(!QAutoTest::tests().contains(name)) { QAutoTest::tests()[name] = test.data(); } } }; #define DECLARE_TEST(className) static Test<className> t(#className); #endif // AUTOTEST_H
AutoTest.h
#include "AutoTest.h" QHash<QString, QObject *> &QAutoTest::tests() { static QHash<QString, QObject *> t; return t; } int QAutoTest::run(int argc, char *argv[]) { int ret = 0; QList<QString> failureTests; foreach(QObject* test, tests().values()) { int result = QTest::qExec(test, argc, argv); if(result) { failureTests.append(tests().key(test)); } ret |= result; } if(ret == 0) { printf("[success] All test succeeded!\n"); } else { printf("[error] %d test(s) failed.\n", failureTests.size()); foreach(const QString &testName, failureTests) { printf("[error] Test failed in: %s\n", testName.toLocal8Bit().data()); } } return ret; }
AutoTest.cpp
#include "AutoTest.h" int main(int argc, char *argv[]) { return QAutoTest::run(argc, argv); }
main.cpp
後はテストしたいクラスを適当に作って DECLARE_TEST でクラス名を与えればいい。以下の様な感じ。
#ifndef __SAMPLE_TEST__ #define __SAMPLE_TEST__ #include <QTest.h> #include "AutoTest.h" class SampleTest : public QObject { Q_OBJECT private slots: void test() { // do your tests here! } } DECLARE_TEST(SampleTest) #endif // __SAMPLE_TEST
SampleTest.h
これで複数のテストを同時に実行できる。とても便利。
Google Mock を導入してみた。
経緯
ようやく余裕ができてきたのか家でときどきコードを書いている。
最近色んな書き方を覚えてきたので、せっかくだし書き方を試してみようと思って、C++ とQtを使ってこんなコードを書いた。
下手くそな英語は放っておいて欲しいが、このクラスはコンストラクタから与えられた音素片のレポジトリと、波形のレポジトリと、音声の分析器を使って、特定条件の音声1フレーム分の分析結果を返すレポジトリクラスである(今思ったが Repository じゃなくて Service だろこれ、後で直そう)。コンストラクタから必要なレポジトリと分析アルゴリズムの実装を注入できるようにしてある。以前作っていたコードはテストの書けない形の実装が多く、二年程度でコードが腐ってしまったためテストの書けるコードにしておきたかった。
ともあれこれでモック実装を注入すれば簡単にテストができる、とQtのテスト用モッキングライブラリを探したもののどうも無さそう。テスト用の実装をいちいち書くのは馬鹿らしいので C++ 用のモッキングライブラリ Google Mock を導入してみた。
インストール
今回は Windows 7 に MinGW 4.8 の環境でインストールを行った。MinGW の bin にパスを通しておく必要がある。
- 以下のアドレスから Google Mock をダウンロードして解凍、適当な位置に配置する。
- Google Mock のルートディレクトリを GMOCK_DIR, ${GMOCK_DIR}/gtest をGTEST_DIR として以下のコマンドを実行する。
g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} -pthread -c ${GTEST_DIR}/src/gtest-all.cc g++ -isystem ${GTEST_DIR}/include -I${GTEST_DIR} -isystem ${GMOCK_DIR}/include -I${GMOCK_DIR} -pthread -c ${GMOCK_DIR}/src/gmock-all.cc ar -rv libgmock.a gtest-all.o gmock-all.o
- 好み次第だが以下の環境変数に、GMOCK_DIR, GTEST_DIR のパスを通すと便利感はある。
- C_INCLUDE_PATH
- C_PLUS_INCLUDE_PATH
- LIBRARY_PATH
- Qt であれば
LIBS += -lgmock
をつけて MinGW のオプションを追加すれば良い。
使い方
インストールはこれで完了。あとはテストコードの main 関数に Google Mock を使用する際の初期化を書く。これでテスト内で Google Mock が使用できる。
#include "gmock/gmock.h" #include "gtest/gtest.h" #include "AutoTest.h" int main(int argc, char *argv[]) { ::testing::GTEST_FLAG(throw_on_failure) = true; ::testing::InitGoogleMock(&argc, argv); return QAutoTest::run(argc, argv); }
最後にモックを作るのだが、モック化したクラスは結局自前で定義する必要はある。自動化するスクリプトもあるのだが今回は使用していないので以下のようにモッククラスを定義した。
class MockPhonemeRepository : public ResourceRepository<PhonemeKey, Phoneme> { public: MOCK_CONST_METHOD1_T(find, const QSharedPointer<Phoneme>(const PhonemeKey &key)); MOCK_CONST_METHOD1_T(contains, bool(const PhonemeKey &key)); MOCK_METHOD2_T(add, bool(const PhonemeKey &key, QSharedPointer<Phoneme> value)); MOCK_METHOD1_T(remove, void(const PhonemeKey &key)); };
MOCK_METHOD*
で仮想メンバ関数の挙動を制御できるようにしてくれる。テンプレートのついた引数がある場合は、MOCK_METHOD*_T
を使い、const メンバ関数であればMOCK_CONST_METHOD*
を使うなど細かい違いはあるが概ねこんな感じでモック化する関数を定義しておくとメンバ関数がモック化される。 * は引数の数で変わる、10個まで行ける。
実際にモック化した関数の振る舞いは、以下のように指定する。
EXPECT_CALL(phonemeRepository, find(key)) .Times(1) .WillOnce(Return(phoneme));
find メソッドの呼び出しが1回、指定された key で行われる。その際一度だけ phoneme を返しなさい。くらいの指定で非常に便利に使える。この辺の使い方は良いドキュメントがあるので下記 URL を参考にするとよい。自前でテスト用のクラス実装するよりははるかに楽で、使い勝手もそこそこ良いように思う。