ささいなことですが。

Windowsアプリテスト自動化ライブラリFriendly開発者の日記です。

Visual Studio for Mac ⇔ Windows (WinFormsがMacでビルド、実行できたよ)

Visual Studio for Mac を触り始めましたが、Windows版との互換性が気になりました。ちょっと実験してみます。(ていうか、この辺はXamarinStudioと同じなんでしょうけど、XamarinStudioの仕様も知らないので)

コンソールアプリ

for Mac で作ったコンソールアプリをそのままWindowsの方にコピーしてみました。
で、Visual Studio 2015 で開いてみます。
おー、開けたよ!逆になんで?
f:id:ishikawa-tatsuya:20161120163921p:plain
プロジェクト情報を見てみると、.NET Framework 4.5 になっていました。
for Macの方でプロジェクト情報見ると・・・
Mac版ではプロジェクトのところからメニューを開いてオプションを選択するようです。
f:id:ishikawa-tatsuya:20161121063356p:plain:w300
で、全般を選択すると
f:id:ishikawa-tatsuya:20161121063650p:plain
なるほど、コンソールアプリはMono/.NetFramework4.5で作られるんですね。
では、macの方でビルドしたexeをWindowsで実行すると・・・。
f:id:ishikawa-tatsuya:20161121063130p:plain
普通に実行できました。
逆にこれはmacでは、普通には動かないんですね。
mac初心者なんで最初あれ?ってなったんですが、exeってmacの実行ファイルではないようです。macで動かすためにはホストする何かが必要になります。で、.Netアプリを実行するにはmonoランタイムを使います。ターミナルでexeのフォルダまで行って、monoコマンドを実行します。
f:id:ishikawa-tatsuya:20161121064059p:plain

WinForms

どうも調べて見るとMonoのランタイムでWinFormsもある程度動くようです。ちょっとやってみたいですね。でも Visual Studio for Mac の新規作成ではWinFormsなんてない。もはや関わりたくない感がすごいw。でも先ほどのConsoleアプリの実験で分かったようにWindows版とMac版でソリューションの互換性はあるようです。というわけでWindows版でソリューション作ってそれをコピーしてみます。.Netのバージョンは4.5にしておきます。で、開くと
f:id:ishikawa-tatsuya:20161121064352p:plain
おー、開けた!
そして実行もしてみます。
f:id:ishikawa-tatsuya:20161121064855p:plain:w280
実行もできますね。結構便利な気はしますが、もはや主流ではないんですねー。
それから、フォームのデザイナも開くことはできませんでした。残念。

Mac買いました!そしてVisual Studio for Macをインストールしましたー。

なんと、人生初のmacを買いました。
このブログもmacで書いてます。
慣れてないから、時間かかるw

そしてVisual Studio for Macをインストールしました。

