カテゴリー別アーカイブ: C++

コマンドラインでUTF-8テキストのBOMを追加したり削除したりする

BOM(バイト順マーク)を削除することはよくあるんだけど、つけたことってあまりなかった。
エディタの機能を使えばできるものもあるけど、大量なファイルをいちいちエディタで開いて処理するわけにもいかないので、コマンドでまとめて処理したい、そんなとき。
結論としては、uconvを使えば割と手軽にBOMを付けたり消したりできる。
「BOMのマークの文字なんだったっけ?」とか考えなくてもよい方法

BOMの確認

fileコマンドを使う。

$ file *.cpp
foo.cpp:   UTF-8 Unicode text
bar.cpp:   UTF-8 Unicode (with BOM) text

BOMが付いてるテキストは「(with BOM)」となるので、エディタなどで開かずにBOMが付いてるかどうかを確認できる。

現在のディレクトリ以下にあるC++のソースファイル(c,h,cpp,hpp)でBOMが付いてないファイルのファイル名を取り出すには

find . -type f \( -name '*.[ch]' -o -name '*.cpp' -o -name '*.hpp' \) -exec file {} + | grep -v "(with BOM)" | cut -d: -f1

BOMが付いてるファイル名を取り出すにはgrepの-vを取って、

find . -type f \( -name '*.[ch]' -o -name '*.cpp' -o -name '*.hpp' \) -exec file {} + | grep "(with BOM)" | cut -d: -f1


BOMの追加と削除

uconvを使う。
詳しくは、International Components for Unicode http://site.icu-project.org/
debianの場合最初から使えたけど、ない人はaptitudeでlibicu-dev辺りを入れればいいんだと思う。Windowsのバイナリもある。

BOMを追加する

uconv -f utf-8 -t utf-8 --add-signature foo.cpp > foo_bom.cpp

BOMを削除する

uconv -f utf-8 -t utf-8 --remove-signature bar.cpp > bar_nobom.cpp

出力ファイルに入力ファイルと同じ名前を指定して消してしまわないように注意。
-fと-tはプラットホームのエンコーディングがutf-8以外の場合は省略するとうまくいかないことがあるので明示してる。
localeコマンドを実行してLANG=ja_JP.utf8とかだったら-f,-tオプションは省略できるよ。

上のBOMの確認の例と組み合わせれば、お手軽にまとめて変換できるね。

なんでBOMつけようとか思ったの

Windowsにつけろって言われたから。

VC++はBOM付きUTF-8

UTF-8はバイト順に依らないので、本当はBOMは要らないのだけどWindowsのVisual Studio C++では、BOM付きじゃないとUTF-8で書かれてるかどうか判断できないらしい。
ソースにマルチバイト文字を含んでる場合にはコンパイル時にC4819という警告が出て、正しく動作しないことがある。

ソースコードにUTF-8を使う場合はBOMつきのみサポート。

gccはどっちでもよい

でもgccはBOMどうなの?と思ったらBOM付きで問題なくコンパイルできる。この辺で直ったようだ
BOM付けてはいけないみたいな文書を見かけるが、イマドキは意外と付いててもなんとかなる。

Visual Studio C++でCppUnitをビルドできない、そんなとき

CppUnitをWindowsでも使うことになってビルドを試みたのだけれど、結構ハマったので、ビルド方法をメモっておくよ。

WindowsでCppUnit使いたいのだけれど…

C++アプリケーションの効率的なテスト手法(CppUnit編) - @IT
ここを参考にMicrosoft Visual Studio VC++でビルドをしてみた。

  • VC++ 2010 Express プロジェクトファイル(.dsw)変換に失敗
  • VC++ 2008 Express リンクエラーでビルド失敗

となって、うまくいかない・・・
試行錯誤の結果、うまくビルドできたので以下手順を説明するよ。

準備

以下のものを用意する

VC++は別々のディレクトリにインストールされるので共存できるよ。

ビルド手順

