Specs2 の Mockito でデフォルト引数つきのメソッドの動作定義がメソッドの呼び出し回数にカウントされる。
経緯
Scala でモックを使用したテストコードを書くときにに Mockito を使っている。Mockitoは大変便利で、 Specs2 で使う場合は以下のように簡単にメソッドの動作を定義できる。
val mockedHoge = mock[Hoge] val bar: Argument = new Argument() val result: Result = new Result() mockedHoge.foo(bar) returns result // Write a test with mockedHoge there was one(mockedHoge).foo(bar)
これはHoge
クラスをモックにして、Hoge#foo
が引数bar
で呼び出されたとき、result
を返すモックを作成している。また、テスト後にmockedHoge.foo(bar)
が一度だけ呼ばれていることを確認するテストである。とても簡単に書けて良い。
問題点
しかしデフォルト引数が入ると話がややこしくなる。以下のコードは適切な引数を受け取ると、そこからエンティティを作成し expire time つきのレポジトリに登録するサービスクラスである。
- EntityRegisterService.scala
package org.hal.stand import scala.concurrent.Future case class ExampleEntityId(value: Int) case class ExampleEntity(id: ExampleEntityId) class ExampleRepository { def set(entity: ExampleEntity, expiresInSecond: Int = 600): Future[Unit] = { Future.successful() } } class EntityRegisterService(repository: ExampleRepository) { def register(id: Int): Future[Unit] = { // 今回は引数で与えているが本来は何らかの処理で ID とエンティティを生成する。 val entityId = ExampleEntityId(id) val entity = ExampleEntity(entityId) repository.set(entity) } }
以上のサービスクラスのテストを以下のように書く。
- EntityRegisterServiceSpec.scala
package org.hal.stand import org.specs2.mock.Mockito import org.specs2.mutable.Specification import scala.concurrent._ import scala.concurrent.duration._ class EntityRegisterServiceSpec extends Specification with Mockito { val fakeExampleEntityId = ExampleEntityId(1234) val fakeEntity = ExampleEntity(fakeExampleEntityId) "register" should { "set the entity with the ID specified by the argument to the repository" in { val mockedRepository = mock[ExampleRepository] mockedRepository.set(fakeEntity) returns Future.successful() val service = new EntityRegisterService(mockedRepository) val result = service.register(fakeExampleEntityId.value) Await.result(result, Duration(100, MILLISECONDS)) mustEqual () there was one(mockedRepository).set(fakeEntity) } } }
一見これでうまく行きそうだがうまくいかない。以下の様なエラーが吐かれる。
[info] register should [info] x set the entity with the ID specified by the argument to the repository [error] The mock was not called as expected: [error] exampleRepository.set$default$2(); [error] Wanted 1 time: [error] -> at org.hal.stand.EntityRegisterServiceSpec$$anonfun$1$$anonfun$apply$3$$anonfun$apply$5.apply(MockitoTrapSpec.scala:20) [error] But was 2 times. Undesired invocation: [error] -> at org.hal.stand.EntityRegisterService.register(MockitoTrap.scala:19) (MockitoTrapSpec.scala:20)
どうもモックの動作定義時に一度メソッドが呼び出されたことになるらしく二回呼び出しされてエラーになってしまう。
解決方法
デフォルト引数やめよう。
- EntityRegisterService.scala
package org.hal.stand import scala.concurrent.Future case class ExampleEntityId(value: Int) case class ExampleEntity(id: ExampleEntityId) class ExampleRepository { def set(entity: ExampleEntity, expiresInSecond: Int): Future[Unit] = { Future.successful() } } class EntityRegisterService(repository: ExampleRepository) { final private val expireInSecond = 600 def register(id: Int): Future[Unit] = { // 今回は引数で与えているが本来は何らかの処理で ID とエンティティを生成する。 val entityId = ExampleEntityId(id) val entity = ExampleEntity(entityId) repository.set(entity, expireInSecond) } }
- EntityRegisterServiceSpec.scala
package org.hal.stand import org.specs2.mock.Mockito import org.specs2.mutable.Specification import scala.concurrent._ import scala.concurrent.duration._ class EntityRegisterServiceSpec extends Specification with Mockito { val fakeExampleEntityId = ExampleEntityId(1234) val fakeEntity = ExampleEntity(fakeExampleEntityId) "register" should { "set the entity with the ID specified by the argument to the repository" in { val mockedRepository = mock[ExampleRepository] mockedRepository.set(fakeEntity, 600) returns Future.successful() val service = new EntityRegisterService(mockedRepository) val result = service.register(fakeExampleEntityId.value) Await.result(result, Duration(100, MILLISECONDS)) mustEqual () there was one(mockedRepository, 600).set(fakeEntity) } } }
今回のはサービス層で与えるべき情報がレポジトリ層でデフォルト引数で与えられていて例が大変悪いけど、呼び出し回数がうまくカウントできずに詰んだので備忘録代わりに。
libsndfile + QIODevice で WAVE ファイルの読み書き
経緯
趣味で音声合成っぽいソフトウェアを作っている。いい加減音声ファイルの読み書き部を作らねばならなくなったのだが、C++ WAVE 読み込み 無料
と Google 先生に聞いてもマッチョな結果しか返ってこない。これはこれで興味はあるが、音声合成のドメインに信号列の処理は含まれても信号の入出力自体は含まれないのでもっと手軽に読み書きしたい。
なので OSS 、ただし GPL は除く、で良いライブラリがあるのが理想である。と言うかなんでみんなフォーマットとバイナリ読むことばっかに興味あるんだ。普通にいろいろ簡単に読み込めるライブラリをくれよ。
libsndfile インストール
いくつか探したのだが libsndfile が良さそう。ライセンスは LGPL で配布はバイナリとソースコード。以下からダウンロードできる。
- mega-nerd - libsndfile http://www.mega-nerd.com/libsndfile/
Windows のバイナリは VC++ 用なので MINGW 使いはソースコードのコンパイルをしないといけない。
MINGW + Windows 7 の組み合わせでは MSYS からインストールすれば良い。
適当に MSYS のコンソールから伝統的な./configure && make && make install
でコンパイルしてインストールできるので、 Windows でも簡単に使える。
libsndfile の使い方
普通に使う分には以下のブログ記事が役に立つ。
読んでいただければ分かるが以下のように読み込みを行う。
SndfileHandle sndfile("sample.wav", SFM_READ); sndfile.samplerate(); // サンプリング周波数 sndfile.channels(); // チャンネル数 short int* input = new short int [sndfile.frames()]; sndfile.readf(input, sndfile.frames()); // 16bit 符号付き整数の信号
ファイルから読み込むのであればこれだけで多くのフォーマットに対応できる。ライセンスも良い感じなのだが、しかしながら基本的なインタフェースはファイルしか無い。別にそれほど不自由はないが、できればQIODevice
のようなインタフェースから読める方が拡張性が高いし安心できる。
QIODevice からファイルを読み込む。
libsndfile 的にはQIODevice
からの読み込みは仮想ファイルからの読み込みという扱いになる。この機能は残念ながら C++ 版の libsndfile にはなく、 C 版のインタフェースを使うしか無い。
SF_INFO info{0, 0, 0, 0, 0, 0}; // ファイルの情報が入る構造体 SF_VIRTUAL_IO io = // 仮想 IO のコールバック関数の設定 { callbackLength, callbackSeek, callbackRead, callbackWrite, callbackTell }; QIODevice device; device.open(QIODevice::ReadOnly); // somehow open the device! SNDFILE *file = sf_open_virtual(&io, SFM_READ, &info, &data);
読み込む部分はこれだけで良い。ただしsf_open_virtual
を使うためにはSF_VIRTUAL_IO
に記述されたコールバック関数を実装せねばならない。すなわち以下の部分になる。
sf_count_t callbackLength(void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->bytesAvailable(); } sf_count_t callbackSeek(sf_count_t offset, int whence, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; switch(whence) { case SEEK_CUR: device->seek(device->pos() + offset); break; case SEEK_SET: device->seek(offset); break; case SEEK_END: device->seek(offset + callbackLength(voidDevice)); break; } return device->pos(); } sf_count_t callbackRead(void *ptr, sf_count_t count, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->read((char *)ptr, count); } sf_count_t callbackWrite(const void *ptr, sf_count_t count, void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->write((const char *)ptr, count); } sf_count_t callbackTell(void *voidDevice) { QIODevice *device = (QIODevice *)voidDevice; return device->pos(); }
ただしこのやり方はストリーミングは一切考慮していない。とはいえQIODevice
から読み込むことができるのでそれなりに恩恵がありそう。あとは適当にインタフェースを整えてやればよい。僕の実装は以下にあるのでご参考までに。
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
これで複数のテストを同時に実行できる。とても便利。