オレオレmvc に PHPUnit を適用する

そんな訳で、オレオレmvc に PHPUnit を使ってみます。
ま、オレオレ mvc はちょっとパワーアップして CakePHP 風なレイアウトとデバッグ表示に。

oreoraMVC ってことで、基本カラーはオレンジ…と言いますか、実は chocolate 色。
実行クエリなんかも表示されます。

テストケースは、こんな感じで書いていこうかなと。

<?php
set_include_path(get_include_path() . PATH_SEPARATOR . '..');
require_once('lib/config.php');
require_once 'controllers/categories_controller.php';

class TestCategoriesController extends PHPUnit_Framework_TestCase
{
	public function testNewIndex()
	{
		$controller = new CategoriesController();
		$controller->index();
		global $Categories;
		$this->assertEquals(19,sizeof($Categories));

		$this->assertEquals(1,$Categories[0]['Category']['term_id']);
		$this->assertEquals(&quot;blog&quot;,$Categories[0]['Category']['slug']);
		$this->assertEquals(&quot;ブログ&quot;,$Categories[0]['Category']['name']);
	}
}
?>

ほら、なんかそれっぽくなっているのが不思議。oreoreMVC はもうちょっとしたら、再度公開します。

カテゴリー: CakePHP, xUnit | オレオレmvc に PHPUnit を適用する はコメントを受け付けていません

PHP on IIS に PHPUnit をインストールする

まず、pear ってのが何者か分からずに苦労したのでメモ。

私の場合、PHP on IIS のインストール先が C:\PHP5.3 なのでコマンドプロンプトで移動。
その後、pear のアップデート。

# PHPUnit を入れるためには、PEAR 1.9.1 が必要なんですが、手元のは PEAR 1.9.0 なので駄目らしい。

windowsにPHPunit入れる – これでも…
http://d.hatena.ne.jp/aqua1127/20101013/1286948514

のあたりを参考にして、

go-pear channel-update pear.php.net

ここで、system|local と聞かれるので local にする。system にすると、c:\windows\pear.ini を作ろうとして失敗するので、local にして、PHPのインストール先 c:\PHP5.3 に pear.ini を作るようにする。

pear upgrade pear

でアップデート。接続にひどく時間がかかるけど、しばらく経つと繋がる。

お次は、phpunit 本体

go-pear channel-discover pear.phpunit.de

pear install phpunit/PHPUnit

でインストール、install 時に随分待たされるけど、しばらく経つと繋がる。

最後に、install failed なんてメッセージが出たら、適宜インストール…なんですが、よくわからん。

phpunit/DbUnit requires package “channel://pear.symfony-project.com/YAML” (version >= 1.0.2)

な形でエラーが出ると、

go-pear channel-discover pear.symfony-project.com
pear install symfony/YAML

な風にインストールするらしいのですが、パッケージ名はどうやって知るのだろうか?

# ちなみに、symfony/YAML は c:\php5 に入れようとして、c:\php5.3 を見てくれない(多分、ピリオドが邪魔をしている)。なので、一度、c:\php5 フォルダを作ってコピーしました。

後は、よくわからないけど

go-pear channel-discover components.ez.no

pear install components.ez.no/ConsoleTools

をインストールしました。

で、再び

go-pear channel-discover pear.phpunit.de

pear install phpunit/PHPUnit

とするとインストール完了。windows 版の場合は

c:\php5.3\phpunit.bat が作成されます(c:\php5.3 のところは各自のインストール先で)

きちんとインストールできたかどうか、試してみる。

PHPUnitのテスト作成と実行
http://php.nice-777.com/PHPUnit/start.html

を読んで ArrayTest.php を作成してみる。

<?php
// require_once 'PHPUnit/Framework.php';
class ArrayTest extends PHPUnit_Framework_TestCase
{
    public function testNewArrayIsEmpty()
    {
        // Create the Array fixture.
        $fixture = array();
 
        // Assert that the size of the Array fixture is 0.
        $this->assertEquals(0, sizeof($fixture));
    }
 
    public function testArrayContainsAnElement()
    {
        // Create the Array fixture.
        $fixture = array();
 
        // Add an element to the Array fixture.
        $fixture[] = 'Element';
 
        // Assert that the size of the Array fixture is 1.
        $this->assertEquals(1, sizeof($fixture));
    }
}
?>

どうやら、先頭の PHPUnit/Framework.php はいらないようです。自動的にインポートされます。

あと、phpunit.bat の設定が悪くて php.exe の起動が.\php.exeになっています。
なので、環境変数 PHPBIN に c:\php5.3\php.exe を設定してやります(あるいはパスを通して、php.exe に書き換えるか)。

D:\work\blog\src\phpunit>phpunit ArrayTest
PHPUnit 3.5.11 by Sebastian Bergmann.

..

Time: 1 second, Memory: 3.00Mb