cppunit-1.12.1.tar.gzはあらかじめ解凍ソフトなどで解凍しておく。

VC++ 2008でCppUnitTestMainプロジェクトを開く

VC++2008を起動
●「ファイル」→「開く」→「プロジェクト/ソリューション」
cppunit-1.12.1\examples\cppunittest\CppUnitTestMain.dswを選択
(cppunit-1.12.1\examples\cppunittest\CppUnitTestMain.dspでもよい)

プロジェクトの変換

●「変換してこのプロジェクトを開きますか?」と聞かれるので「すべてはい」

VC++2010じゃなくてVC++2008で使いたい人は→後述へ飛ぶ

プロジェクトの依存関係を外す

●「プロジェクト」→「プロジェクトの依存関係」
プロジェクト「CppUnitTestMain」を選択し、依存先のcppunit、cppunit_dllに入ってるチェックを外して「OK」

ソリューションファイルの保存

●ソリューションエクスプローラで
「ソリューション ‘CppUnitTestMain’ (3プロジェクト)」と書いてあるところを選択して、「ファイル」→「CppUnitTestMain.slnを保存」
ソリューションエクスプローラが見つからないときは「表示」→「ソリューションエクスプローラ」
●「ファイル」→「終了」でVC++2008の役目はこれでおしまい。

VC++ 2010でCppUnitTestMainプロジェクトを開く

VC++ 2010を起動
●「ファイル」→「開く」→「プロジェクト/ソリューション」
cppunit-1.12.1\examples\cppunittest\CppUnitTestMain.slnを選択

VisualStudio変換ウィザード

自動的にウィザードが起動する。基本的に全部そのまま「次へ」でOK

●バックアップ作成の選択「変換前にバックアップを作成する」を選択すると.oldをつけたバックアップが作成される。
再びVC++2008で開きたいときなどに必要になるよ。

●「変換の完了」が出たら「閉じる」。「ウィザードが閉じたとき変換ログを表示する」にチェックが入ってると変換レポートが表示される。

プロジェクトのプロパティ設定

cppunit,cppunit_dllプロジェクトについて確認すべき点は2点。

  • 「構成プロパティ」→「全般」→「ターゲット名
  • 「C/C++」→「コード生成」→「ランタイムライブラリ

これらに気をつけて各プロジェクトのプロパティを以下の表のように設定する。

プロジェクト 構成 ターゲット名 ランタイムライブラリ
cppunit Debug $(ProjectName)d マルチスレッドデバッグ(/MTd)
Release $(ProjectName) マルチスレッド(/MT)
cppunit_dll Debug cppunitd_dll マルチスレッドデバッグDLL(/MDd)
Release $(ProjectName) マルチスレッドDLL(/MD)
CppUnitTestMain Debug $(ProjectName) マルチスレッドデバッグ(/MTd)
Release マルチスレッド(/MT)
DebugDLL マルチスレッドデバッグDLL(/MDd)
ReleaseDLL マルチスレッドDLL(/MD)

プロジェクト依存関係の設定

「プロジェクト」→「プロジェクトの依存関係」でCppUnitTestMainプロジェクトを選択、
 依存先

  • cppunit
  • cppunit_dll

にチェックを入れてOK。

バッチビルドの実行

●「ツール」→「設定」→「上級者用の設定」を選択
メニューバーに「ビルド」項目が現れる
●「ビルド」→「バッチビルド」→「すべて選択」→「ビルド」
一度「クリーン」してから「ビルド」すると安心かもね。

DLL版はコンパイル中にC4251のwarningが結構出るけど特に問題ないと思う。
CppUnitTestMainがビルドできると、自動でテストの実行も行ってるよ。

●8つの全部が正常終了すればOK。

CppUnitを使ったプロジェクトを作るときにはCppUnitTestMainのプロパティを参考に設定するといいよ。

うまくいかない?