www.visualstudio.com
インストール時もキャプチャしておけばよかったですね。
まあ、特に問題なく入りました。
(でも、前日にXCodeJDK、XamarinStudioを入れていたからかも。
起動してみるとこんな感じ。
f:id:ishikawa-tatsuya:20161117232631p:plain

実験

試しにConsoleアプリを作ってみました。Xamarin? 最初はシンプルに行きましょうw
f:id:ishikawa-tatsuya:20161117232927p:plain
あれ?マック用のアプリとかも作れるんですねー。とりあえず、今回はコンソールプロジェクトを選択しました。
f:id:ishikawa-tatsuya:20161117233154p:plain
で、作成を押すとプロジェクトができあがります。
さー、実行!F5。おーF5効いたよ。ショートカットキーがWindows版と同じなのが嬉しいですね。
f:id:ishikawa-tatsuya:20161117233639p:plain
あれー、でもプログラム終了しても、コンソールのこるんだ。ちょっとウザい。
ステップ実行試してみます。
f:id:ishikawa-tatsuya:20161117234437p:plain
F9でブレイクはってF5で実行。
そんで止まったら、F10でステップ!いいねー。
さー、では関数にステップイン、F11...
ノー!どういうことやねん!
f:id:ishikawa-tatsuya:20161117234843p:plain
F11はMacのシステム的なキーなんかな?デスクトップが表示されました。
おいおい、ステップインどうすんだよ!
あ、ここでキー設定みれるっぽい。
f:id:ishikawa-tatsuya:20161117235332p:plain
Macはアプリのメニューがデスクトップの上部にでるんですねー。慣れてないから違和感がすごいw
f:id:ishikawa-tatsuya:20161117235417p:plain
ステップで検索してみると、出てきました。なるほど、それぞれCommandキーをつければいいのね。
ステップインが Command+F11
ステップアウトが Command+Shift+F11
ついでに、定義位置にジャンプはF12で動作しました。

Nuget

今日のところはNuget見つけれたら満足ですね。ここにありました。
f:id:ishikawa-tatsuya:20161118000340p:plain
f:id:ishikawa-tatsuya:20161118000457p:plain
試しにJSON入れてみました。
f:id:ishikawa-tatsuya:20161118001825p:plain

いい感じです。

ぱっと使いそうな機能を試してみましたが、普通に使えて逆にびっくりですw
もっと使い込んでみようっと。

dotnetConf関西 2016 で登壇してきました。

これですね。
.Netの新しい機能の勉強会です。
connpass.com

僕はWPFとUWPのテスト自動化に関して話をしてきました。
まあ、WPFは新技術でもないですけど・・・。
とは言え、いい感じに枯れてきたので、日本でもようやく導入が始まってきたようですね。(過去何回も、日本でもようやく・・・的な話を聞きましたが、今回は本当のようですw)

WPFはFriendlyでバッチリテストできます。
UWPも結構いいところまで来てるんですよねー。
これも早いところAPI固めて正式版にしたい。

www.slideshare.net

デモが多かったのですが、
こちらから、そのアプリとテストをダウンロードできますので
よろしければ試してみてください。
github.com

Friendly.UWP_α 0.0.5 をリリースしました。

すごい久しぶりのリリースです。半年以上ほったらかしてましたからねー。
www.nuget.org

そもそも、動かなくなってた

おいおいって感じですが。UWPの画面描画に関するオブジェクトの生成をメインスレッド以外で実行した場合、例外が発生するようになっていました。Microsoftも結構な破壊的変更をぶっこんできますねー。newもメインスレッドで実行するようにしました。

UWPAppFriendから現在のウィンドウを取得できるプロパティを追加

地味機能です。さらにそこからContentも取得できます。もちろんFriendlyの素の機能でもできますが、インテリセンス効いた方が格段に便利ですよね。

var content = app.CurrentWindow.Content;

VisualTree検索機能

WPFの時に評判の良かったツリーからの検索機能を実装しました。UWPはLogicalTreeってないんですねー。VisualTreeからタイプとバインディングで検索できるようにしています。

var tree = app.CurrentWindow.Content.VisualTree();

//型から取得
var textBox = tree.ByType("Windows.UI.Xaml.Controls.TextBox").Single();

//バインディングパスから取得
var button = tree.ByBinding("Execute").Single());

コントロールラッパーの作成開始

とりあえず、以下作りました。順次増やしていきます。

  • Button
  • CheckBox
  • RadioButton
  • ComoboBox
  • ListBox
  • TextBox

で、今のところこんな操作ができます。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Codeer.Friendly.Dynamic;
using System.IO;
using VSHTC.Friendly.PinInterface;
using EnvDTE80;