OK (2 tests, 2 assertions)

D:\work\blog\src\phpunit>

そうすると、めでたく PHPUnit が動きます。

ソースを書き換えて失敗するようにすると、

D:\work\blog\src\phpunit>phpunit ArrayTest
PHPUnit 3.5.11 by Sebastian Bergmann.

.F

Time: 0 seconds, Memory: 3.00Mb

There was 1 failure:

1) ArrayTest::testArrayContainsAnElement
Failed asserting that <integer:1> matches expected <integer:2>.

D:\work\blog\src\phpunit\ArrayTest.php:24

FAILURES!
Tests: 2, Assertions: 2, Failures: 1.

D:\work\blog\src\phpunit>

ちゃんと失敗するので大丈夫そうですね。

~~

余談ですが、

class ArrayTest extends PHPUnit_Framework_TestCase

の継承のところ、このファイル名がちょうど

PEAR/PHPUnit/Framework/TestCase.php

に対応しているのですね。PHP5 から加わった自動でクラスをロードしてくれる機能だと思います(多分)。

カテゴリー: 開発, xUnit | 1件のコメント

単体テストグルグル…な.NET勉強会

一応、こちらでも宣伝をしておきます。
今月の .NETラボ勉強会(2/26 土曜日)で、NUnit を使った単体テストの話をします。

.NETラボ勉強会 2011年2月
http://kokucheese.com/event/index/8415/

単体テストツール NUnit の概略と実践的な導入ということで、

・NUnit が導入しやすい構造設計とはどのようなものか?
・NUnit のテストケースは、どれくらいまで書き込むのか?
ボリュームはどのくらいが適当なのか?
・受け入れテストで NUnit を使う

なところを実践をお話します。

例によって、資料が間に合えば資料があるのですが、まあ、間に合わなければ
コードを直に見せるという形になりますが。

# 来た方の様子を見て、NUnit の概要から始めるか、そのあたりは
# わかっているものとして飛ばして、実践編のほうに進むのかを
# 決めます。

今回は、

■ 会場:日本マイクロソフト品川本社(SGT)31F セミナールーム B
http://www.microsoft.com/japan/mscorp/branch/new/default.mspx

なところで、microsoft さんの転居先になるので、セミナールームを
見たい方はどうぞ、とかなんとか。

カテゴリー: 開発, xUnit | 単体テストグルグル…な.NET勉強会 はコメントを受け付けていません

データコンバートに explicit を使う

C#のあまり知られていない機能シリーズってことで、explicit を使ってデータコンバートしましょうってのを試してみます。と言いますか、linq to xml いらないよもどきを作っている最中に、そういえばキャストもオーバーライドができたよなぁ、ってところで気づきました。

一般的な型のコンバートは↓のようにするのが普通なのですが…

型なしDataTableから型付きDataTableにコピーする方法 | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/2047

いちいち、何とか.CONV 関数を作るのがうざったいですよね。と言うか、使うときに Conv しているのが、ちょっとってな感じなのです。変換しているのが分かり易いといえば分かり易いのですが、C++ 風に言えば、型から型へキャストっていうのも明示的な変換な訳でして、それを利用します。

まずは、使い方から

private void button3_Click(object sender, EventArgs e)
{
	// データベース接続
	SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=mvcdb;Integrated Security=True;MultipleActiveResultSets=True");
	cn.Open();
	DataTable dt = new DataTable();
	SqlDataAdapter da = new SqlDataAdapter(
		"SELECT * FROM TProduct", cn);
	da.Fill(dt);
	// キャストする
	DataTableProduct product = (DataTableProduct)dt; // ★
	dataGridView1.DataSource = product;
}

何をやっているのか、いまいち不明ですがw、★のところで、DataTable から 型付の DataTableProduct に変換しています。ここのところ、普通は converet 関数を作るところなのですが、こんな風に明示的なキャスト(explicit)を使うことができます。

# ちなみに暗黙のキャスト(implicit)を使うこともできるのですが、この場合は convert するのが明確になったほうがいいかなと。

そして、これを実現しているのが↓の仕組みです。

public class DataRowProduct
{
	// プロパティ
	public string id { get; set; }
	public string name { get; set; }
	public int price { get; set; }

	// キャストで変換
	public static explicit operator DataRowProduct(DataRow row)
	{
		DataRowProduct dest = new DataRowProduct();
		foreach (PropertyInfo pi in dest.GetType().GetProperties())
		{
			pi.SetValue( dest, Convert.ChangeType(row[pi.Name], pi.PropertyType),null);
		}
		return dest;
	}
}
public class DataTableProduct : IListSource 
{
	protected List<DataRowProduct> _rows = new List<DataRowProduct>();
	public List<DataRowProduct> Rows
	{
		get { return _rows; }
	}

	public bool ContainsListCollection
	{
		get { return true; }
	}

	public System.Collections.IList GetList()
	{
		return this.Rows;
	}