設定をもう一度見直してみよう。とくにランタイムライブラリの設定だね。
VC++2008でプロジェクトの依存関係を外さずにCppUnitTestMain.slnを保存してしまった場合は、
VC++2010でこれを開くと「プロジェクト」→「プロジェクトの依存関係」から編集ができなくなるよ。
CppUnitTestMainの「共通プロパティ」→「Frameworkと参照」ですべての「参照を削除」すれば編集できるようになるよ。

VC++ 2008でビルドしたいんだよ

って言う人は、上記手順のCppUnitTestMain.dswを開いた後、「プロジェクトの依存関係」は外さずに、
CppUnitTestMainのプロパティで
「リンカー」→「全般」→「リンクライブラリの依存関係」をすべての構成(Debug,Release,DebugDLL,ReleaseDLL)で「いいえ」に設定。
これをいいえにしないと、プロジェクト依存関係にあるcppunit,cppunit_dllのどちらもリンクしようとして競合して失敗する。

リンクライブラリの依存関係を「はい」のまま、プロジェクトの依存関係を外して、先にcppunitとcppunit_dllをビルドしておいて、CppUnitTestMainを単体でビルドするとうまくリンクできるはずだ。
あとは上のVC++ 2010のプロパティ設定の表のようにランタイムライブラリを適切に設定する。ターゲット名は修正しなくてよい。

(以下追記 2011/04/16)

ていうか、プロジェクトファイルくれよ

というリクエストがありました。ですよね。欲しいですよね。

cppunit-1.12.1 の Microsoft Visual Studio C++ 2010 Express用プロジェクトファイル

cppunittest-1.12.1-vcxproj.zip
↑このzipを展開すると、以下の7つのファイルが入ってるよ。

    examples\cppunittest
  • CppUnitTestMain.sln
  • CppUnitTestMain.vcxproj
  • CppUnitTestMain.vcxproj.filters
  • src\cppunit
  • cppunit.vcxproj
  • cppunit.vcxproj.filters
  • cppunit_dll.vcxproj
  • cppunit_dll.vcxproj.filters

この4つをcppunit-1.12.1以下の対応するディレクトリに置いてCppUnitTestMain.slnをダブルクリックで起動すればそのまま使えるはず。

ちなみにVisual Studio C++のProfessional版なら、上の手順みたく頑張らなくてもプロジェクトファイル変換できたよ…

必要なときだけ計算する

Xerces-C++でXMLからDOMツリー作ったりしてるんだけれど
最近パフォーマンス気にするようなコード書いてなかったので、非常に基礎的なところでハマってた。
テスト用の短いXMLだと問題なかったんだけど、本番用のXML食わせたらforの中では大したことやってないのに5分以上かかっちゃうわけ。

問題のコード

//...
DOMNodeList* nodelist = document->getElementsByTagName(tagname);
//...
for(int i=0;i< nodelist->getLength();i++){
    //....
}

ここだけ抜き出せばもうわかると思うけど、forの終了条件の中で nodelist->getLength() してる。こいつの計算コストがけっこう高い。
nodelist->getLength()をnodelist->getLength()回呼び出したらそりゃ遅い。
変数の名前考えるのが苦手なので、できるだけ変数宣言しないように書いてたらこうなったってのもある。

次のように書き直すべき

//...
DOMNodeList* nodelist = document->getElementsByTagName(tagname);
//...
for(int i=0,num=nodelist->getLength();i<num;i++){
    //....
}

今回作ってたケースでは、途中でリストのサイズが変わったりしないので最初に1回計算すればそれでよかったわけ。
だからforの初期条件のところで、あらかじめ計算するようにしたよ。
これだけで5分以上かかってたコードが1秒ちょっとで終わったよ。

ちなみに、Xercec-c++(2.8.0)のsrc/xercesc/dom/impl/DOMNodeListImpl.cppには、ご丁寧に

// this implementation is too stupid – needs a cache of some kind.

って書いてあるよ。

CppUnitを使ってみる

CppUnit