namespace Friendly.UWP.Test
{
    [TestClass]
    public class TestAttach
    {
        [TestMethod]
        public void Test()
        {
            using (var app = new UWPAppFriend(new ByVisualStudio(Path.GetFullPath("../../../TargetApp/TargetApp.sln"))
            {
                VisualStudioPath = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\IDE\devenv.exe",
                ChangeVisualStudioSetting = (vs, dteSrc)=>
                {
                    var dte = dteSrc.Pin<DTE2>();
                    dte.Solution.SolutionBuild.SolutionConfigurations.Item(3).Activate();
                }
            }))
            {
                //Visual Tree取得
                var tree = app.CurrentWindow.Content.VisualTree();

                //テキストボックス
                var textBox = new TextBox(tree.ByType(TextBox.TypeFullName).Single());
                textBox.EmulateChangeText("abc");

                //ボタン
                var button = new Button(tree.ByBinding("Execute").Single());
                button.EmulateClick();

                //コンボボックス
                var comboBox = new ComboBox(tree.ByType(ComboBox.TypeFullName).Single());
                comboBox.EmulateChangeSelectedIndex(2);

                //リストボックス
                var listBox = new ListBox(tree.ByType(ListBox.TypeFullName).Single());
                listBox.EmulateChangeSelectedIndex(2);

                //ラジオボタン
                var radioButton = new RadioButton(tree.ByType(RadioButton.TypeFullName).Single());
                radioButton.EmulateCheck(true);

                //チェックボタン
                var check = new CheckBox(tree.ByType(CheckBox.TypeFullName).Single());
                check.EmulateCheck(true);

                //もちろん素のFriendlyの機能で、内部APIを直で実行できます。
                //背景色変更
                var mainPage = app.CurrentWindow.Content.Dynamic().Content;
                var color = app.Type("Windows.UI.Colors").Blue;
                var brush = app.Type("Windows.UI.Xaml.Media.SolidColorBrush")(color);
                mainPage.Content.Background = brush;
            }
        }
    }
}

実行結果です。
f:id:ishikawa-tatsuya:20160905010130p:plain

LambdicSqlがβ版になりました!

www.nuget.org

やっとβ版になりました。
1.0に向けて仕様は、ほぼ固まったかなー。これもおださんをはじめ、色々助言いただいた方々のおかげです。ありがとうございます!ちなみに織田さんには少し前からNugetのOwerにも入ってもらってます。

SQLをLambdaから作成するライブラリです

その名のとおりです。Linqとはアプローチが違って、書いたC#が(割と)そのままSQLになります。