	// キャストで変換
	public static implicit operator DataTableProduct(DataTable dt)
	{
		DataTableProduct dest = new DataTableProduct();
		foreach (DataRow row in dt.Rows)
		{
			dest.Rows.Add((DataRowProduct)row);
		}
		return dest;
	}
}

DataRowProduct クラスのほうでは、リフレクションを使ってデータの詰め込み、そして、DataTableProduct のほうは、キャストを使ってデータ Rows コレクションにため込んでいます。実は、この convert の時にコピーが発生しているので、あんまりうまくないんですよね。どちらかといえば、Rows コレクションのアクセスのたびに DataTable.Rows から引っ張ってくるっていう技のほうが良いかなぁとも思います。

まぁ、それは兎も角として、キャストもうまく使うと変換の意味が表せるから便利ってな話です。
キャストと convert 関数の違いは、

  • キャストのほうは、変換先が注目される。
  • convert 関数のほうは、変換元にメソッドを追加する。
  • あるいは、convert 関数は、変換先に from メソッドを追加する。

という違いがあります。具体的にコードで示してみると、


DataTable dt ;
// キャストの場合は、左向きが明確になる。
// 変換元は表面上は出てこない
DataTableProduct table = (DataTableProduct)dt;

// 変換元で追加するコンバート関数
// ToInteger なんてのがそう。今回の場合は、DataTable を触れないので無理。
// 実は、C# の拡張メソッドを利用すると実現できたり。
DataTableProduct table = dt.ToDataTableProduct();

// 変換先で追加するコンバート関数
// ParseInt なんてのがそう。
// From メソッドか、Conv メソッドか命名に迷うところ
DataTableProduct table = DataTableProduct.From(dt);

こんな感じにすると、キャストの意味が分かっていれば、キャストのほうが自然かな、と思ったりします。DataTable も DataTableProduct も似た感じのクラスな訳ですから、型から型への変換、というほうが自然に見えます。まぁ、シンタックスシュガーっぽいところはありますが。

カテゴリー: C# | データコンバートに explicit を使う はコメントを受け付けていません

Windows アプリケーションでは NUnit を使える構造設計を

ちょっとメモ的に書き下し

CppUnit やら NUnit やらは、10年前から使っているのですが、これらの単体テストをうまく活用するためにはコツが入ります。と、言うか、うまく活用できるようなプログラミングスタイル、構造設計、進捗管理状態、になっていないとうまく活用できません

これ、経験上ですが、NUnit を導入するときの技術的な問題(NUnitを知っているとか知らないとか、Assert.AreEqual だとかなんとか)よりも障壁が大きかったりします。

いくつか導入時のときに注意するポイントを上げると、

■構造設計が無理なパターン

まず、xUnit 自体、UI を必要としない設計にしないとうまく動きません。例えば、Windows アプリケーションでのボタンクリックのエミュレート、Web サイトでのリストボックスの選択、なんていうのが内部ロジックとべったりくっ付いていると、それだけでうまく活用できません。かつて、Visual Test というテストツールがあったのですが、そこまでやらないとうまく行かないという状態でして、このような手間/お金を掛けるのであれば、構造設計の段階で、単体テストがうまく機能するようにデザインする、という手間が必須になってきます。

必然的に、MVC パターン、少なくとも View の分離をしないと、ロジック部分だけをテストする、ということはできないわけです。

このあたり、プロジェクトの作業コストや設計/実装のスキル具合によるので、難しいところもあり、xUnit がうまく導入できないのは、この構造設計の段階で失敗しているというのが非常に大きいです。

■進捗管理で単体試験の項目を管理したいパターン

アジャイル開発で大雑把に管理していれば問題ないののですが、従来のウォーターフォール開発の場合は、どうしてもコーディング(PG) 単体試験(PT)の進捗を別々に管理したくなります。というか、工程的に PG と PT は別ものだから、項目を上げて進捗率を出すとか、PT 工程は PG の後でないと駄目、なんていうのが必須だったりします。

ご存じのように xUnit の場合は、コーディングとテストが一体になったときに初めて効率的になります。勿論、コーディングを全て終わった段階で、テストコードを書くという分離の方法もできるのですが、大抵は面倒くさくなって Excel シートでの単体試験管理に戻ってしまいます。

xUnit の最大の利点は、再帰テストが非常に簡単にでき、それが自動化されることにありますから、PG と PT が混在となるのが必須になります。なので、これを分離しようとすると、そもそも xUnit の利点を排除してしまうことになり、導入に失敗します。

■プログラミングスタイルの統一