JavaのJUnitをC++に移植したのがCppUnitだよ。何そのJUnitって?
SmalltalkのSUnitをもとに作られたのがJUnitだよ。何そのSUnitって?
要は単体テスト(Unit Test)をうまいこと自動化してくれるC++用のフレームワークがCppUnit
XP(Extreme Programming)のテスト駆動開発(TDD)を考えているひとは是非取り入れたいものなんだよ。
よくわからないからまずは使ってみるよ。

インストール

debian環境でCppUnitを使うためにlibcppunitをインストール。

$ sudo aptitude install libcppunit-1.12.1 libcppunit-dev

libcppunit-1.12.1だけじゃなんかダメだったんでlibcppunit-devも。
必要ならlibcppunit-docも入れればいいと思うよ。

ドキュメント

CppUnit Documentation
一応ここにいろいろ書いてある。

Hello world

何はともあれCppUnitでテストしてみる。
An Hello, world program for CppUnit
にあるコードをHelloWorld.cppという名前で作成

//--- Hello, World! for CppUnit

#include <iostream>

#include <cppunit/TestRunner.h>
#include <cppunit/TestResult.h>
#include <cppunit/TestResultCollector.h>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/BriefTestProgressListener.h>
#include <cppunit/extensions/TestFactoryRegistry.h>

class Test : public CPPUNIT_NS::TestCase
{
  CPPUNIT_TEST_SUITE(Test);
  CPPUNIT_TEST(testHelloWorld);
  CPPUNIT_TEST_SUITE_END();

public:
  void setUp(void) {}
  void tearDown(void) {} 

protected:
  void testHelloWorld(void) { std::cout << "Hello, world!" << std::endl; }
};

CPPUNIT_TEST_SUITE_REGISTRATION(Test);

int main( int ac, char **av )
{
  //--- Create the event manager and test controller
  CPPUNIT_NS::TestResult controller;

  //--- Add a listener that colllects test result
  CPPUNIT_NS::TestResultCollector result;
  controller.addListener( &result );        

  //--- Add a listener that print dots as test run.
  CPPUNIT_NS::BriefTestProgressListener progress;
  controller.addListener( &progress );      

  //--- Add the top suite to the test runner
  CPPUNIT_NS::TestRunner runner;
  runner.addTest( CPPUNIT_NS::TestFactoryRegistry::getRegistry().makeTest() );
  runner.run( controller );

  return result.wasSuccessful() ? 0 : 1;
}

コンパイル

$ g++ -o HelloWorld HelloWorld.cpp -lcppunit 

-lcppunitがポイントね。

実行

$ ./HelloWorld
Test::testHelloWorldHello, world!
 : OK

上のやり方でコンパイルや実行がうまくいかない場合はインクルードパスとかライブラリパスを確認してね。

解説

あ、うん。OKだけど?でっていう?
一応解説をダラダラと書くよ。すげー読む気失せるよ。

CPPUNIT_TESTナントカっていうのがテストを簡単に書くためのマクロなんだけど、これを使うために
cppunit/extensions/HelperMacros.h
をincludeする。その他のcppunitのincludeはmainテスト実行部分のためのものだよ。

■クラス宣言

テストのためのクラスTestはCppUnitのTestCaseというクラスを継承して作るよ。
CPPUNIT_NSはCppUnitの名前空間、とはいっても実は
#define CPPUNIT_NS CppUnit
としてるだけだったりする。

CPPUNIT_TEST_SUITE(クラス名);
ここから一連のテストケース(テストスイート)を書くよーって言ってる。

CPPUNIT_TEST(メソッド名);
このメソッドのテストするよー宣言。上の例ではtestHelloWorldを宣言

CPPUNIT_TEST_END();
テストケースはここまでだよーって言ってる。上の例ではCPPUNIT_TEST(testHelloWorld);ひとつだけなんだけど、
他にもテストしたいメソッドがあれば、CPPUNIT_TEST_SUITE~CPPUNIT_TEST_ENDの間に並べて宣言しておけばいいよ。