public void TestStandard()
{
    var min = 3000;

    //C#でラムダを書くと
    var query = Db<DB>.Sql(db =>
        Select(new SelectData()
        {
            Name = db.tbl_staff.name,
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_staff.id == db.tbl_remuneration.staff_id).
        Where(min < db.tbl_remuneration.money && db.tbl_remuneration.money < 4000));

    //文字列とパラメータになる!
    //デバッグ出力してみます。
    var info = query.Build(_connection.GetType());
    Debug.Print(info.SqlText);

    //後は、それを投げるだけ
    //Dapper使ってたらこんな感じです。
    var datas = _connection.Query(query).ToList();
}
SELECT
    tbl_staff.name AS Name,
    tbl_remuneration.payment_date AS PaymentDate,
    tbl_remuneration.money AS Money
FROM tbl_remuneration
    JOIN tbl_staff ON (tbl_staff.id) = (tbl_remuneration.staff_id)
WHERE ((@min) < (tbl_remuneration.money)) AND ((tbl_remuneration.money) < (@p_1))

シンプルなものだけじゃなくて、こんな複雑なものも書けます。

できること

こちらに一覧を書きました。(長いので転記はあきらめました。参照お願いします。)
github.com

多くの句、キーワード、関数、Window関数に対応

私が最近買った、SQLの入門書に書かれているDCLは全て対応しましたw。この本に載っているDCLはすべて書ける!(はず)CreateとかDropのようなDDLはサポートしていません。
「SQL ゼロからはじめるデータベース操作」
f:id:ishikawa-tatsuya:20160902233751p:plain

主要DBで動作

以下で動作確認しています。とは言え、同じ書き方でOKってわけでなくて、普通のSQLと同様に関数や句はDBごとに使える、使えないはあります。

DataBase type Support
SQL Server
SQLite
PostgreSQL
Oracle
MySQL
DB2

その他、便利機能が盛りだくさん

便利機能が盛りだくさんです。記事はα版の時に書いたものをβ版の仕様で修正していますので、参照お願いします。Dapper、EntityFrameworkの操作運用に関してはさらに便利にしたのでまた書きます。

早く1.0になりたい

残件は

  1. 関数コメント(英語)
  2. テスト追加
  3. チューニング
  4. リファクタリング

①が一番難易度高い...
9月中リリースを目標に進めよっと。

SQLWorld★大阪#38 に参加してきました。

SQL力を高めるべく、SqlWorld :: SQLWorld★大阪#38に参加してきました!
簡単なものから高度すぎるものまで、レンジの広い問題が出題されました。
で、その中でこんな問題がありました。
友達の人数を抽出するというものです。
f:id:ishikawa-tatsuya:20160825000048p:plain
で、私はこんなSQLを書きました。

SELECT 人.名前,
       isnull(友達人数.人数, 0) AS 人数
FROM   人
       LEFT OUTER JOIN
       (SELECT   友達一次元.ID AS ID,
                 sum(人数) AS 人数
        FROM     (SELECT   人ID1 AS ID,
                           count(*) AS 人数
                  FROM     友達
                  GROUP BY 人ID1
                  UNION
                  SELECT   人ID2 AS ID,
                           count(*) AS 人数
                  FROM     友達
                  GROUP BY 人ID2) AS 友達一次元
        GROUP BY 友達一次元.ID) AS 友達人数
       ON 人.ID = 友達人数.ID;

LambdicSqlで書いてみます

まあ、サブクエリの入れ子とか複雑ですよね。これをLambdicSqlで書いてみようと思います。(実はもっと良い書き方を主催のおださんから教えていただいたのですが、LambdicSqlの題材としてはこっちの方がよかったので)
LambdicSqlは順に組み立てられるのでこんなとき便利です。
最後のToSqlInfoでがSQL文字列が作成されます。

//テーブルの定義
class 人
{
    public int ID { get; set; }
    public string 名前 { get; set; }
}
class 友達
{
    public int 人ID1 { get; set; }
    public int 人ID2 { get; set; }
}
class DB
{
    public 人 人 { get; set; }
    public 友達 友達 { get; set; }
}

[TestMethod]
public void TestSqlWorld()
{
    //①友達テーブルの人ID1の列で友達の数を数えるクエリ
    var 人数1 = Sql<DB>.Create(db =>
        Select(new
        {
            ID = db.友達.人ID1,
            人数 = Count<int>(new Asterisk())
        }).
        From(db.友達).
        GroupBy(db.友達.人ID1));

    //②友達テーブルの人ID2の列で友達の数を数えるクエリ
    var 人数2 = Sql<DB>.Create(db =>
        Select(new
        {
            ID = db.友達.人ID2,
            人数 = Count<int>(new Asterisk())
        }).
        From(db.友達).
        GroupBy(db.友達.人ID2));

    //③合算して一列のテーブルにするクエリ
    var 友達一次元 = 人数1.Union(人数2);

    //④IDでグルーピングして人数を数える
    var 友達人数 = Sql<DB>.Create(db =>
    Select(new
    {
        ID = 友達一次元.Body.ID,
        人数 = Sum(友達一次元.Body.人数)
    }).
    From(友達一次元).
    GroupBy(友達一次元.Body.ID));

    //⑤クエリ完成
    //名前を付けるクエリ
    var 友達人数_名前付き = Sql<DB>.Create(db =>
        Select(new
        {
            名前 = db.人.名前,
            人数 = IsNull(友達人数.Body.人数, 0)
        }).
        From(db.人).
        LeftJoin(友達人数, db.人.ID == 友達人数.Body.ID)
    );

    //文字列とパラメータの取得
    //後はDapperへ
    var info = 友達人数_名前付き.ToSqlInfo(typeof(SqlConnection));
    Debug.Print(info.SqlText);
}

こんなSQLになります。

SELECT
	人.名前 AS 名前,
	ISNULL(友達人数.人数, @p_1) AS 人数
FROM 人
	LEFT JOIN 
	(SELECT
		友達一次元.ID AS ID,
		SUM(友達一次元.人数) AS 人数
	FROM 
		(SELECT
			友達.人ID1 AS ID,
			COUNT(*) AS 人数
		FROM 友達
		GROUP BY 友達.人ID1
		UNION
		SELECT
			友達.人ID2 AS ID,
			COUNT(*) AS 人数
		FROM 友達
		GROUP BY 友達.人ID2) 友達一次元
	GROUP BY 友達一次元.ID) 友達人数 ON (人.ID) = (友達人数.ID)

履歴

2016/09/02 β版対応

LambdicSql -主要DBゆるふわ対応-

LambdicSql_α0.0.63をリリースしました。β版間近です!
www.nuget.org

マルチDB対応

なんと主要6DBで動作確認しています。Surfaceに全部インストールしましたが、意外と入るものですね。(SQLiteはインストール不要)

DataBase type 動作確認
SQL Server
SQLite
PostgreSQL
Oracle
MySQL
DB2

とは言え、同じ書き方でOKなわけではないです。

この辺がゆるふわ。LambdicSqlは基本はそのままSQLのテキストになります。つまり、普通にSQL書く時と同じように使える句や関数だけ使うという方針です。

public void TestWindow()
{
    //SQLiteとMySqlはWindow関数使えないよ。
    if (_connection.GetType().FullName == "System.Data.SQLite.SQLiteConnection") return;
    if (_connection.GetType().FullName == "MySql.Data.MySqlClient.MySqlConnection") return;

    //make sql.
    var query = Db<DB>.Sql(db =>
        Select(new SelectData()
        {
            Avg = Window.Avg(db.tbl_remuneration.money).
                    Over<decimal>(new PartitionBy(db.tbl_staff.name, db.tbl_remuneration.payment_date),
                        new OrderBy(new Asc(db.tbl_remuneration.money), new Desc(db.tbl_remuneration.payment_date)),
                        new Rows(1, 5)),
            PaymentDate = db.tbl_remuneration.payment_date,
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
        Join(db.tbl_staff, db.tbl_remuneration.staff_id == db.tbl_staff.id));

    //to string and params.
    var info = query.Build(_connection.GetType());
    Debug.Print(info.SqlText);

    //dapper
    var datas = _connection.Query<SelectData>(info.SqlText, info.Params).ToList();
}

オプションで、以下を切り替えれるようにしました。

じゃあ、何がマルチDB対応なんだよってことなのですが、以下の二つだけオプションで切り替えれるようにしています。こればっかりは書き分けられないので・・・。

  • 文字列の+演算
  • パラメータのプレフィックス
[TestMethod]
public void Test()
{
    var query = Db<DB>.Sql(db =>
        Select(new
        {
            Name = db.tbl_staff.name + "★",
            Money = db.tbl_remuneration.money,
        }).
        From(db.tbl_remuneration).
            Join(db.tbl_staff, db.tbl_staff.id == db.tbl_remuneration.staff_id).
        Where(1000000 < db.tbl_remuneration.money));

    //文字列化するときにオプションを指定する
    var info = query.Build(new SqlConvertOption() { ParameterPrefix = ":", StringAddOperator = "||" });
    Debug.Print(info.SqlText);
}
SELECT
	/*文字列結合の演算子に||を指定*/
	(tbl_staff.name) || (:p_0) AS Name,
	tbl_remuneration.money AS Money
FROM tbl_remuneration
	JOIN tbl_staff ON (tbl_staff.id) = (tbl_remuneration.staff_id)
/*オラクルは@使えない*/
WHERE (:p_1) < (tbl_remuneration.money)

なんで、こんなとこに差だすかなー。オラクルで@使ったらダメとか、わからなくて結構悩みました。

DataBase type 文字列結合 パラメータプレフィックス
SQL Server + @
SQLite || @
PostgreSQL || @
Oracle || :
MySQL + @
DB2 || @

これを毎回指定するのは面倒なんで、コネクション(SqlConnectionとか)のタイプを渡すと最適なオプションを選択するようにしています。

//コネクションタイプを渡すと、最適なオプションを選択します。
var info = query.ToSqlInfo(connection.GetType());

同一の記法でSQLテキストをカスタマイズする方法も提供しています。

長くなるので端折りますが、ToSqlInfoの第三引数にテキスト出力をカスタマイズするインターフェイスを渡せるようにしています。どうしてもやりたい人はこちらでできるように。また句や関数を自分で増やせる手段も提供しています。(拡張オレオレ句も作れる)

//第三引数でカスタマイズインターフェイスを渡せる仕様にしてます。
public static SqlInfo ToSqlInfo(this ISqlExpression exp, SqlConvertOption option, ISqlSyntaxCustomizer customizer)