これは構造設計にもあてはまるのですが、ひとりで xUnit を導入しているときは、テストする/テスト省略の境界が分かり易いのですが(そう、テストを省略できるというのも xUnit のメリットのひとつです)、複数名のプロジェクトとなると、何をテストするのか、何をテストしないのかの基準が曖昧になります。スクラムなどのアジャイル開発の場合は、たいてい、コーディングスキルの高い人たちが集まるので、それでも大丈夫なんでしょうが、ウォーターフォール開発に場合はスキルがばらばらな場合が多いので、境界があいまいだと、全体の品質が落ちます(人によって品質がばらばらになるという意味で)。

なので、構造設計をした後、クラス分けをした後に、どのロジックはテストで重視するのか、ほとんどのコンストラクタはテストを省略して良いのか、などを分離させる必要があります。

このあたりは、主にウォーターフォール開発のノウハウになります。標準的なスキルを如何に、標準的なスピードに落とし込めるのか、がウォーターフォール開発の要になるので、できる限りクラスの役割を平準化させます。加えて、標準的に活用するライブラリを準備します。
個人的に、インテル方式と呼んでいます。

逆にアジャイル開発の場合は、ライブラリはリファクタリングでできるので、標準化のプロセスはあまりありません。と言いますか、経験者が集まることが多いので、手間がかからずに自然と標準化されます(逆に、そうならなければ、アジャイル開発自体が失敗しているとも言えます)。

で、念のために書いておきますが、今回お手伝いをしているプロジェクトでは、この要点を確実に抑えて頂きました。なので、多分大丈夫。このあたりマネジメントの極意なんですけど、落とし穴を避ければ、成功するという鉄則ですね。リスク管理という意味で。

カテゴリー: 設計 | Windows アプリケーションでは NUnit を使える構造設計を はコメントを受け付けていません

NUnit で DBUnit もどきを使う

データベースアクセスがある場合は、DBUnit を使うのがベストなんでしょうが、ひとまず、初期データの投入だけできればいいや、って気持ちで作ったのがこれです。

public class DBTest
{
    public static void DataSetup( IDataTable tbl, SqlConnection cn )
    {
        Type RowType = tbl.GetRowType();
        string TableName = tbl.GetTableName();


        PropertyInfo[] pis = typeof(XProductRow).GetProperties();

        string sql_columns = "(";
        string sql_values = "(";
        foreach (PropertyInfo pi in pis)
        {
            sql_columns += pi.Name + ",";
            sql_values += "@" + pi.Name + ",";
        }
        // 最後のカンマを削除
        sql_columns = sql_columns.Substring(0, sql_columns.Length - 1);
        sql_values = sql_values.Substring(0, sql_values.Length - 1);
        string sql = "insert into " + TableName+ " "
            + sql_columns + ") values "
            + sql_values + ")";


        SqlCommand cmd = new SqlCommand(sql, cn);
        foreach (PropertyInfo pi in pis)
        {
            SqlParameter param = new SqlParameter();
            param.ParameterName = pi.Name;
            cmd.Parameters.Add(param);
        }

        cmd.Connection.Open();
        foreach ( object item in tbl.GetList())
        {
            foreach( PropertyInfo pi in pis ) {
                cmd.Parameters[pi.Name].Value = pi.GetValue(item,null);
            }
            cmd.ExecuteNonQuery();
        }
        cmd.Connection.Close();
    }
}

例のごとく、リフレクションを使ってプロパティ名=テーブルのカラム名と想定して、INSERT 文を作成します。Visual Studio で作成される型付 DataSet を使っても良いのですが、データ投入をするたびに、別の DataAdapter を呼び出さないといけないし、なんかいまいちなので、ってのと、テストデータの投入なので INSERT 文と、テーブルの中身を DELETE する部分だけが欲しい訳で、型付 DataSet だと冗長なんですよね。

/// <summary>
/// リフレクションが簡単になるためのインターフェース
/// </summary>
public interface IDataTable
{
    string GetTableName();
    Type GetRowType();
    System.Collections.IList GetList();
}

/// <summary>
/// 商品クラス(カラム)
/// </summary>
public class XProductRow
{
    public string id { get; set; }
    public string name { get; set; }
    public int price { get; set; }
    public int cateid { get; set; }
}
/// <summary>
/// 商品クラス(テーブル)
/// </summary>
public class XProduct : IDataTable
{
    public XProduct() 
    {
        Rows = new List<XProductRow>();
    }
    public List<XProductRow> Rows { get; set; }
    public Type GetRowType() { return typeof(XProductRow); }
    public System.Collections.IList GetList() { return this.Rows; }
    public string GetTableName() { return &quot;XProduct&quot;; }
    public XProductRow NewRow() { return new XProductRow(); }
}

ざっくりと、POJO タイプの DataRow と、単なるコレクションを集めただけの DataTable を作っておきます。

そうして、データ投入をする場合は、