setUp()tearDown()はテストケースの事前処理と事後処理をそれぞれ書けるよ。
ここでは”Hello, World!”表示するだけなので、特に何もしてないよ

protected:に続くtestHelloWorldがテストケースになるメソッド。CPPUNIT_TEST(メソッド名);で書いたメソッドを宣言する。
ここでは実装も合わせて書いてるけど、クラス宣言の外で実装してもOKだよ。

クラス宣言ここまで。

CPPUNIT_TEST_SUITE_REGISTRATION(テストスイート名);
で、以上のテストスイートを登録しておく。

■main
ここからテスト実施部分のmain。だけど例によって書くの面倒くさくなってきたからテキトー。
controllerがテストのコントローラー
テスト結果resultとテスト進捗progressにはそれぞれイベントを受け取るためのリスナをつけとく。
テストランナーrunnerにテストスイートに登録されてるテストを持ってきて、controllerをrunすれば、テストケースが実行されるわけ。
で、テストがうまくいってれば最後に0を返して、コケてたら1を返す。

単に”Hello, World!”出力してるだけなのでピンと来ないよね
そういう人は、C++界では言わずと知れたεπιστημη(えぴすてーめー)氏の
第2回 C++アプリケーションの効率的なテスト手法(CppUnit編)
を見ればいいと思うんだ。値が期待通りか確認するケースとか、NGになる場合のケースなんかも紹介してるよ。
というか最初からこれ見ればいいような気もするんだ。今さらだけど。

テスト結果をXML形式で出力

で、επιστημη氏のサンプルでは、最後にreturnする前に結果を標準出力に吐くコードが入ってる。

#include <cppunit/CompilerOutputter.h>
//...
// output test result
  CPPUNIT_NS::CompilerOutputter outputter( &result, CPPUNIT_NS::stdCOut() );
  outputter.write();
//...

これを少し改造してXML形式で出力するように変更。

#include <cppunit/XmlOutputter.h>
  //....
  // output test result
  std::ofstream ofs("result.xml");
  CPPUNIT_NS::XmlOutputter outputter(&result, ofs,"UTF-8");
  outputter.write();
  //....

テスト結果をカレントディレクトリにresult.xmlという名前のXMLを出力するよ。

<?xml version="1.0" encoding='UTF-8' standalone='yes' ?>
<TestRun>
  <FailedTests></FailedTests>
  <SuccessfulTests>
    <Test id="1">
      <Name>Test::testHelloWorld</Name>
    </Test>
  </SuccessfulTests>
  <Statistics>
    <Tests>1</Tests>
    <FailuresTotal>0</FailuresTotal>
    <Errors>0</Errors>
    <Failures>0</Failures>
  </Statistics>
</TestRun>

なんでこれを出したかというと、この結果ファイルをHudsonに渡したかったの。Hudsonとの連携はまたあらためて。

テスト駆動開発

CppUnitでテストをするHello Worldを紹介したわけだけど、
大事なのは、しれっと書いてるtestHelloWorldの実装部分なんだ。これがすなわちテストケース。

このケース部分を早い段階で書いておいて、このテストをパスするように実装をしていくというのが、テスト駆動開発(TDD:Test Driven Development)という開発手法。
ケースがきちんと書けないうちは、仕様がはっきり定まっていないということになるわけで、早い段階であいまいな点を洗い出すきっかけになるんだ。
そしてあらかじめケースを書いておけば、いろいろな変更を加えた場合のリグレッションテストが楽になり、プロジェクト後期でその威力を発揮してくる。
はっきりいってプロジェクト中盤はかったるい。仕様が変更になる度、コードもケースも書き直さなくてはならなかったりするからね。
でも長期での開発保守を考えるならば、あらかじめ仕込んでおくと幸せになれるんだと思うよ。多分。ぜひプロジェクト開始時には一度検討して欲しいと思う。