private void button1_Click(object sender, EventArgs e)
{
    // 自動でinsert文を作る
    SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=mvcdb;Integrated Security=True;MultipleActiveResultSets=True");

    XProduct table = new XProduct();
    XProductRow it = table.NewRow();
    it.id = "A8000";
    it.name = "新商品XXX";
    it.price = 2000;
    it.cateid = 2;
    table.Rows.Add(it);

    it = table.NewRow();
    it.id = "A8001";
    it.name = "新商品YYY";
    it.price = 2000;
    it.cateid = 2;
    table.Rows.Add(it);

    DBTest.DataSetup(table, cn);
}

こんな風に、DataRow/DataTable にデータを詰めておいて、一気にセットアップできます。

C# v3.0 の場合は、こんな風に初期化しながらということもできます。

table.Rows.Add(
    new XProductRow
    {
        id = "A8002",
        name = "新商品ZZZ",
        price = 20000,
        cateid = 1
    });

まあ、これで良いかなぁと。

カテゴリー: C#, xUnit | NUnit で DBUnit もどきを使う はコメントを受け付けていません

コマンドラインで NUnit を使う

VB2010 Express + NUnit 2.5 で、 初めてのTDD Step by Step
http://www.tdd-net.jp/vb2010ee-nunit25-tdd-stepbystep.html
NUnit V2 Test Framework
https://launchpad.net/nunitv2

暫く、NUnit は使っていなかったのですが(主に Visual Studio 付属の Test Framework を使っていたので)、ちょっとメモ書き程度に。

GUI で使う場合には、上記のリンクから辿るとして、私の場合はコマンドラインの動作を少し。コマンドラインでビルドをして、コマンドラインでテストをする、というシビアなw環境で動かしているので、まずは、テスト環境のディレクトリ構成を決めます。

+ 諸々のソースコードがあるところ
+ test/ テストコード
+ test/make.cmd

な感じで test ディレクトリを作ります。

先日、あえなく MSBuild で挫折したので、挫折したままバッチファイルを作ります。

@echo off
if "%1" == "test" goto TEST

copy ..\*.dll .
set NUNIT=nunit.framework.dll
csc /t:library /out:test.datasets.dll /r:%NUNIT% /r:sample.datasets.dll datatable.test.cs

goto END

:TEST
nunit-console test.datasets.dll

goto END

:END

ビルドをするときは、*.dll を親ディレクトリからコピーして、コンパイル。
コンパイルするときに必要なのは、nunit.framework.dll だけです。これは、テスト実行時にも必要なので、test フォルダにコピーしておきます。

テストをする場合は【make test】で動作させます。いちいち nunit-console.exe と打つのも面倒だし、テストの自動化としては make した後に test も実行というスタイルがいいですよね。昔、cppunit で makefile を作っていたときもこうやっていました。

このコンソールバージョン、何が便利かというと、

  • 再帰テストしたいときに、いちいち Visual Studio を立ち上げないですむ
  • 視覚的には GUI がいいんでしょうが、色々なテストをいっぺんに行うときは、コマンドラインが楽

という利点があります。まぁ、DOS プロンプトとかターミナルに慣れている私だからなのかもしれませんが。受け入れテスト的な形で NUnit を使う場合は、コマンドライン版のほうが便利ですよ。

ちなみ、実行結果はこんな感じです。

C:\masuda\demo\test>make test
NUnit version 2.5.9.0
Copyright (C) 2002-2010 Charlie Poole.
Copyright (C) 2002-2004 James W. Newkirk, Michael C. Two, Alexei A. Vorontsov.
Copyright (C) 2000-2002 Philip Craig.
All Rights Reserved.

Runtime Environment -
   OS Version: Microsoft Windows NT 5.1.2600 Service Pack 3
  CLR Version: 2.0.50727.3615 ( Net 2.0 )

ProcessModel: Default    DomainUsage: Single
Execution Runtime: Default
..
Tests run: 2, Errors: 0, Failures: 0, Inconclusive: 0, Time: 0.09375 seconds
  Not run: 0, Invalid: 0, Ignored: 0, Skipped: 0

copyright を消したい場合は /nologo スイッチをつけると OK です。

カテゴリー: 開発, C#, xUnit | コマンドラインで NUnit を使う はコメントを受け付けていません

型なしDataTableから型付きDataTableにコピーする方法

最近だと、LINQ to SQL や LINQ to Entities があるので、DataSet/DataTable はあまり使わないのですが、ADO.NET と云えば、DataAdapter と DataSet の組み合わせでした。
その頃は、データベースのテーブルを型付で取ってこれる、型付きDataSetの存在が結構大きかったのです。

どういうことかというと、DataTable を直接扱ってしまった場合、

foreach ( DataRow row in dt.Rows ) {
	int id = (int)row["id"];
	...
}

のように、DataRow から値を取ってくる場合は、列名を指定しないと駄目かつキャストをしなければならず、という2重苦が待っています。これが、文字列なので、ええ、ちょっと間違うとえらいことになってしまうのです。PHP だと、こんな風に書くのが普通なので、特に気にしないのですが、ASP.NET で規模の大きいデータベースだと、ちょっとデバッグが大変なことに、っていう具合です。これ、実行時のエラーでしか取れないので、難しいんですよね。

なので、C# の厳密な型、というのを利用して、

foreach ( MyDataRow row in dt.Rows ) {
	int id = row.id ;
	...
}

のように、プロパティで値を安全に取得できるようにするのが、型付の意味なのです。

で、この型付DataSetですが、Visual Studio 上で作成すると膨大な自動生成のソースコードがあって整理が大変ッ!!! ということもあり、さらに、自前で型付DataTableを使いたいときは、あのデザイナでちまちま列を作らなきゃならない、という問題がありまして。なんとなく避けてしまって、とりあえず、DataSet や DataTable のまま扱っているのが普通なのではないかなぁと。

そこで、本題ですが、これをコンバーターのクラスを用意して、型なしから型付にコピーできるようにしてしまおう、というものです。で、MyDataTable, MyDataRow の作成はなるべく手間をかけたくないという訳で。

それを実現してみたのが次のコードです。

/// <summary>
/// リフレクションを簡単にするための準備
/// </summary>
public interface IDataTable  
{
    Type GetRowType();
    object CreateRow();
}
/// <summary>
/// 型付DataTable用のテンプレート
/// </summary>
/// <typeparam name=&quot;DRType&quot;></typeparam>
public class DTTemplate<DRType> : IDataTable, IListSource
{
    /// <summary>
    /// DataRow の型を返す
    /// </summary>
    /// <returns></returns>
    public Type GetRowType() {
        return typeof(DRType);
    }
    /// <summary>
    /// DataRow のリスト
    /// </summary>
    protected List<DRType> _rows = new List<DRType>();
    public List<DRType> Rows {
        get { return _rows; }
    }

    /// <summary>
    /// 新しい DataRow を作成
    /// </summary>
    /// <returns></returns>
    public DRType NewRow() {
        return (DRType)Activator.CreateInstance(typeof(DRType));
    }
    /// <summary>
    /// 新しい DataRow を作成(object型)
    /// </summary>
    /// <returns></returns>
    public object CreateRow() {
        return NewRow();
    }
    
    // インターフェースの実装
    /// <summary>
    /// 内部リストから IList を使う
    /// </summary>
    public bool  ContainsListCollection
    {
        get { return true; }
    }
    /// <summary>
    /// IListのコレクションを返す
    /// </summary>
    /// <returns></returns>
    public System.Collections.IList GetList()
    {
        return this.Rows;
    }
}

/// <summary>
/// いわゆる型付DataRow
/// </summary>
public class ProductRow  
{
    protected string _id;
    protected string _name;
    protected int _price;

    // プロパティアクセスは、C# 3.0 ならば、
    // public string id { get; set; }
    // で書ける.
    // 今回は v2.0 なので、この形式で。
    public string id
    {
        get { return _id; }
        set { _id = value; }
    }
    public string name
    {
        get { return _name; }
        set { _name = value; }
    }
    public int price
    {
        get { return _price; }
        set { _price = value; }
    }
}
/// <summary>
/// 型付DataSet
/// </summary>
public class Prodcut : DTTemplate<ProductRow>
{
}
/// <summary>
/// 形無しDataSetを型付DataSetにコンバートするクラス
/// </summary>
public class DataBind
{
    public static void Conv(DataTable src, IDataTable dest)
    {
        Type rowType = dest.GetRowType();
        System.Collections.IList rows = ((IListSource)dest).GetList();
        foreach (DataRow row in src.Rows)
        {
            object item = dest.CreateRow();
            // リフレクションを使って、列ごとにコピーする
            foreach (DataColumn column in src.Columns)
            {
                // この部分はキャッシュすると高速化する
                string key = column.ColumnName;
                PropertyInfo pi = rowType.GetProperty(key);
                if (pi != null)
                {
                    // object型 -> 元の型のキャストでエラーになるため、
                    // 一度元の型に ChangeType してから代入する。
                    pi.SetValue(item, 
                        Convert.ChangeType(row[key], pi.PropertyType), 
                        null);
                }
            }
            rows.Add(item);
        }
    }
}

いやぁ、リフレクションを使っているんですが、結構実現するのは長いコードになってしまいました。型付の ProductRow は、v2.0 なのでget/set を別に書く必要がありますが、v3.0 以降ならば、次のように短く書けます。

public class ProductRow  
{
    public string id { get; set; }
    public string name { get; set; }
    public int price { get; set; }
}

そして、型付DataTable に関しては、こんな風に更に短く。

public class Prodcut : DTTemplate<ProductRow> {}

実質、1行で書くことができます。
コンバート関数は、DataBind.Conv() だけで使えるので、非常に簡単。

データベースに接続して、DataGrid に表示するコードがこのように短く書けます。

private void button2_Click(object sender, EventArgs e)
{
    // データベース接続
    SqlConnection cn = new SqlConnection(@"Data Source=.\sqlexpress;Initial Catalog=mvcdb;Integrated Security=True;MultipleActiveResultSets=True");
    cn.Open();
    DataTable dt = new DataTable();
    SqlDataAdapter da = new SqlDataAdapter(
        "SELECT * FROM TProduct", cn);
    da.Fill(dt);
    cn.Close();

    Prodcut product =new Prodcut();
    DataBind.Conv(dt, product);

    dataGridView1.DataSource = product;

}

まあ、単純に DataGridView の DataSource プロパティを使ってバインドする場合は、DataTable のままでいいんですけどね。列名なんかで条件分岐をするときなんか、dt.id のようなプロパティで指定できると間違いが少なくなりますよねぇ。という話でした。

ええと、業務で使うコードは、もうちょっと高速化していきます。といいますか、DataBind.Conv のように static 関数にせず、実は ProductRow の行テーブルへのマッピング方式にしています。ま、これは別の機会に紹介します。値を加工して ProductRow に詰め込むようなことをするために、ProductRow の内部メソッドとして用意しています。

カテゴリー: C# | 型なしDataTableから型付きDataTableにコピーする方法 はコメントを受け付けていません

アリスはプラグインで強化する(Assembly.LoadFrom を使う)

オブジェクト指向の肝で、それは【継承】を使うのが良いのか、それとも【委譲】にしたほうがいいのか、という話があります。結論を言えば、ケースバイケースなのですが、どうしても委譲でしか解決できないものもあります。

プラグインのパターンがそうで、とあるクラスの機能を強化しようとする場合、とあるクラスに手を加えずに強化する方法が【委譲】のパターン、インターフェースを作っておいて、それを動かすというパターンになります。

さて、C++ の場合は、プラグイン作りは、インターフェースとなる関数を定義しておいて、外部のDLLで定義しておけば良いので、結構簡単にできます。結構簡単に、と言うのは DLL 作りに慣れていればの話であって、非常に敷居の高いものでもあります。DLL の import/export の対象となる関数の型の変換やスタックの使い方の問題があって、ややこしいのですね。

これを統一化させるために、COM がある訳ですが、いちいち登録が必要なのと、COM を扱うのが相当手間(少なくとも生のC++でやるのは手間です)なので、避けたいプラグインです。VB6 あたりだと CreateObject 関数で作成すればよいのですが、このあたりは variant 型を扱える言語だからですね~、とかなんとか。

と、C# ではどうやるの?と思って調べて作ってみたのが以下です。
ネタ元は、先日買った「プログラミング .NET Framework 第3版」なのです。

まずはメイン関数です。

using System;
using System.Reflection;
using Sample;

public class Program 
{
	public static void Main(string[] args )
	{
		Console.WriteLine("plugin test");
		
		Type per = null;
		// 動的に DLL をロードする
		Assembly asse = Assembly.LoadFrom("Person.dll");
		foreach ( Type t in asse.GetExportedTypes() ) {

			Console.WriteLine("class: {0}", t.ToString());
			// 内部で公開されているクラスで IPerson なものを探す
			if ( t.IsClass && typeof(IPerson).IsAssignableFrom(t) ) {
				per = t;
				break;
			}
		}
		if ( per == null ) {
			Console.WriteLine("Error: no interface");
			return;
		}
		// IPerson のコンストラクタを使ってオブジェクトを作成
		IPerson p = (IPerson)Activator.CreateInstance(per);
		p.Say("hello");
	}
}

何をやっているかわかり辛いですが、Person.dll というアセンブリを動的にロードしています。そして、この中にある IPerson というインターフェースを探して、見つかったら、IPerson::Say メソッドを実行しています。

このあたり、インターフェースを使わない場合はリフレクションを使うのですが、インターフェースを使ったほうが楽ですし、コンパイル時にチェックができるので、お奨めです。

この IPerson インターフェースは、次のコードです。

namespace Sample
{
	public interface IPerson
	{
		void Say( string val );
	}
}

実に単純ですね。単純に委譲をするためだけのインターフェースです。

実際に動作するところの Person クラスは以下のコードです。

using System;

namespace Sample
{
	public class Person : IPerson
	{
		public void Say( string val ) 
		{
			Console.WriteLine("in Person: {0}", val );
		}
	}
}

IPerson インターフェースを継承して Person クラスを作ります。メイン関数で IPerson::Say メソッドを呼び出したときには、この Say メソッドが呼び出されます。

さて、これをコンパイルするのはどうするかというと、こんな風です。

csc /t:library IPerson.cs
csc /t:library /r:iperson.dll Person.cs
csc /t:exe /r:iperson.dll main.cs

まず、iperson.dll だけを作ります。このアセンブリを、Person.cs と main.cs が読み込む訳です。当然なことですが、person.cs と main.cs の直接的な関係はありません。関係がないので、main.exe と person.dll は別々に開発することができるのです。

実行したのがこれです。

C:\masuda\alice>plugin
plugin test
class: Sample.Person
in Person: hello

さて、person.cs とは別の personOther.cs を作ります。

using System;

namespace Sample
{
	public class PersonOther : IPerson
	{
		public void Say( string val ) 
		{
			Console.WriteLine("in PersonOther: {0}", val );
		}
	}
}

のように PersonOther というクラスを作ります。そして、これをコンパイルする時に、person.dll が出力されるようにします。

csc /t:library /r:iperson.dll /out:Person.dll PersonOther.cs

いわゆる、DLL 名はそのままにして、中身をすげかえてしまうわけです。
そして、main.exe はそのままにして、実行すると、

C:\masuda\alice>plugin
plugin test
class: Sample.PersonOther
in PersonOther: hello

な風にメッセージ(動作)が変わります。

まあ、本来は main 関数で使っている Person.dll の名前を変えるのが普通なんですけどね。さくっとアセンブリを変えるだけで、クラス名までも変えられてしまう(インターフェースが合っているのだから、これは妥当なんだけど)のが不思議なところです。

さて、書籍に載っていたのですが、この Assembly.LoadFrom メソッドの引数、ローカルファイルだけでなくて WEB サイトに置いてあるファイルも参照できます。つまり

Assembly asse = Assembly.LoadFrom("http://servername/assemblries/Person.dll");

のように、http プロトコルが使えるそうです。へぇッ!!! 試していないのですが、これって結構アレな機能ですよね。インターフェースをうまく使うと、一度インストールしてしまえば、アップデート無しで機能を追加してしまうことが可能なのです。勿論、オフラインの時にも動作するようにするためには、ちょっと工夫が必要ですが。

カテゴリー: 開発, C# | アリスはプラグインで強化する(Assembly.LoadFrom を使う) はコメントを受け付けていません

コマンドラインで C# をコンパイルしよう(2)

続きです。csc.exe を使って C# のソースコードがコンパイルできるようになると、C++ の make のようにたくさんのファイルを一度にコンパイルしたいですよね。

アセンブリを分けようと思って、/target と /reference /out を駆使して作ったバッチファイルは以下のものです。

csc /nologo /target:library /out:sample.datasets.dll datatable.cs
csc /nologo /target:library /r:sample.datasets.dll /out:sample.dao.dll dao.cs
csc /nologo /target:library /out:sample.models.dll /reference:sample.datasets.dll model.cs
csc /nologo /target:library /out:sample.bizlogic.dll ^
	/r:sample.models.dll /r:sample.datasets.dll /r:sample.dao.dll bizlogic.cs
csc /nologo /target:library /out:sample.dll ^
	/r:sample.models.dll ^
	/r:sample.bizlogic.dll ^
	/r:sample.datasets.dll ^
	SampleForm.cs
csc /nologo /target:exe ^
	/r:sample.dao.dll ^
	/r:sample.models.dll ^
	/r:sample.bizlogic.dll ^
	/r:sample.datasets.dll ^
	/r:sample.dll ^
	main.cs
csc /nologo /target:exe ^
	/r:sample.dao.dll ^
	/r:sample.models.dll ^
	/r:sample.bizlogic.dll ^
	/r:sample.datasets.dll ^
	test.cs
csc /nologo /target:exe ^
	/r:sample.datasets.dll ^

make.cmd って言う名前のファイルにしています。
実はコマンドラインは複数行の指定ができたんですね。知らんかった。
「^」をタイピングすると、次の行に移れます。コマンドプロンプトでもできるのですが、More の前に戻れないから編集できないし…まぁ、こんな風にバッチファイルを作るときに便利です。

さて、このファイルをアセンブリ(*.dll)と C# のソースコード(*.cs)の依存関係をつけたいなぁ、と思って、さて MSBuild でやるか、とマニュアルを見てみたのですが…

MSBuild リファレンス
http://msdn.microsoft.com/ja-jp/library/0k6kkbsd%28v=VS.80%29.aspx
.NETビルド・エンジン「MSBuild」使いこなし術
http://www.atmarkit.co.jp/fdotnet/special/msbuild01/msbuild01_01.html

結論を言えば、素の状態で MSBuild の XML ファイルを書いていくのは【無理】です。到底手軽にできるようなものではありません。Visual Studio で *.csproj とか *.sln のファイルを直接ビルドするときに使うと良いですね。

# 当時、Ant が流行っていて、NAnt ができて、make よりも高機能なことができる、ってのが売りなんですけど、make のようにコロンとタブで依存関係を作りたい場合は make のほうが適していますね。

という訳で、依存関係を使って手軽にビルドしたいときは make/nmake を使いましょう、って話でした。

カテゴリー: 開発 | 1件のコメント