XBAPとASP.NET MVCをどうやって連携するか

このドキュメントではASP.NET MVCとXBAPをどうやって連携するか考える。

XBAPのファイルをどう配置すべきか?

XBAPは別プロジェクトにする。
ASP.NET MVCのプロジェクトはXBAPのプロジェクトに依存させる。
xbap001.png

ASP.NET MVCのxabapフォルダを対象にxbapを発行するようにしとけばいい。
VisualStudioからXBAPプロジェクトを選んで一々発行するのもいいが、以下のようにASP.NET MVCプロジェクトのビルド前イベントにバッチを組むといい。

1.MSBUILD用のスクリプトを記述する。

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Publish" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
    <PropertyGroup>
        <!-- the folder of the project to build -->
        <ProjLocation>.\WpfBrowserApplication1</ProjLocation>
        <ProjLocationReleaseDir>$(ProjLocation)\bin\Release</ProjLocationReleaseDir>
        <ProjPublishLocation>$(ProjLocationReleaseDir)\app.publish</ProjPublishLocation>
        <!-- This is the web-folder, which provides the artefacts for click-once. After this
         build the project is actually deployed on the server -->
        <DeploymentFolder>MvcApplicationTest\xbap</DeploymentFolder>
    </PropertyGroup>

    <Target Name="Publish" DependsOnTargets="Clean">
        <Message Text="Publish-Build started for build no $(ApplicationRevision)" />
        <MSBuild Projects="$(ProjLocation)/WpfBrowserApplication1.csproj" Properties="Configuration=Release" Targets="Publish"/>   

        <ItemGroup>
            <SchoolPlannerSetupFiles Include="$(ProjPublishLocation)\*.*"/>
            <SchoolPlannerUpdateFiles Include="$(ProjPublishLocation)\Application Files\**\*.*"/>
        </ItemGroup>
        <Copy
            SourceFiles="@(SchoolPlannerSetupFiles)"
            DestinationFolder="$(DeploymentFolder)\"
        />
        <Copy
            SourceFiles="@(SchoolPlannerUpdateFiles)"
            DestinationFolder="$(DeploymentFolder)\Application Files\%(RecursiveDir)"
        />      
    </Target>
    <Target Name="Clean">   
        <Message Text="Clean project:" />
        <MSBuild Projects="$(ProjLocation)/WpfBrowserApplication1.csproj" Properties="Configuration=Release" Targets="Clean"/>
    </Target>       
</Project>

2.ASP.NET MVCプロジェクトのビルド前イベントで以下のようなバッチを記述する

Mage.exe -cc
"C:\Program Files (x86)\MSBuild\12.0\Bin\MSBuild.exe" "$(SolutionDir)deployxbap.xml" /target:publish

Marge.exeはXBAPのキャッシュをクリアしている。

http://stackoverflow.com/questions/1919625/msbuild-doesnt-respect-publishurl-property-for-my-clickonce-app

ASP.NET MVCからどうXBAPにデータを渡すべきか?

セッションは無理。
クエリーストリングを使うか、ブラウザーのクッキーつかうか、どっちか。

クエリーストリングを使う方法

1.XBAPプロジェクトのプロパティで「発行」のオプションを押す
xbap002.png

2.マニフェストで「URLパラメータをアプリケーションに渡すことを許可する」
xbap003.png

3.XBAPプロジェクトに「System.Deployment」を参照設定する。

4.ASP.NET MVCプロジェクトからXBAPのパスを呼ぶときにパラメータを与える。

    <iframe height="130"
        width="100%"
        src="/xbap/WpfBrowserApplication1.xbap?param=1" />

5.XBAP側の実装で以下のようにするとクエリー付きのURLがとれるので解析しとく。

Uri launchUri = System.Deployment.Application.ApplicationDeployment.CurrentDeployment.ActivationUri;

セッションを使う方法

1.ASP.NET MVCプロジェクトのビューで以下のような実装をする。ASPXの例だが、razorとかでもできるはず。

    <%
        // Construct the cookies.
HttpCookie cookie = new HttpCookie("MyData");
cookie.Value = HttpUtility.HtmlEncode("MyData");

//Add to the response stream
Response.Cookies.Add(cookie);
    %>

2.XBAPプロジェクト側は次のような実装で、クッキーの中身をとる

            string cookieString = "";
            try
            {
                String cookie = Application.GetCookie(System.Windows.Interop.BrowserInteropHelper.Source);
                cookieString = cookie;
            }
            catch (Exception)
            {
            }

http://bytes.com/topic/net/answers/844652-pass-parameters-xbap-webpage

JSLint for Visual Studioを改造する

背景

JsLint for VisualStudioはJavaScriptの静的解析を行うjslintやjshintをVisualStudio上で動かすVisualStudio用のアドインである。

https://jslint4vs2010.codeplex.com/

しかしながら、2012年が最後のリリースでメンテナンスをしていない。
そのため、以下のようなコードはエラーとなり、オプションで回避もできない。

switch (1) {
    case 1:
        x = 3;
        break;
    case 2:
        x = 4;
        break;
    case 3:
        x = 5;
        break;
    default:
        x = 3;
        break;
}

/* 改造前のただしい書き方
switch (1) {
case 1:
    x = 3;
    break;
case 2:
    x = 4;
    break;
case 3:
    x = 5;
    break;
default:
    x = 3;
    break;
}*/

https://jslint4vs2010.codeplex.com/workitem/1446

このドキュメントではこれを何とかするものである。

JSLint.VS2012.vsixの中身

VSIXはVisualStudioの拡張配置である。このVSIXはzipファイルにすぎない。
拡張子をzipにしてみよう。

次のような内容が確認できる。

jslint1.png

JSLint.Framework.dllが実際のjslintなどの処理を行っているプログラムである。
このDLLはjavascriptファイルをリソースとしてもっており、そのJavaScriptをNoesis.Javascript.dllを利用して実行し、静的解析を行っている。

JSLint.Framework.dllの改造方法

JSLint.Framework.dllの使用方法

JSLint.Framework.dllを参照して以下のようなプログラムを実行する。

using System;
using System.Collections.Generic;
using JSLint.VS2010.LinterBridge;
using JSLint.VS2010.OptionClasses;
namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            var jslint = new JSLint.VS2010.LinterBridge.JSLinter();
            var option = new JSLintOptions();
            option = JSLintOptions.Default;
            option.SelectedLinter = Linters.JSHint;
            string script = @"
var x = 9;
switch (1) {
    case 1:
        x = 3;
        break;
    case 2:
        x = 4;
        break;
    case 3:
        x = 5;
        break;
    default:
        x = 3;
        break;
}
/* 改造前のただしい書き方
switch (1) {
case 1:
    x = 3;
    break;
case 2:
    x = 4;
    break;
case 3:
    x = 5;
    break;
default:
    x = 3;
    break;
}*/
x = x * 2;
";
            List<JSLintError> list = jslint.Lint(script, option, true);
            foreach (var x in list)
            {
                Console.WriteLine(x.Line + ":" + x.Message);
            }
        }
    }
}

おそらく、jshintでエラーが発生するだろうが、これをなんとかすればよい。

.NETのDLLの埋め込みリソースを取得する

VisualStudioの開発者コマンド プロンプトから以下のコマンドを実行すると、埋め込みリソースが展開される。

ildasm ../JSLint.Framework.dll /out=JSLint.Framework.il

jslint2.png

jslintの比較方法を修正する。

JSLint.Framework.JS.jshint.jsを以下のように修正する。07-25-2015でコメントを打っている箇所が修正箇所だ。

    blockstmt("switch", function () {
        var t = nexttoken,
            g = false;
        funct["(breakage)"] += 1;
        advance("(");
        nonadjacent(this, t);
        nospace();
        this.condition = expression(20);
        advance(")", t);
        nospace(prevtoken, token);
        nonadjacent(token, nexttoken);
        t = nexttoken;
        advance("{");
        nonadjacent(token, nexttoken);
        indent += option.indent;
        this.cases = [];
        for (;;) {
            switch (nexttoken.id) {
            case "case":
                switch (funct["(verb)"]) {
                case "break":
                case "case":
                case "continue":
                case "return":
                case "switch":
                case "throw":
                    break;
                default:
                    // You can tell JSHint that you don't use break intentionally by
                    // adding a comment /* falls through */ on a line just before
                    // the next `case`.
                    if (!ft.test(lines[nexttoken.line - 2])) {
                        warning(
                            "Expected a 'break' statement before 'case'.",
                            token);
                    }
                }
                // indentation(-option.indent); m.ita 07-25-2015 for ignore switch indent.
                advance("case");
                this.cases.push(expression(20));
                increaseComplexityCount();
                g = true;
                advance(":");
                funct["(verb)"] = "case";
                break;
            case "default":
                switch (funct["(verb)"]) {
                case "break":
                case "continue":
                case "return":
                case "throw":
                    break;
                default:
                    if (!ft.test(lines[nexttoken.line - 2])) {
                        warning(
                            "Expected a 'break' statement before 'default'.",
                            token);
                    }
                }
                //indentation(-option.indent);  m.ita 07-25-2015 for ignore switch indent.
                advance("default");
                g = true;
                advance(":");
                break;
            case "}":
                indent -= option.indent;
                indentation();
                advance("}", t);
                if (this.cases.length === 1 || this.condition.id === "true" ||
                        this.condition.id === "false") {
                    if (!option.onecase)
                        warning("This 'switch' should be an 'if'.", this);
                }
                funct["(breakage)"] -= 1;
                funct["(verb)"] = undefined;
                return;
            case "(end)":
                error("Missing '{a}'.", nexttoken, "}");
                return;
            default:
                indent += option.indent;  //m.ita 07-25-2015 for ignore switch indent.
                if (g) {
                    switch (token.id) {
                    case ",":
                        error("Each value should have its own case label.");
                        return;
                    case ":":
                        g = false;
                        statements();
                        break;
                    default:
                        error("Missing ':' on a case clause.", token);
                        return;
                    }
                } else {
                    if (token.id === ":") {
                        advance(":");
                        error("Unexpected '{a}'.", token, ":");
                        statements();
                    } else {
                        error("Expected '{a}' and instead saw '{b}'.",
                            nexttoken, "case", nexttoken.value);
                        return;
                    }
                }
                indent -= option.indent;  //m.ita 07-25-2015 for ignore switch indent.
            }
        }
    }).labelled = true;

.NETの埋め込みリソースを変更してDLLを作り直す

JavaScriptを修正したら、以下のコマンドでDLLを作り直す。

ilasm JSLint.Framework.il /dll

動作確認

JSLint.Framework.dllを使用したプログラムを再実行すれば、switchのインデントでjslintが文句をいわなくなったことが確認できる。

参考

Is it possible to Add/Remove/Change an embedded resource in .NET DLL?
http://stackoverflow.com/questions/6545858/is-it-possible-to-add-remove-change-an-embedded-resource-in-net-dll

ひとこと

自分でメンテできなかったり、金でメンテさせることができないツールを安易に採用してはいけない。

なお、お助け料一億万円でいいぞ!ローンも可。

Microsoft Fakesを利用したテストコードの記述

Microsoft Fakesとは?

本来、プログラムコードはまっとうなプログラマーが、まっとうなスケジュールで設計、実装すれば自然とテストしやすくなります。

しかしながら、我々は楽園には住んでいないので、さまざまな理由で名状しがたきプログラムコードは作成され、厳しい選択肢を突き付けられます。

選択① トムデマルコのような管理者がプログラムを修正するようなスケジュールを引き直す。
選択② オブジェクト指向を理解した増員が来て助けてくれる。
選択③ 直せない。現実は非情である。

Microsoft Fakesはこのような非情な現実を突き付けられた者にとっての一助になります。

MicrosoftFakesのShim機能を使用することで、複雑に依存した機能を切り離して特定の箇所のみテストを実施することが可能になります。

unit004.png

Microsoft Fakes を使用したテストでのコードの分離
https://msdn.microsoft.com/ja-jp/library/hh549175.aspx

たとえば、データベースや別システムに通信するような機能のテストであっても、データベースや別システムがテストに都合のいいデータを返すように偽装できます。

たとえば、現在時刻に依存したテスト対象や、ユーザ名に依存したものでもテストに都合のいい現在時刻やユーザ名を偽装して返すことができます。

これにより、テスト対象に手を加えることなくテストを実施することが可能になります。

ただし、VisualStudio2013ではPremiume以上、VisualStudio2015ではEnterpriseのエディションが必要になります。

また基本的なVisualStudioでのテストコードの書き方は下記を参照してください。
http://qiita.com/mima_ita/items/55394bcc851eb8b6dc24

Microsoft Fakesを試す

環境構築

VisualStudioEnterpriseは下記のページから無料試用版を取得することができます。
https://www.visualstudio.com/ja/downloads/

なお、75万円くらいするのでなかなかハードルが高いです。

簡単なチュートリアル

(1)以下のような構成のプロジェクトを用意する
unit001.png

プロジェクト名 説明
ClassLibrary1 テスト対象
UnitTestProject1 テストプログラム

テスト対象

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ClassLibrary1
{
    public class Class1
    {
        private int a;
        public Class1(int a)
        {
            this.a = a;
        }
        public int Cal(int x, int y)
        {
            return a + x + y;
        }

    }

    public class Class2
    {
        Class1 c1 = new Class1(5);
        public int CallCal()
        {
            return c1.Cal(1, 2);
        }
    }
}

(2)テストプロジェクトの参照から偽装したいアセンブリを選択して右クリックをして「Fakesアセンブリに追加」を実行する。
unit002.png

(3)Fakesフォルダに拡張子がfakesのファイルが作成される。
unit003.png

構成管理にあげる場合は、このfakesファイルを上げること。

(4)テストコードの実装

テストコードの例

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.QualityTools.Testing.Fakes;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                var c2 = new ClassLibrary1.Class2();

                ClassLibrary1.Fakes.ShimClass1.AllInstances.CalInt32Int32 = (ClassLibrary1.Class1 obj, int x, int y) =>
                {
                    // c1.Cal(1, 2); が実行されていることを確認する
                    Assert.AreEqual(c2.c1, obj);
                    Assert.AreEqual(1, x);
                    Assert.AreEqual(2, y);

                    return 999999;
                };

                var act = c2.CallCal();

                // Shimで偽装した値が返ってくることを確認する。
                Assert.AreEqual(999999, act);
            }
        }
    }
}

(5)テストを実施するとShimで偽装した値が取得できることが確認できる。

Shimの使い方

Shimの対象

public/private/protectedのあらゆるスコープのメソッドを偽装できるが、privateの内部クラスや型をパラメータもしくは戻り値にする関数は偽装はできない。

偽装対象のクラス

    public class Class3
    {
        public int test1()
        {
            return 1;
        }

        public static int test2()
        {
            return 2;
        }
        private int test3()
        {
            return 3;
        }
        protected int test4()
        {
            return 5;
        }

        public class Test5Ret
        {
        }

        private Test5Ret test5()
        {
            return new Test5Ret();
        }

        private class Test6Ret
        { }

        // パラメータまたは戻り値が公開された型でないので偽装ができない。
        private Test6Ret test6()
        {
            return new Test6Ret();
        }
    }

Shimによる偽装の方法

        [TestMethod]
        public void TestMethod4()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                var c2 = new ClassLibrary1.Class2();

                ClassLibrary1.Fakes.ShimClass3.AllInstances.test1 = (ClassLibrary1.Class3 obj) =>
                {
                    return 11;
                };

                ClassLibrary1.Fakes.ShimClass3.test2 = () =>
                {
                    return 22;
                };

                ClassLibrary1.Fakes.ShimClass3.AllInstances.test3 = (ClassLibrary1.Class3 obj) =>
                {
                    return 33;
                };

                ClassLibrary1.Fakes.ShimClass3.AllInstances.test4 = (ClassLibrary1.Class3 obj) =>
                {
                    return 44;
                };

                ClassLibrary1.Fakes.ShimClass3.AllInstances.test5 = (ClassLibrary1.Class3 obj) =>
                {
                    return null;
                };

                //これは作られない。
                //ClassLibrary1.Fakes.ShimClass3.AllInstances.test6 = (ClassLibrary1.Class3 obj) =>
                //{
                //    return null;
                //};

            }
        }

Shimの関数名の作成ルール

偽装するための関数は以下の命名規則で作成される。

インスタンスのメソッドの場合:
【テスト対象の名前空間】.Fakes..Shim【クラス名】.AllInstances.【関数名】【パラメータ1の型】【パラメータ2の型】..【パラメータnの型】(クラスのインスタンス,パラメータ1,パラメータ2...)

スタティックのメソッドの場合:
【テスト対象の名前空間】.Fakes..Shim【クラス名】..【関数名】【パラメータ1の型]【パラメータ2の型】..【パラメータnの型】(パラメータ1,パラメータ2...)

プロパティの場合:
【テスト対象の名前空間】.Fakes..Shim【クラス名】.AllInstances.Get【プロパティ名】(クラス)
【テスト対象の名前空間】.Fakes..Shim【クラス名】.AllInstances.Set【プロパティ名】【パラメータの型】(クラスのインスタンス,パラメータ)

偽装対象の関数のパラメータ数分だけ名称がながくなるので注意が必要。
古いVS2012だと長すぎると偽装用の関数が作成されない。(256あたり?)
VS2015だと、後方の文字を切って適正な文字に変換しているようだ。

現在日付の偽装

現在日付はテストをするうえでやっかいだが、これも偽装できる。

(1)System.dllのfakesアセンブリを追加する。
unit005.png

unit006.png

(2)System.DateTime.Nowを偽装する。

        [TestMethod]
        public void TestMethod2()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                DateTime exp = new DateTime(2000, 10, 5);
                System.Fakes.ShimDateTime.NowGet = () =>
                {
                    return exp;
                };

                var act = System.DateTime.Now;

                Assert.AreEqual(exp, act);
            }
        }

現在ユーザやマシン名などのSysetem.Environmentを偽装する。

(1)Fakesフォルダの「mscorlib.fakes」を開く
unit007.png

(2)ShimGenerationにSystem.Environmentを追加する。

<Fakes xmlns="http://schemas.microsoft.com/fakes/2011/">
  <Assembly Name="mscorlib" Version="4.0.0.0"/>
  <ShimGeneration>
    <Add FullName="System.Environment"/>
  </ShimGeneration>  
</Fakes>

(3)リビルドを行う。

(4)下記のように偽装を行う。


        [TestMethod]
        public void TestMethod3()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                System.Fakes.ShimEnvironment.UserNameGet = () =>
                {
                    return "user";
                };
                System.Fakes.ShimEnvironment.MachineNameGet = () =>
                {
                    return "machine";
                };

                Assert.AreEqual("user", Environment.UserName);
                Assert.AreEqual("machine", Environment.MachineName);
            }
        }

内部クラスのメソッドを偽装

内部クラスはShim親クラス名.Shim内部クラス名という形で偽装が可能である。

内部クラス

    public class Class5
    {
        public class Class5Inner
        {
            public int Test5Inner()
            {
                return 5;
            }
        }
    }

内部クラスのメソッドを偽装

        [TestMethod]
        public void TestMethod5()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                ClassLibrary1.Fakes.ShimClass5.ShimClass5Inner.AllInstances.Test5Inner = (ClassLibrary1.Class5.Class5Inner obj) =>
                {
                    return 99;
                };
                var o = new ClassLibrary1.Class5.Class5Inner();
                Assert.AreEqual(99, o.Test5Inner());
            }
        }

ベースクラスのメソッドを偽装

ベースクラスの偽装はベースクラス自体を偽装する必要がある。継承先のShimにはベースクラスのメソッドは存在しない。

テスト対象

    public class Class6Base
    {
        protected int Test6Base()
        {
            return 6;
        }
    }

    public class Class6: Class6Base
    {
        public int Test6BasePlus1()
        {
            return base.Test6Base() + 1;
        }
    }

テストコード

        [TestMethod]
        public void TestMethod6()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                ClassLibrary1.Fakes.ShimClass6Base.AllInstances.Test6Base = (ClassLibrary1.Class6Base obj) =>
                {
                    return 99;
                };
                var o = new ClassLibrary1.Class6();
                Assert.AreEqual(100, o.Test6BasePlus1());
            }
        }

ジェネリックメソッドの偽装方法

ジェネリックメソッドの偽装方法は型を指定したShimを作成する必要がある。

テスト対象

    public static class Class7
    {
        /// <summary>
        /// xとyを入れ替える
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="x"></param>
        /// <param name="y"></param>
        static void Swap<T>(ref T x, ref T y)
        {
            T temp;
            temp = x;
            y = x;
            x = temp;
        }

        public static void Test7()
        {
            int x = 1;
            int y = 9;
            Swap(ref x, ref y);
        }
    }

テストコード

        [TestMethod]
        public void TestMethod7()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                bool callSwap = false;
                ClassLibrary1.Fakes.ShimClass7.SwapOf1M0RefM0Ref<int>((ref int x, ref int y) =>
                {
                    // 
                    callSwap = true;
                    Assert.AreEqual(1, x);
                    Assert.AreEqual(9, y);
                    return;
                });
                ClassLibrary1.Class7.Test7();
                Assert.AreEqual(true, callSwap);
            }
        }

ジェネリッククラスの偽装

ジェネリッククラスのShimを使うにはShimクラス名<型>を定義する必要がある。

偽装対象

    public class Class8<K, V>
    {
        public K key {set; get;}
        public V value { set; get; }

        public void Log()
        {
            Console.WriteLine(key.ToString() + value.ToString());
        }
    }

テストコード

       [TestMethod]
        public void TestMethod8()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                bool callLog = false;

                ClassLibrary1.Fakes.ShimClass8<int,string>.AllInstances.Log = (ClassLibrary1.Class8<int,string> obj) => 
                {
                    // 
                    callLog = true;
                };
                var o = new ClassLibrary1.Class8<int, string>();
                o.Log();
                Assert.AreEqual(true, callLog);
            }
        }

どう利用するか?

Shimは強力なツールであります。これにより多くのコードを通すことが可能になり、カバレッジも100%近くにすることができるかもしれません。

しかし、適切に実行が検証されないテストは、無意味です。
ここでは、この強力なツールをどう利用してコードの検証を行うかを考えてみます。

パラメータやShimを実行した際のオブジェクトの状態を確認するようにする。

Shimのパラメータをチェックすることで、渡された値が期待値通りか?実行時のオブジェクトが期待通りの状態か検査すべきです。

            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                ClassLibrary1.Fakes.ShimClass1.AllInstances.CalInt32Int32 = (ClassLibrary1.Class1 obj, int x, int y) =>
                {
                    // c1.Cal(1, 2); が実行されていることを確認する
                    Assert.AreEqual(1, x);
                    Assert.AreEqual(2, y);
                    // 実行時のClass1.xxxxが123であることを確認する
                    Assert.AreEqual(123, obj.xxxx);

                    return 999999;
                };
            }

実行回数または実行されたか否かの検査を行う

Shimの実行回数をカウントとするか、フラグで管理することによりShimが期待の回数実行されるかどうかを確認します。

        public void TestMethod10()
        {
            // ShimsContextのブロック内のみ偽装する。
            using (ShimsContext.Create())
            {
                int callCalCnt = 0;
                ClassLibrary1.Fakes.ShimClass1.AllInstances.CalInt32Int32 = (ClassLibrary1.Class1 obj, int x, int y) =>
                {
                    // 実行した回数を数える
                    ++callCalCnt;
                    return 999999;
                };

                var c2 = new ClassLibrary1.Class2();
                var act = c2.CallCal();
                Assert.AreEqual(1, callCalCnt, "c1.Calが1回実行されていることを確認");
            }
        }

複数回実行されるShimの場合

Shimが複数回実行される場合は、以下のようにリストで期待するパラメータとShimが返す値を設定します。

テスト対象

    public class Class11
    {
        private int x = 0;
        private int Inc(int i)
        {
            x += i;
            return x;
        }

        public int Test11()
        {
            int i = 0;
            i = Inc(5);
            i = Inc(i);
            i = Inc(i);
            return i;
        }
    }

テスト対象

        class ShimIncData
        {
            public int expParam { set; get; }
            public int returnVal { set; get; }
        }

        [TestMethod]
        public void TestMethod11()
        {
            using (ShimsContext.Create())
            {
                int callIncCnt = 0;
                var incShimDataList = new List<ShimIncData>();
                incShimDataList.Add(new ShimIncData
                {
                    expParam = 5,
                    returnVal = 6
                });
                incShimDataList.Add(new ShimIncData
                {
                    expParam = 6,
                    returnVal = 7
                });
                incShimDataList.Add(new ShimIncData
                {
                    expParam = 7,
                    returnVal = 8
                });

                ClassLibrary1.Fakes.ShimClass11.AllInstances.IncInt32 = (ClassLibrary1.Class11 obj, int i) =>
                {
                    // 実行した回数を数える
                    int ix = callIncCnt;
                    ++callIncCnt;

                    Assert.AreEqual(incShimDataList[ix].expParam, i);

                    // 
                    return incShimDataList[ix].returnVal;
                };

                var o = new ClassLibrary1.Class11();
                var act = o.Test11();

                Assert.AreEqual(3, callIncCnt, "Incが3回実行されること");
                Assert.AreEqual(8, act);
            }
        }

今回は回数で返す値を替えたが、分岐を作ってパラメータが~だったら~を返すという方法でもいいでしょう。

まとめ

Microsoft Fakesを使用するとテスト対象の依存先のコードを偽装することができます。
これにより、テスト対象に手を加えずにテストコードを記述することができます。

Pythonで「まめバス」のオープンデータを使ってみよう

草津市都市計画交通政策課では草津市のコミュニティバス「まめバス」のオープンデータ化を行っています。
http://www.city.kusatsu.shiga.jp/kurashi/kotsudorokasen/mamebus/opendata.html

ここでは、この「まめバス」のデータを取り扱ってみます。
目的としては、自動で、全てのデータをダウンロードして、適切にDataBaseを構築してバスデータがWebで使用できるようにします。
この際、Excelデータは、Windowsだけでなく、Linuxでも解析するものとします。

これにより、多くのレンタルサーバーなどで、(一定の変更の範囲内では)人手を介さず自動で、最新のデータを使用できることになります。

デモ
http://needtec.sakura.ne.jp/bus_data/kusatu.html

bus7.png

Github
https://github.com/mima3/bus_data

以下のコマンドでデータをダウンロードしてDBの構築は行います。

python import.py application.ini

データが更新された場合も、このコマンドで最新を取り込むことができます。cronなどで定期的に実行するといいでしょう。

データの説明

各路線は以下の3つの構成でなりたっています。

名称 形式 説明
時刻表 Excel 各バス亭の到着時刻を記述したデータです。
平日、土曜日と曜日により到着時刻がことなる場合があります
停留所 csv 停留所の名前、読み、座標が格納されたCSVデータです。
同一路線において右回り、左回りと複数データが存在する可能性があります。
路線図 shape 路線図の形状を表したshapeファイルです。この測地系は「平面直角2000(6系)」であることに注意してください。
同一路線において右回り、左回りと複数データが存在する可能性があります。

データを取り扱う際に注意する点がいくつか存在します。

同じバス停名で複数のバス停が存在する。

同名のバス停が複数存在します。

たとえば、M04_stops_ccw.csvを見てください。
野村運動公園口が2行存在します。
135.954709,35.023382に存在する野村運動公園口と、135.954445,35.023323に存在する野村運動公園口です。

同じ位置のバスは同じルートで複数回停車する

同じ位置のバス停であっても、同じルートで複数回停車する場合があります。

たとえば、M04_stops_ccw.csvを見てください。
草津駅西口は、1番目と37番目に停車します。

停車順番はCSVの2列目に記載されています。

CSVとExcelの表記のゆれ

ExcelとCSVでバス停名が異なる場合があります。
改行が入っているとか、半角・全角の違いどころか、名称が違うものがいくつかあります。

csv excel
山田小学校前 山田小学校
木ノ川東 木川東
西渋川1丁目 西渋川一丁目
野村八丁目 野村8丁目
新堂中学校前 新堂中学校

時刻表の曜日の表し方の不統一

普通、Excelのデータの配置は同じようになるもんですが、ワークブック毎に異なっています。

M01_stop_times.xlsxを見てみましょう。
bus3.png

このワークブックでは「●」の有無で土曜日か、平日かを判断しています。
しかし、別のM03_stop_times.xlsxを見てみましょう。

bus4.png

ここではシート毎に曜日を分けています。

ブック名 曜日の判定方法
M01_stop_times.xlsx ●による判定
M02_stop_times.xlsx ●による判定
M03_stop_times.xlsx シート分割
M04_stop_times.xlsx シート分割
M05_stop_times.xlsx 曜日についての言及なし

このことから察することができると思いますが、データ開始位置はブックごとに違うと見なしたほうがいいでしょう。

バス停名と時刻の間の空行

バス停名の次行から時刻が入っているので、そこから検索していき、1行すべてがブランクだったら時刻表が終わったと判定したくなるかと思います。

しかしながら、それはできません。
M04_stop_times.xlsxの山田線(木ノ川循環:左回り)を見てみましょう。
バス停名の次行まるまる空白で、その次の行からデータが始まっています。

拡張性のないデータの配置

以下のシートの場合、バスの本数が増えても単純にデータ量が変わるだけなので、Excelを解析する処理に変更はありません。
bus6.png

しかし、次の行を考えてみましょう。
bus5.png

この例だと、データ量が増えると下部のデータ開始位置もずれるため、処理を変更する必要がでてきます。

Pythonで取り扱う例

Excelファイルを取り扱う

PythonでExcelを取り扱うには、xlrdを使用します。
https://github.com/python-excel/xlrd

いくつか、このライブラリを使用したサンプルがググると出てきますが、基本的に、公式のサンプルコードを参考に作成した方がいいでしょう。

https://github.com/python-excel/xlrd/blob/master/scripts/runxlrd.py

たとえば、以下のような実装例がよくあります。

from xlrd import open_workbook
wb = open_workbook('test_err.xlsx')
for sh in wb.sheets():
  for row in range(sh.nrows):
    values = []
    for col in range(sh.ncols):
      v = sh.cell(row,col).value
      if not isinstance(v, basestring):
        v = str(v)
      v = v + ':' + str(sh.cell(row,col).ctype)
      values.append(v)
    print ','.join(values)

XLS拡張子や、セルの結合のないxlsxでは上記のコードは何の問題もなく動作します。
しかし、以下のようなセルの結合があるシートを操作するとエラーになります。

bus2.png

エラー内容

Traceback (most recent call last):
  File "test2.py", line 7, in <module>
    v = sh.cell(row,col).value
  File "C:\Python27\lib\site-packages\xlrd-0.9.3-py2.7.egg\xlrd\sheet.py", line
399, in cell
    self._cell_types[rowx][colx],
IndexError: array index out of range

どうも、行によって列の数がことなっており、行ごとに列数を取得する必要があります。
以下のようにrow_lenで行毎に列数を取得すれば、この問題は回避できます。

from xlrd import open_workbook
wb = open_workbook('test_err.xlsx')
for sh in wb.sheets():
  for row in range(sh.nrows):
    values = []
    for col in range(sh.row_len(row)):
      v = sh.cell(row,col).value
      if not isinstance(v, basestring):
        v = str(v)
      v = v + ':' + str(sh.cell(row,col).ctype)
      values.append(v)
    print ','.join(values)

このほか、日付表示の方法についても、runxlrd.pyでは実装しているので、とりあえず一度は読んだ方がいいでしょう。

どうデータを取り込むか

先に述べたように、気分でExcelを作っている節があるので、それぞれに柔軟に対応する必要があります。
そこで、どのようにデータを取り込むかをJSONの設定ファイルに保存しておき、それをみてデータを取り込むようにしました。

https://github.com/mima3/bus_data/blob/master/data/kusatu.json

ダウンロード後の処理の分岐

今回は圧縮済みのデータと、圧縮していないデータが混ざっています。
そこで、設定ファイルのdownloadにてダウンロード後の処理を記載しています。

data/kusatu.json

    "download" : {
        "http://www.city.kusatsu.shiga.jp/kurashi/kotsudorokasen/mamebus/opendata.files/M01_stop_times.xlsx" : "save_local",
        "http://www.city.kusatsu.shiga.jp/kurashi/kotsudorokasen/mamebus/opendata.files/M01_stops_ccw.csv" : "save_local",
        "http://www.city.kusatsu.shiga.jp/kurashi/kotsudorokasen/mamebus/opendata.files/M01_shapes.zip" : "expand_zip",

save_localはローカルディスクに保存する。
expand_zipは保存後解凍を試みる処理を実行します。

実際のコードは下記を参照してください。
https://github.com/mima3/bus_data/blob/master/downloader.py

CSVとEXCELの表記の揺れに対応

CSVとExcelの表記のゆれに対応します。

基本的なルールは以下の通りです。
・バス亭名を設定ファイルのconvert_ruleに合わせて変換する
・改行を取りのぞく
・半角を全角にする。

バス停名の変換

data/kusatu.json

    "convert_rule" : {
        "山田小学校前": "山田小学校",
        "木ノ川東":"木川東",
        "西渋川1丁目": "西渋川一丁目",
        "野村八丁目": "野村8丁目",
        "新堂中学校前": "新堂中学校"
    },

bus_data_parser.py

def convert_bus_stop_name(rule, bus_stops):
    for bus_stop in bus_stops:
        if bus_stop['stopName'] in rule:
            bus_stop['stopName'] = rule[bus_stop['stopName']

改行の除去・半角を全角にする

bus_data_parser.py

def get_bus_timetable(wbname, sheetname, stop_offset_row, stop_offset_col, stopdirection, timetable_offset_row, timetable_offset_col, chk_func):
    xls = xlsReader(wbname, sheetname)
    stop_name_list = []
    if stopdirection == DataDirection.row:
        busdirection = DataDirection.col
    else:
        busdirection = DataDirection.row
    xls.set_offset(stop_offset_row, stop_offset_col)
    while True:
        v = xls.get_cell()
        if not v:
            break
        v = zenhan.h2z(v)
        v = v.replace('\n', '')
        stop_name_list.append(v)
        xls.next_cell(stopdirection)

半角、全角の変換にはzenhanを利用しています。
https://pypi.python.org/pypi/zenhan

CSV、EXCEL、Shapeファイルのインポートルールの記述

各ファイルをどのようにインポートするかを以下のように指定します。

    "import_rule" : [
        {
            "operation_company" : "草津市",
            "line_name" : "商店街循環線",
            "shape" : "M01_shapes/M01.shp",
            "srid" : 2448 , 
            "timetables" : [
                {
                    "route" : "Route1L",
                    "routeName" : "商店街循環線",
                    "bus_stops" : "M01_stops_ccw.csv",
                    "weekday_timetable" : {
                        "workbook" : "M01_stop_times.xlsx",
                        "sheetname" : "M01_stop_times",
                        "stop_offset_row" : 6,
                        "stop_offset_col" : 3,
                        "timetable_offset_row" : 7,
                        "timetable_offset_col" : 3
                    },
                    "saturday_timetable" : {
                        "workbook" : "M01_stop_times.xlsx",
                        "sheetname" : "M01_stop_times",
                        "stop_offset_row" : 6,
                        "stop_offset_col" : 3,
                        "timetable_offset_row" : 7,
                        "timetable_offset_col" : 3,
                        "check_func" : "check_shoutengai_saturday"
                    },
                    "holyday_timetable" : {
                    }
                }
            ]
        }, // 略
        {
            "operation_company" : "草津市",
            "line_name" : "山田線(北山田循環)",
            "shape" : "M03_shapes/M03.shp",
            "srid" : 2448 , 
            "timetables" : [
                {
                    "route" : "Route3R",
                    "routeName" : "北山田循環線 右回り",
                    "bus_stops" : "M03_stops_cw.csv",
                    "weekday_timetable" : {
                        "workbook" : "M03_stop_times.xlsx",
                        "sheetname" : "M03_stop_times(平日)",
                        "stop_offset_row" : 6,
                        "stop_offset_col" : 3,
                        "timetable_offset_row" : 7,
                        "timetable_offset_col" : 3
                    },
                    "saturday_timetable" : {
                        "workbook" : "M03_stop_times.xlsx",
                        "sheetname" : "M03_stop_times(土曜)",
                        "stop_offset_row" : 6,
                        "stop_offset_col" : 3,
                        "timetable_offset_row" : 7,
                        "timetable_offset_col" : 3
                    },
                    "holyday_timetable" : {
                    }
                },
                {
                    "route" : "Route3L",
                    "routeName" : "北山田循環線 左回り",
                    "bus_stops" : "M03_stops_ccw.csv",
                    "weekday_timetable" : {
                        "workbook" : "M03_stop_times.xlsx",
                        "sheetname" : "M03_stop_times(平日)",
                        "stop_offset_row" : 14,
                        "stop_offset_col" : 3,
                        "timetable_offset_row" : 15,
                        "timetable_offset_col" : 3
                    },
                    "saturday_timetable" : {
                        "workbook" : "M03_stop_times.xlsx",
                        "sheetname" : "M03_stop_times(土曜)",
                        "stop_offset_row" : 14,
                        "stop_offset_col" : 3,
                        "timetable_offset_row" : 15,
                        "timetable_offset_col" : 3
                    },
                    "holyday_timetable" : {
                    }
                }
            ]
        }, // 略

shapeファイルに関係するのは「shape」と「srid」です。
shapeにはshapeファイル名、sridには測地系を記述します。

csvファイルに関係するのは「bus_stops」です。
bus_stopsにはCSVファイル名を記述します。

Excelファイルは、平日、土曜日、休日毎に記載します。
workbookにワークブック名
sheetnameにシート名
stop_offset_row、stop_offset_colにバス停名が記載される開始位置、
timetable_offset_row、timetable_offset_colに時刻が記載される開始位置を記載します。
check_funcは省略可能な項目で、時刻表を1行読み込むたびに実行されるコールバック関数を指定します。

ここでは、下記のように特定の列の値をチェックして、無効なデータであれば、Falseを返し行を無視します。これは土曜日の判定に使用しています。

import.py

class BusParserCallBack(object):
    def check_shoutengai_saturday(self, workbook, sheet, busrow, buscol, item):
        if sheet.cell(busrow  - 1, 2 - 1).value:
            return True
        else:
            return False

Shapeファイルの取り扱い

Pythonの場合は、pyshpを使うといいでしょう。
下記を参考にしてください。

Pythonで国土数値情報のShapeFileを操作してデータベースにインポートしてみる
http://qiita.com/mima_ita/items/e614a281807970427921

測地系の変換

まめバスのshapeファイルの測地系は「平面直角2000(6系)」であり、SRIDだと、2448になります。
これを、世界測地系に変換しなければなりません。
この変換は結構めんどくさいのですが、SpatiaLiteなどのジオメトリを扱うDBだと、簡単に対応できます。

Spatialiteの場合は次のようなSQLを実行すると、返還ができます。

select AsText(Transform(GeomFromText('POINT(-4408.916645 -108767.765479)', 2448), 4326))

pythonのコードでは次のようになります。

bus_db.py

        for timetable in timetables:
            database_proxy.get_conn().execute(
                """
                INSERT INTO RouteTable
                  (metaData_id, operationCompany, lineName, route, routeName, geometry)
                VALUES(?, ?,?,?,?,Transform(GeometryFromText(?, ?),?))
                """,
                (
                    meta_id,
                    operation_company,
                    line_name,
                    timetable['route'],
                    timetable['routeName'],
                    routedict[timetable['route']], src_srid, SRID
                )
            )

まとめ

このように、Pythonのライブラリを使えば特に意識しなくても「まめバス」のデータを使用することはできます。

ただし、データにかなり癖があり、機械解析を前提するような構成になっていないので、そこで苦労するでしょう。

もし、機械でデータを取り扱いやすくデータ側を改善するなら以下の点が必要だと思います。
・CSV,Excelなどの別ファイル間のデータの整合性をとる
・Excelを使うのはしょうがないとして書式は統一させる
・データが増えたときの考慮をおこなう。

AndroidでLiquidFunを使用して流体とか軟体を表現する。

LiquidFunは流体とか軟体を扱うためのライブラリで、Box2dをベースに実装しています。
基本的にC++で実装されていますが、JavaまたはJavaScriptからも使用できます。
今回はAndroidからJavaでLiquidFunを利用する方法について説明します。

device-2015-03-26-191808.png

LiquidFunTest
https://www.youtube.com/watch?v=kAaiJtDYa9Q

GitHub
https://github.com/mima3/LiquidFunTest

JavaScriptで使いたい場合は下記を参考にしてください。

流体とか軟体を扱えるLiquidFunをJavaScriptで100%利用する
http://qiita.com/mima_ita/items/3e903f89952aea07b924

Javaで使えるようにする

以下からliquidfun-x.x.x.zipをダウンロードして任意のフォルダに解凍します。
https://github.com/google/liquidfun/releases

JavaからLiquidfunを使用するには、SWIGとAndroid NDKを使用してビルドをする必要があります。
ここでは、Debian7を開発環境とします。

Android NDKのインストール方法

以下から、適切なNDKをダウンロードします。
https://developer.android.com/tools/sdk/ndk/index.html

ダウンロード後に、「android-ndk-r10d-linux-x86_64.bin」を実行すると「android-ndk-r10d」というフォルダが作成されます。

もしかしたら、「android-ndk-r10d-linux-x86_64.bin」実行時に下記のエラーが出るかもしれません。

./android-ndk-r10d-linux-x86_64.bin: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.14' not found (required by ./android-ndk-r10d-linux-x86_64.bin)

この場合は、libc6が古いので下記のページを参考に更新してください。

http://stackoverflow.com/questions/10863613/how-to-upgrade-glibc-from-version-2-13-to-2-15-on-debian

SWIGのインストール方法

SWINGは、C/C++で記述されたプログラムを様々な言語に接続するためのツールです。
Debianでは、apt-getでインストールすることができます。

apt-get install swig

あるいは、下記からソースコードを入手してビルドしてください。
http://www.swig.org/download.html

なお、3.0.5でビルドした場合エラーになります。
その対応については次項で解説します。

LiquidFunのビルド

liquidfun/Box2D/swigをビルドします。
公式のドキュメントは下記のURLです。
https://google.github.io/liquidfun/Building/html/md__building_android.html

・・・がSWIGについて記述がないようですので、以下のようにやるといいでしょう。

cd liquidfun/Box2D/swig
ndk-build APP_ABI=all

これにより以下のファイルが作成されます。

Javaのファイル
liquidfun/Box2D/swig/gen/com/google/fpl/liquidfun

libliquidfun.so,libliquidfun_jni.so
liquidfun/Box2D/swig/libs
soファイルはプラットフォーム毎に作成されます。

SWIGでエラーが発生した場合

3.0.5でビルドした場合、以下のエラーが発生しました。

Processing nested classes...
Generating wrappers...
[armeabi-v7a] Compile++ arm  : liquidfun_jni <= liquidfun_wrap.cpp
jni/../gen/cpp/armeabi-v7a/liquidfun_wrap.cpp: In member function 'virtual void SwigDirector_Draw::DrawPolygon(const b2Vec2*, int32, const b2Color&)':
jni/../gen/cpp/armeabi-v7a/liquidfun_wrap.cpp:707:52: error: exception handling disabled, use -fexceptions to enable
       throw Swig::DirectorException(jenv, swigerror);

この場合は、swig/jni/Android.mkに以下のフラグを追加します。

LOCAL_CFLAGS += -fexceptions

AndroidStudio1.1でのNDKを利用する

AndroidStudioの設定

AndroidStudio1.0と基本的に同じです。

Android NDK Sample with Android Studio 1.0
http://qiita.com/abekatsu/items/31459d11284623277668

1.local.propertyを修正します。

ndk.dir=C\:\\tool\\android-ndk-r10d

今回は、AndroidStudioの外部でビルドしたので、この設定は不要かもしれません。

2.Module.appのbuild.gradleの修正

apply plugin: 'com.android.application'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "jp.ne.needtec.liquidfuntest"
        minSdkVersion 10
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    sourceSets.main {
        jniLibs.srcDir 'src/main/libs'
        jni.srcDirs = []
        //jni.srcDirs = ['src/main/jni'] //disable automatic ndk-build call
    }
    productFlavors {

        'armeabi' {
            flavorDimension "abi"
            ndk {
                abiFilter "armeabi"
            }
        }

        'armeabi-v7a' {
            flavorDimension "abi"
            ndk {
                abiFilter "armeabi-v7a"
            }
        }

        'arm64-v8a' {
            flavorDimension "abi"
            ndk {
                abiFilter "arm64-v8a"
            }
        }

        'mips' {
            flavorDimension "mips"
            ndk {
                abiFilter "mips"
            }
        }

        'mips64' {
            flavorDimension "mips64"
            ndk {
                abiFilter "mips64"
            }
        }

        'x86' {
            flavorDimension "abi"
            ndk {
                abiFilter "x86"
            }
        }

        'x86_64' {
            flavorDimension "abi"
            ndk {
                abiFilter "x86_64"
            }
        }

        'fat' {
            flavorDimension "abi"

        }
    }

    project.ext.versionCodes = ['armeabi':1,
                                'armeabi-v7a':2,
                                'arm64-v8a':3,
                                'mips':5,
                                'mips64':6,
                                'x86':8,
                                'x86_64':9] //versionCode digit for each supported ABI, with 64bit>32bit and x86>armeabi-*

    // make per-variant version code
    applicationVariants.all { variant ->
        // assign different version code for each output
        variant.outputs.each { output ->
            output.versionCodeOverride =
                    project.ext.versionCodes.get(output.getFilter(com.android.build.OutputFile.ABI), 0) * 1000000 + defaultConfig.versionCode
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'
}

sourceSetsでjniをおくフォルダを指定するのと、productFlavorsでプラットフォームごとのビルドを行えるようにします。

3.libliquidfun.so,libliquidfun_jni.soのコピー
Debianで作成した「liquidfun/Box2D/swig/libs」からAndroidStudioのフォルダ「\AndroidStudioProjects\LiquidFunTest\app\src\main\libs」にコピーします。

4.swigで作成したJavaファイルのコピー
Debianで作成した「liquidfun/Box2D/swig/gen/com/google/fpl/liquidfun」からAndroidStudioのフォルダ「\AndroidStudioProjects\LiquidFunTest\app\src\main\java」にコピーします。

コードの例

以下のように、Liquidfunのオブジェクトの操作が行えます。

import com.google.fpl.liquidfun.World;
world = new World(0, -10);

その他のサンプルは下記を参照してください。
https://github.com/mima3/LiquidFunTest/blob/master/app/src/main/java/jp/ne/needtec/liquidfuntest/MainRenderer.java

実行方法

実行するプラットフォームごとに異なるapkを使用することに注意してください。
たとえば、VMPalyer + Android-x86を使用している場合は「Build Variants」で「x86Debug」を選択して実行します。

AndroidExe1.png

あるいは、SO-02G XPERIAで実行する場合は、「Build Variants」で「armeabiDebug」を選択して実行します。

まとめ

ここではLiquidFunをAndroidで動作させる方法について解説しました。
これにより、流体を利用した表現を行うことが可能になります。

しかしながら、デフォルトのJavaで使用できる関数はC++でサポートされている関数の一部にすぎません。
必要に応じて、b2Settings.swigなどを修正する必要があります。

WindowsのVisualStudioでDoxygenをビルドする方法

この記事ではWindowsのVisualStudioを用いてDoxygenのソースファイルをコンパイルする方法について解説する。

Compiling from source on Windows
http://www.stack.nl/~dimitri/doxygen/manual/install.html#install_src_windows

環境
VisualStudio2008

事前準備

任意のソースコードをダウンロードして解凍する。
https://codeload.github.com/doxygen/doxygen/zip/Release_1_8_9_1

Doxygenのビルドにはflexを使用している。
flexは字句解析のツールである。このツールはWindowsにも移植されている。
http://sourceforge.net/projects/winflexbison/

ダウンロードしたflexのフォルダにパスを通すか、doxygen-Release_1_8_9_1\winbuildにコピーする。

その後、下記のような改名処理を行う。

win_bison.exe→bison.exe
win_flex.exe→flex.exe

doxygen2.png

Doxygenプロジェクトのみをビルド

VisualStudioでソリューションを開いてDoxygenプロジェクトのみをビルドする。

なお、他のプロジェクトは別の依存ライブラリが必要なので今回は対象外とする。

・doxysearchにはxapianが必要
http://stackoverflow.com/questions/25209217/missing-xapian-library-in-doxygen-build

・doxywizardにはQt version 4が必要
http://qt-project.org/

文字コード関係のエラーが発生する場合...

VisualStudioはBOMなしのUTF-8の挙動が怪しい場合がある。
この場合は、プロジェクトで右クリックを押して、プロパティーを表示する。
そして、「Language」にて「Use English Only」を選択すればよい。

doxygen.png

Unicodeが必要な言語を全て排除できるので、円滑にデバッグを開始できる。

デバッグ実行

デバッグ実行時にコマンドライン引数が与えられるので、問題のdoxyfileを指定してやれば、デバッグが可能になる。

doxygen3.png

ぼくのかんがえたさいきょーの提案方法

年度替わりにもなりまして新しい環境になる方もいらっしゃるかと思います。
すると、新しい環境の不備な点も目につき、「僕がこの職場を導く!」という夜神月チックな発想に行き着くかもしれません。

ここでは、新しい環境で、特に弁が立つわけでもない場合に、提案が通りやすい方法について考えてみます。

これは、銀の玉でもありませんし、詐欺師のような才覚を持っていらっしゃる方には不要なことかもしれませんが、参考程度にご覧ください。

進言ってのは難しい

仮に世の中に正しいという事が存在して、それを言えば通るっていう甘えをまず捨てましょう。

進言ってのは非常に難易度が高いものです。

むかしむかしの中国に韓非さんという思想家がおりました。
かの始皇帝もお気に入りの著書、韓非子には、説難という「進言するのって難しいYO」っていう篇がまるまる一つあります。

進言というのはむずかしい。
君主に説くには、まずこちらが十分な知識を身につけておかなければならない。また、自分の意見を口で言い表す弁舌がなければならない。また、いいたいことをはばからず、いってのける勇気がなければならない。どれもなかなか難しい。
しかし、本当にむずかしいのはそんなことではない。君主を説得することの難しさは、相手の心を読み取ったうえで、こちらの意見をそれに適応させること、この一点につきるのである。

・・・といっています。
ちなみに、これほど、進言って難しいってのを理解していた韓非さんは、始皇帝にその著書を高く評価されつつも、謀殺されるという皮肉な結果になっております。

こういう、後世の歴史に名が残るような偉人であっても進言は難しいという事実を認めるところから始めましょう。

とりあえず現状を受け入れる

引き継がれたコードや体制の不備に対して思わず「クソコード」とか「クソプロジェクト」とか言いたくなる気持ちはわかりますが、そこはまず現状を受け入れるところから始めましょう。

どんなマズイものであっても、ここまで築きあげるのに、それ相応の苦労はあったわけなので、そのあたりには敬意を払いましょう。

それに、できあがったものに文句をつけるのは、猿にもできます。
そんなモンキーオピニオンを、ドヤ顔して披露しても、周囲は納得しないでしょう。

たとえば、ExcelからTestLinkにテスト仕様書を替えることを進言したいとします。
この場合は、Excelで仕様書を書くメリットを徹底的に洗うべきです。
その上で、「ExcelからTestLinkに変更するとここは不便だが、ここは便利になりますが、どうでしょうか?」という持っていきかたにしましょう。

「Excelはクズ。さっさと変えるべき」とか全否定すると、相手は今までやってきたことが否定されるように感じる場合があります。

ちゃんと今までの良い点を挙げて、相手の逃げ道を残しておきましょう。

具体的な提案の戦略

提案を行うための、基本戦略は3つです。

・プロジェクトの貢献者になる
・タイミングを見計らう
・実行あるのみ

プロジェクトの貢献者になる

まず、最も基本的な事としてプロジェクトの貢献者となることです。
たとえば、あなたがどんなに、クールで効率的なバグ管理の方法を提案しようとも、あなたがやるべき作業をやっていなければ、その提案は絶対に通りません。

まず、与えられた作業をこなし、プロジェクトに貢献していることを周りに認めさせることです。

そして、提案する内容にエゴが含まれていないか考えましょう。
たとえば、このコードはPythonで書いた方が良いという提案をする場合、「自分がやりたいから」とか「自分が得意だから」とかの俺々なエゴはまずゴミ箱に捨てます。
「速度が改善されるから」とか、「メンバーの8割がPython使えるから」とかプロジェクトに寄与する進言をしましょう。

タイミングを見計らう

どんな貢献者であっても、進言するタイミングを間違ってはいけません。
たとえば、プロジェクトの初期でテスト仕様書をTestLinkで書こうという提案は良いかもしれません。
しかし、明日リリースなのに、テスト仕様書をTestLinkで書こうとか言い出してはいけません。
たしかにTestLinkはクールなツールでありますが、学習コスト、記述の手間を考えると明日リリースのプロジェクトに導入するのは不適切でしょう。

このように、提案するものの内容によって、適切な時期というものが存在します。
これを外してはどのような提案も受け入れられません。

ちなみに、もっとも意見を通しやすかったのは、権限のある人間に貸しを作った後です。
「徹夜明け」とか「休日出勤時」とか管理者が貴方に「苦労をさせた」と負い目を感じさせた後のタイミングで言うと大変効果的でしょう。

実行あるのみ

システム開発をしていると色々な提案がでてくるかと思います。
・テストを自動化する
・Jenkinsでビルドを自動化する
・静的解析を自動で行う
・Wikiにノウハウをまとめる
etc,etc...

こういうものは、理屈をこねて説得するより、実際効果を見せたほうが早いわけです。
・自分の担当分のテストはテストコードを書く
・自分のローカルの環境で自動ビルドの環境をつくる
・自分のローカルの環境で静的解析を行う
・自分のローカルの環境にWikiを書けるサーバーを立てる。

これらを実際に行い、ある程度、見せられる状況になって成果として見せるのです。
色々理屈をこねくり回すよりも非常に有効です。

もちろんこれは、「プロジェクトの貢献者になる」という前提を踏まえた上です。
致命的なバグを放置して、HTTPサーバーの構築に苦労するというのは適切な行為ではありません。

また、最近はソフトウェアのインストールにうるさいところもあるので、そのあたりは、きちんと根回ししておきましょう。

管理者権限ないけど、msiのツールを動かしたい場合

フリーソフトのダウンロードは認められても、与えられたパソコン自体に管理者権限がない場合もあります。
この場合、msiを展開してexeをとりだすことも可能です。

MSIファイルを解凍して内部のファイルを取り出す(msiexec編)
http://www.atmarkit.co.jp/fwin2k/win2ktips/856msiext/msiext.html

バッチファイルかなにかで、環境変数まわりを上手く設定後、起動してやれば、Javaだろうが、Pythonだろうが管理者権限なしで使えるようになります。
ただし、実行に管理者権限が必須のアプリケーションは動作しません。

あきらめが肝心

・・・とまあ、色々手を尽くしても通らない時は通りません。
先にのべたように、「歴史上の人間でも、うまくいかないのに、凡人が上手くやれるわけないよなw」とあきらめることも肝心です。

すくなくとも、同じ内容を同じ人間に何回言っても通らないなら、諦めましょう。
別の人間に言うか、別の方法に変えておいたほうが、保身が図れますし、無駄な労力を割かなくて済みます。

まとめ

提案を通しやすくする方法について考えてみました。

重要なのは、提案するってのは難しく、そして、とりあえず、プロジェクトとチームに貢献して信頼されなきゃ話にならないということです。

参考

下っ端でも何かを成し遂げる方法
http://web.archive.org/web/20170625093906/http://japanese.joelonsoftware.com/Articles/GettingThingsDoneWhenYour.html

韓非子
http://www.amazon.co.jp/dp/4198928347

最近はじめたAndroidのメモ

最近始めたAndroidについてのメモです。

このメモのおかげで、貧弱な坊やだった私でも一月でアプリがくめたよ。やったねたえちゃん。

Bluetoothで監視カメラ
https://play.google.com/store/apps/details?id=jp.ne.needtec.bluetoothcameraclient

開発環境

AndroidStudio

最近はEclipseでやるより、AndroidStudioで開発するようだ。
https://developer.android.com/sdk/index.html

以下の環境で、モッサリとではあるが、動作する。
CPU:Intel(R) Core(TM) i5CPU M450 @2.4GHz
メモリ:8GB
OS:Windows7 64bit

アプリケーションの実行

アプリケーションを実行する方法は3つある。
1.実機を使用する方法
2.エミュレータ―を利用する方法
3.VMWarePlayerにAndroidOSを入れる方法

実機を使用する方法

一番楽。
USBケーブルでつなげて、AndroidStudioからRunを実行すれば、実行対象のデバイスを選択できる。
androidstudiox1.png

エミュレータを利用する方法

AndroidStudioからエミュレータを起動することができる。
実機がなくても動くのは大きなメリットではあるが、速度が遅すぎて話にならない。

また、Windowsの場合、エミュレータの割り当てメモリ量によって落ちたりする。
http://stackoverflow.com/questions/7222906/failed-to-allocate-memory-8
メモリ量は、小さすぎても、大きすぎても使えない。

貧弱なPCにはおすすめしない。

VMWarePlayerにAndroidOSを入れる方法

VMWarePlayerにAndroidOSを入れて実行する。
エミュレータ―よりずっとはやい!!
ただし、センサーやBluetoothのサポートは行われていない。

この手順については下記を参照
http://www.japan-secure.com/entry/blog-entry-434.html
http://blog.fujiu.jp/2011/05/android-vmware-playerandroid.html

AndroidStudioでVMWare上のAndroidOSを認識させるには以下のような手順が必要

1.AndroidOS側でALT+F1を押してコマンドプロンプトを開き、下記のコマンドでipを調べる

ifconfig eth0

2.ホスト側で以下のコマンドを実行して、VMWare上のAndroidと接続させる。

C:\Users\ユーザー名\AppData\Local\Android\sdk\platform-tools\adb connect 192.168.67.146:5555

3.Android StudioでRunを実行するとデバイスとして表示される。
androidstudiox2.png

なお、VMWarePlayer上で使用できる特殊なショートカットキーは以下の通り

キー操作 動作
CTRL+ALT ホスト側のコンピュータに操作を戻す
ALT+F1 コマンドプロンプト
ALT+F7 コマンドプロンプト終了
F9を二回連打 上を上にした画面になる
F10を二回連打 上を下にした画面になる
F11を二回連打 上を左にした画面になる
F12を二回連打 上を右にした画面になる

仮想環境でのセンサーの利用

実機であれば実機についているセンサーをそのまま使える。仮想環境の場合はどうするべきか?

Sensor Simulatorを利用すれば、一応不可能ではない。
https://code.google.com/p/openintents/wiki/SensorSimulator

ソースコードも公開されているので自分で調整も可能。
https://github.com/openintents/sensorsimulator

使い方としては下記のページを参照
http://blog.goo.ne.jp/jsp_job/e/8d524f55e621899b7a7fcf85a14fa8d5

対象の仮想環境にSensorSimulatorをインストールして、ホストOSのGUIから、その設定値を伝える。

android10.png

センサーの数値を明示できる分、便利かもしれないが、これは、アプリケーション側のコードでクラス名を修正する必要がある。

android.hardware.Sensor
  →org.openintents.sensorsimulator.hardware.Sensor

多分実際に使うとしたら、この当たりをクラスでラップして、実機と仮想環境で切り分けるようにしとかんとダメだろう。

Jarの利用

他人が作ったJarファイルはlibsフォルダに入れておけばAndroidStudioが認識するようになる。

このあたり参照。
http://rakuishi.com/archives/5768/

NDKの利用

NDKのサポートは将来機能になっているが、現時点でも、NDKで作成したライブラリも利用できる。

NDKでビルドしたライブラリをAndroid Studio1.1で使用するには以下を参照。
http://qiita.com/mima_ita/items/6cee54fefcdb96999ab6

おそらく、バージョンアップに伴って使い方が変わるだろう。
(実際、1.0の記事がすでに役にたたなくなっている)

デバイスとファイルのやり取りを行う方法

AndroidDeviceMonitorを使用すればファイルのやり取りが行える。
ただし、権限の関係で全部のディレクトリの内容が見えるわけでもなさそう。

android7.png

android8.png

デバイス上のスクリーンキャプチャーと動画の作成

「Screen Capture」ボタンを押せば、現在接続中のデバイスのスクリーンショットを任意の名前で保存できる。

android1.png

その下の「Screen Record」ボタンでは、動画として保存可能。

メモリの使用状況

「Tool->Android->Memory Monitor」を実行することで、その指定のプロセスのメモリ使用状況が確認できる。

androidstudiox3.png

詳細な使用状況を調査するには以下のようにする。
1.AndroidStudioのDevicesより、調査対象のプロセスを選択して、「Dump Java Heap」を選択する。
android3.png

2.MATでそのダンプを解析する。
android5.png

MATは下記からダウンロードできる。
http://www.eclipse.org/mat/downloads.php

ビルド

AndroidStudioのビルドはGradleっていう言語を利用して記述されている。
XMLとかとちがってプログラミング言語なので、わかりやすいかもしれない。

AndroidのGradleのリファレンスは下記のページの「Plugin Language Reference」からダウンロードできる。
http://developer.android.com/tools/building/plugin-for-gradle.html

便利なショートカットキー

「CTRL+ALT+L」で書式を自動で整形してくれる
androidx4.png

androidx5.png

その他、次のようなショートカットキーがある。
http://qiita.com/sugoi_wada/items/db449d5cbb5c83cb586c

静的解析

AndroidStudioは常時、静的解析を行い、まずい書き方をしているコードを教えてくれる。
エディタの右側に黄色くなっている箇所をマウスオーバーすると、なにがまずいか教えてくれる。
androidstudiox4.png

テスト

Android Studio1.1からUnitテストをデフォルトでサポートしている。
http://tools.android.com/tech-docs/unit-testing-support

基本的にJUnitっぽい。そして、GUIのテストもできるっぽい。
http://developer.android.com/tools/testing/activity_testing.html

開発TIPS

アプリケーションのアイコンの変更

下記のアイコンが必要になる。
・MDPI 48x48
・HDPI 72x72
・XHDPI 96x96
・XXHDPI 144x144

あとは、下記参照。
http://androidstudio.hatenablog.com/entry/2014/07/23/111303

GoogleMap

GoogleMapを使用したアプリも作れるが、GoogleでのAPIの登録が必要。
http://maruta.be/intfloat_staff/168

現在位置を取得するには、FusedLocationApiを使って現在地を取る。
このあたりを参照。
http://blog.teamtreehouse.com/beginners-guide-location-android
http://javapapers.com/android/android-location-fused-provider/

VMWarePlayerではGoogleMapが上手く表示できない。
エミュレータはOK.

デザインについて

Google様が下々にデザインガイドラインをお示しくださっている
http://developer.android.com/design/index.html

アプリケーションの権限

アプリケーションインストール時に固有ユーザーを作成している。
おそらく、これで、別のアプリからの干渉をさける。
マニフェストファイルのsharedUserIdを使うとインストール時のユーザーを同じにできる。

android:sharedUserId="http://jp.co.needtec.bmical"

android9.png

AlermManager

AlermManagerの~WALK_UPはスリープ中でもイベント発火するっぽい。
でも,onReceiveぬけるとすぐ二度寝するので、WalkLockが必要っぽい。

アプリ間のデータ共有

コンテントプロバイダって機能を使ってあるアプリが持っている機能を別のアプリに公開する。
SharedPreferences のMODEWORLD~は非推奨になっている。

クリック関係のイベントの発火順序

OnTouch(ACTION_DOWN)
OnLongClick(長おししたとき)
OnTouch(ACTION_UP)
OnClick

長押しの時間ってのはてのはViewConfigurationのgetLongPressTimeoutっぽい。デフォで500m秒。
http://qiita.com/y-takano/items/58cd0060089bd4775780

Widgetの重み

android:lay_outweightをつけるとウィジェットに重みをつけられる。
これを利用すれば、どっちを優先して大きくするか指定できる。

Bluetoothの操作

以下の記事参照
https://sites.google.com/a/gclue.jp/android-docs-2009/bluetoothapurino-kaihatsu

Windowsと通信するときはエンディアンに注意。
稼動CPUを問わずJava仮想マシンについてはビッグエンディアンなので、Windows側では「_byteswap_ulong」などでバイト列を調整する。

Java同士なら意識する必要なし。

また、通信速度はそんなに速くない。小さい画像を転送する分には気にならないが、大きなサイズだと遅すぎる。接続範囲も、狭い。
(窓の外にAndroid端末おいて操作する程度はできるが、Android端末もって部屋から出ていくと切断される)

カメラの制御

カメラの制御については下記参照。
http://qiita.com/zaburo/items/d9d07eb4d87d21308124

ただし、2.1-Update 1が対象の場合、プレビュー周りで落ちる場合があるので、以下を参考にする。
https://code.google.com/p/android/issues/detail?id=7909

どうも、surfaceChangedでのsetPreviewSizeが具合悪いようだ。
onResumeで使用する分には問題なし。

https://github.com/mima3/BluetoothCamera/blob/e547e0980d7107b7ae11b2fee316d3e4897a9cfe/BluetoothCameraClient/app/src/main/java/jp/ne/needtec/bluetoothcameraclient/CameraPreviewActivity.java

また、カメラのプレビューの画像はyuv420spになっている。
これをビットマップに変換する場合は、下記を参考。
http://qiita.com/GeneralD/items/68142abb852c392db236

もし、WindowsのBITMAPにする場合は上下を反転させる必要がある。
https://github.com/mima3/BluetoothCamera/blob/e547e0980d7107b7ae11b2fee316d3e4897a9cfe/BluetoothCameraServerWin/BluetoothCameraServer/BluetoothCameraReceiver.cpp

ORMの使用

SQLiteのORMとして、ORMLiteがある。
http://ormlite.com/releases/
http://qiita.com/radiocatz/items/5f1dce3f8c5faa55e6f6

jarをlibsにつっこめば使える。

NFCタグに情報を読み書きする方法

書き込み可能なタグはネット通販などで20枚1200円程度で購入可能なので、社員証の代わりとかで使えそう。

http://www.atmarkit.co.jp/ait/articles/1211/27/news072.html

C++で実装しようとすると銭が必要そう。
http://www.sony.co.jp/Products/felica/business/products/ICS-D004_002_003.html

リリース方法

手順は以下の通り
1.Android StudioでAPKファイルを作成する。
http://androidstudio.hatenablog.com/entry/2014/07/26/154043

2.GooglePlayに登録する。
https://play.google.com/apps/publish/

・開発者の登録料として25$をクレジットカードで支払う必要がある。

・電話番号の登録には国コードが必要。日本は「+81」をいれとく。

・アプリを公開したい場合、「なぜ公開できないか」という情報が表示されているので、それにあわせて対応していけば、公開できるようになる。

・アプリのバージョンアップ時にはbuild.gradle中のversionCodeを前回リリース時より大きくする必要がある。

・「アルファ版 / ベータ版テストと段階的公開の使用」として特定のユーザーに絞って公開することもできるっぽい。
https://support.google.com/googleplay/android-developer/answer/3131213?hl=ja

この際、米国の輸出法に従う必要がある。
暗号を利用している場合は注意。
たとえば、SSLでの通信も対象になる。
例外事項として、パスワードの暗号化、認証、デジタル署名を目的に使用するのは対象外。

Google Marketにある輸出規制って何?
http://synchack.com/memo/index.php?Tips%2FGoogle%20Market%A4%CB%A4%A2%A4%EB%CD%A2%BD%D0%B5%AC%C0%A9%A4%C3%A4%C6%B2%BF%A1%A9
http://www.cistec.or.jp/service/iinkaidayori/houkokusho2010/data_kamotsu/kamotsu5-3-1.pdf

暗号化に関する輸出制限について
https://msdn.microsoft.com/ja-jp/library/windows/apps/xaml/hh694069.aspx

気付いた点

・変化がくっそ早い分野なので、古い本とか使用すると、間違い探しになります。なるべく新しい本を買った上で、あんまり信用しないで使用しましょう。
 →ゆえに初心者が初手Android開発やると嵌ると思います。

・いきなりBluetoothとかのハードウェアを使うプログラムはやめよう。
 →仮想環境で動かないので、実機を用意する必要がでます。
  金かけれるならともかく、おためしでやるにはつらいかと。

・いきなりNDKを使ったプログラムはやめよう。
 →むちゃくちゃきつい

・公開時の審査は数時間でおわる。アップルと違って緩いってはっきしわかる。

・GUIのプログラムを学習するときは、最初、XMLを無視して、コード上でGUIを設置した方が直観的にわかりやすい。
 ある程度、つかんだ後に、XMLでも定義するという流れがいいかと。

MsTestによるユニットテストの解説

概要

この記事ではVisualStudioインストール時に導入されるMsTestの使用方法について解説する。

参考:

単体テストの基本
https://msdn.microsoft.com/ja-jp/library/hh694602.aspx

環境:
VisualStudio Community 2013

簡単なテストプロジェクト

1.VisualStudioにてテストプロジェクトを追加する。

unittest1.png

2.UnitTestを記述する。

UnitTest1.cs

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestSample
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            int exp = 10;
            int act = 5 + 5;
            Assert.AreEqual(exp, act);
        }
    }
}

3.テストを実行する
unittest2.png

4.テストエクスプローラーで結果を確認
unittest3.png

コマンドラインからのテストの実行

継続的インテグレーションにMsTestを組み込む場合、コマンドラインにてMsTestを実行する必要がある。

注意
下記の情報は古いです。Microsoft Fakesを使用するには「VSTest.Console.exe」を使用しましょう。
https://msdn.microsoft.com/ja-jp/library/ms182486.aspx

# 環境変数の設定
"C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\VsDevCmd.bat"

# テストの実行
mstest /testcontainer:UnitTestSample\UnitTestSample\bin\Debug\UnitTestSample.dll

環境変数の設定で実行しているバッチファイルは、「開発者コマンド プロンプト for VS2013」で実行しているバッチと同じものである。

mstestはテストを実行するコマンドである。
/testcontainerのあとにテストプロジェクトが作成した、DLLを指定する。
その他、MsTestの使用方法については下記のヘルプコマンドで確認のこと。

mstest /help

標準出力の出力例:

Microsoft(R) Test Execution Command Line Tool Version 12.0.21005.1
Copyright (c) Microsoft Corporation. All rights reserved.

UnitTestSample\UnitTestSample\bin\Debug\UnitTestSample.dll を読み込んでいます...
実行を開始しています...

結果            トップ レベルのテスト
--            -----------
成功            UnitTestSample.UnitTest1.TestMethod1
1/1 テスト 成功

概要
--
テストの実行 完了 です。
  成功  1
  -----
  合計  1
結果ファイル:C:\Users\xxxx\Documents\Visual Studio 2013\Projects\Sample\TestResults\XXX-YYYY-PC 2015-05-26 17_59_47.trx
テストの設定:既定のテストの設定

テストを実行することで、「TestResults」というフォルダがカレントディレクトリに作成されて、テスト結果とテストを行ったバイナリのコピーが格納される。
この際、作成されたテスト結果は「.trx」拡張子のファイルとなり、VisualStudioを使用して開くことで内容を確認できる。

unittest4.png

このリストには、すべての列が表示されているわけではない。
列のヘッダを右クリックすることで、列の追加削除が行える。

unittest5.png

また、この内容はXMLなので自前でパースしてExcelなどに結果を記述することも可能である。

テストエクスプローラーの機能

テストエクスプローラーでは任意の方法でテストをグループ化できる。

下記の例ではクラスごとにテストを分類したものである。

unittest14.png

また、Category属性を使用することで、各テストメソッドに特徴を指定でき分類が可能になる。

[TestMethod, TestCategory("カテゴリ1"), TestCategory("カテゴリ2")]
public void 足し算の確認()
{
}

[TestMethod, TestCategory("カテゴリ1")]
public void 引き算の確認()
{
}

[TestMethod, TestCategory("カテゴリ3")]
public void 常にオッケーのテスト()
{
}

unittest15.png

その他、テストエクスプローラーの各機能については下記を参照。
https://msdn.microsoft.com/ja-jp/library/hh694602.aspx#BKMK_Running_tests_in_Test_Explorer

様々なAssert機能

MsTestはAssertクラスで様々なAssertの方法を提供している。
もっとも単純なものは以下のように期待値、結果、メッセージを付与するものである。

Assert.AreEqual(expected, actual, message)
Expected: 期待値
Actual:実際の値
Message:アサーションが失敗した時のメッセージ。テスト結果にでる

このほかにも、浮動小数点をAreEqualする際に、許容範囲を指定できたり、Collectionに対しての検証も行える。

        [TestMethod]
        public void TestDouble()
        {
            double e = 1.5;
            double r = 1.51;
            Assert.AreEqual(e, r, 0.011); // 0.01だとNG
        }

       [TestMethod]
        public void TestCollection()
        {
            var expected = new List<int> { 1, 1, 2, 3, 4, 5};

            // 等価かどうか?
            // 2 つのコレクションが等価であるためには、同じ要素が同じ数だけ含まれている必要があります。要素の順番が一致している必要はありません。 2 つの要素が同一であるためには、その値が一致している必要があります。それぞれの要素が同一のオブジェクトを参照している必要はありません。
            CollectionAssert.AreEquivalent(expected, new List<int> { 1, 5, 4, 3, 2, 1 });

            // 同一であるか?
            CollectionAssert.AreEqual(expected, new List<int> { 1, 1, 2, 3, 4, 5 });

            // 一意であるか?
            CollectionAssert.AllItemsAreUnique(new List<int> { 1, 2, 3, 4, 5 });

            // 特定の値が含まれるか?
            CollectionAssert.Contains(new List<int> { 1, 2, 3, 4, 5 }, 3);

        }

詳細は下記を参照。

Assert クラス
https://msdn.microsoft.com/ja-jp/library/Microsoft.VisualStudio.TestTools.UnitTesting.Assert.aspx

CollectionAssert クラス
https://msdn.microsoft.com/ja-jp/library/microsoft.visualstudio.testtools.unittesting.collectionassert.aspx

テストコードをステップ実行する方法

下記のメニューをすることデバッグ実行が行える。
「テスト」 > 「デバッグ」 > 「選択したテスト」 又は 「すべてのテスト」

unittest6.png

こうすることで、ブレイクポイントを指定した行で止めることができる。

テスト中の標準出力

以下のようにテストメソッドで標準出力をしたとする。

        [TestMethod]
        public void TestStdOut()
        {
            Console.WriteLine("stdout {0} {1}", 1, "text");
        }

この場合、テストエクスプローラーの結果で確認することができる。
unittest7.png

「出力」をクリックすることで、テスト結果の詳細が表示されて、標準出力の箇所に該当の文字が出力される。

この内容は、コマンドラインで実行した場合に作成された「.trx」ファイルにも記録されている。

テスト結果.trx

<TestRun>
  <Results>
    ...
    <UnitTestResult ...>
      <Output>
        <StdOut>stdout 1 text</StdOut>
      </Output>
    </UnitTestResult>
  </Results>
</TestRun>

出力ウィンドウ には出力されないので注意すること。

出力ウィンドウに出力したい場合は、Traceメソッドで該当のメッセージを記述して、Debug実行を行うこと。

        [TestMethod]
        public void TestStdOut()
        {
            System.Diagnostics.Trace.WriteLine("test");
        }

unittest8.png

なお、Debugではない通常のテストを実行した場合、この出力は行われない。

テストケース共通の初期処理と終了処理

テストクラスにおいて、各テストメソッドの初期処理と終了処理を共通化したい場合は、TestInitialize、TestCleanupなどを使用する。

以下では、このコードには、メソッド、クラス、およびアセンブリの初期化とクリーンアップの実行順序を制御する属性を実験したものとなる。

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestSample
{
    [TestClass]
    public class UnitTest2
    {
        [TestMethod]
        public void TestMethod1()
        {
            System.Diagnostics.Trace.WriteLine("TestMethod1");
        }

        [TestMethod]
        public void TestMethod2()
        {
            System.Diagnostics.Trace.WriteLine("TestMethod2");
        }

        [AssemblyInitialize]
        public static void AssemblyInit(TestContext context)
        {
            // アセンブリ内のすべてのテストが実行される前に、アセンブリによって取得されるリソースを割り当てるために使用されるコードを含むメソッドを識別します。 
            System.Diagnostics.Trace.WriteLine("AssemblyInit " + context.TestName);
        }

        [AssemblyCleanup]
        public static void AssemblyCleanup()
        {
            // アセンブリ内のすべてのテストが実行された後、アセンブリによって取得されたリソースを開放するために使用されるコードを含むメソッドを識別します。
            System.Diagnostics.Trace.WriteLine("AssemblyCleanup");
        }

        [ClassInitialize]
        public static void ClassInit(TestContext context)
        {
            // テスト クラス内の任意のテストが実行される前に、テスト クラスによって使用されるリソースを割り当てるために使用する必要のあるコードを含むメソッドを識別します。 
            System.Diagnostics.Trace.WriteLine("ClassInit " + context.TestName);
        }

        [ClassCleanup]
        public static void ClassCleanup()
        {
            // テスト クラスのすべてのテストが実行された後、テスト クラスによって取得されたリソースを解放するために使用されるコードを含むメソッドを識別します。
            System.Diagnostics.Trace.WriteLine("ClassCleanup");
        }

        [TestInitialize]
        public void TestInitialize()
        {
            // テスト クラスのすべてのテストに必要なリソースの割り当ておよび構成を行うために、テストの前に実行するメソッドを識別します。
            System.Diagnostics.Trace.WriteLine("TestInitialize");
        }

        [TestCleanup]
        public void TestCelean()
        {
            // テストが実行された後、テスト クラス内のすべてのテストによって取得されたリソースを開放するために使用される必要のあるコードを含むメソッドを識別します。 
            System.Diagnostics.Trace.WriteLine("TestCelean");
        }

    }
}

デバッグ実行をした場合の、出力結果は以下のようになる。

AssemblyInit TestMethod1
'vstest.executionengine.x86.exe' (CLR v4.0.30319: UnitTestAdapter: Running test): 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.Transactions\v4.0_4.0.0.0__b77a5c561934e089\System.Transactions.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'vstest.executionengine.x86.exe' (CLR v4.0.30319: UnitTestAdapter: Running test): 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.EnterpriseServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'vstest.executionengine.x86.exe' (CLR v4.0.30319: UnitTestAdapter: Running test): 'C:\Windows\Microsoft.Net\assembly\GAC_32\System.EnterpriseServices\v4.0_4.0.0.0__b03f5f7f11d50a3a\System.EnterpriseServices.Wrapper.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
'vstest.executionengine.x86.exe' (CLR v4.0.30319: UnitTestAdapter: Running test): 'C:\Windows\Microsoft.Net\assembly\GAC_MSIL\System.Numerics\v4.0_4.0.0.0__b77a5c561934e089\System.Numerics.dll' が読み込まれました。シンボルの読み込みをスキップしました。モジュールは最適化されていて、デバッグ オプションの [マイ コードのみ] 設定が有効になっています。
ClassInit TestMethod1
TestInitialize
TestMethod1
TestCelean
TestInitialize
TestMethod2
TestCelean
ClassCleanup
AssemblyCleanup

TestInitialize,TestCeleanはテストメソッド毎に実行されことが確認できる。

テストデータの利用

MSTestではDataSource属性を使用することで、外部のファイルをテストデータとして使用できる。

前提

System.Dataが必要なので参照に追加しておくこと。

unittest10.png

もっともシンプルな例

1.以下のようなtest.csvファイルを用意する。

test.csv

a,b,result
1,2,3
2,4,6
3,5,8

このCSVはCP932で保存する

2.CSVをテストプロジェクトの直下に配置する。

unittest9.png

3.追加したCSVのプロパティで、テストデータを「出力ディレクトリ―」にコピーするようにする。

unittest11.png

4.テストコードを記述する

        public TestContext TestContext { get; set; }

        [TestMethod]
        [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", @"test.csv", "test#csv", DataAccessMethod.Sequential)]
        public void TestCsv()
        {
            int a = (int)TestContext.DataRow["a"];
            int b = (int)TestContext.DataRow["b"];
            int result = (int)TestContext.DataRow["result"];
            System.Diagnostics.Trace.WriteLine("TestCsv " + a + " " + b + " " + result);
            Assert.AreEqual(result, a + b);
        }

5.テストを実行すると、CSVを1行づつ順番によみこんでTestCsvを都度実行する。
デバッグ実行をすると以下のような出力がされ、順次実行されていることがわかる。

TestCsv 1 2 3
TestCsv 2 4 6
TestCsv 3 5 8

テストデータを格納するフォルダを指定する

実際にテストデータを配置する場合、プロジェクトの直下ではなくフォルダを構成して配置する。

unittest12.png

DataSource属性には、|DataDirectory|を指定して、その後、プロジェクトからの相対パスを記述する。

        [TestMethod]
        [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", @"|DataDirectory|\TestData\Test001\test.csv", "test#csv", DataAccessMethod.Sequential)]
        public void TestCsv()
        {
            int a = (int)TestContext.DataRow["a"];
            int b = (int)TestContext.DataRow["b"];
            int result = (int)TestContext.DataRow["result"];
            System.Diagnostics.Trace.WriteLine("TestCsv " + a + " " + b + " " + result);
            Assert.AreEqual(result, a + b);
        }

コマンドラインから実行した場合に、テストデータもコピーする。

コマンドラインからMSTestを実行すると、テスト結果のOutフォルダにdllなどが出力される。
この際、テストデータも出力したい場合は、DeploymentItem属性を使用する。

    [TestClass]
    [DeploymentItem(@"TestData\Test001\test.csv", @"TestData\Test001")]
    public class UnitTest1
    {
       // 略
    }

これにより、Outフォルダにdllと共にテストデータが出力される。

unittest13.png

ランダムアクセスによるデータの読み込み

DataAccessMethod.Randomを実行することで、CSVをランダムな順番に読み込むことができる。

        [TestMethod]
        [DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV", @"|DataDirectory|\TestData\Test001\test.csv", "test#csv", DataAccessMethod.Random)]
        public void TestCsvRandom()
        {
            int a = (int)TestContext.DataRow["a"];
            int b = (int)TestContext.DataRow["b"];
            int result = (int)TestContext.DataRow["result"];
            System.Diagnostics.Trace.WriteLine("TestCsvRandom " + a + " " + b + " " + result);
            Assert.AreEqual(result, a + b);
        }

デバッグ出力の例:

TestCsvRandom 3 5 8
TestCsvRandom 1 2 3
TestCsvRandom 2 4 6

参考:
Data-driven troubles
http://www.codeproject.com/Articles/710072/Data-driven-troubles

未確定なテスト項目

現在、テストを一時的に無視したり、テストの成功・失敗の条件がわからない場合がある。
この時は、コメントアウトをしないで、明確に無視するなり、未確定とすべき。

テスト項目を一時的に無視する

ペンディングのテスト項目を実行させないで、警告を出力させたい場合がある。
この際は、Ignore属性を使用する。

unittest16.png

未確定な物

Assert.Inconclusiveを使用する。

        [TestMethod]
        public void TestInconclusive()
        {
            Assert.AreEqual(1, 1);
            Assert.Inconclusive("Unable to determine success or failure");
        }

この場合も、Ignoreと同様に警告として扱われる。

Privateメソッドのテスト

Privateのメソッドについても、「PrivateObject」を利用することでテストが行える。

            var pbObj = new PrivateObject(_obj);
            var ret = pbObj.Invoke("privateAdd", 1, 2) as int?;
            Assert.AreEqual(3, ret);    

PrivateObject クラス
https://msdn.microsoft.com/ja-jp/library/ms245564(v=vs.110).aspx

例外のテスト方法

例外が発生する方法を確認する方法について説明する

ExpectedException属性を使用する

ExpectedExceptionは以下のように使用する

        [TestMethod()]
        [ExpectedException(typeof(System.DivideByZeroException))]
        public void DivideTest()
        {
            int x = 1;
            int y = 0;
            int r = x / y;
            System.Diagnostics.Trace.WriteLine(r);
        }    

System.DivideByZeroExceptionが発生した場合は成功、それ以外の場合は、テストは失敗する。
この方法でテストした場合、1メソッドについて、1例外しか確認できない。

自分でTry-catchして例外をテストする方法

自分でTry-catchして例外をテストする場合は、例外が発生しなかった場合に、Assert.Failを利用してテストを失敗させる。

ノーマルにMSTestを使おう
http://qiita.com/moonmile/items/269295ad5758fa69d203

        [TestMethod]
        public void DivideTest3()
        {
            try
            {
                int x = 0;
                int ans = 1 / x;
            }
            catch (System.DivideByZeroException ex)
            {
                // 例外が発生すればOK
                return;
            }
            // 別の例外が出た場合は、予期せぬ例外となって、テストの失敗となる。
            // 例外が発生ない場合は、以下のコードで例外を発生させる
            Assert.Fail("例外が発生しませんでした");
        }

例外を投げるメソッドのテストを確認する関数を作る方法

例外を投げるメソッドのテストを確認する関数を作り、確認することもできる。

[Visual Studio 2012, C#] ユニットテストのメモ
http://fernweh.jp/b/vs2012-unittesting

        // http://fernweh.jp/b/vs2012-unittesting/#id-1
        private E GetException<E>(Action action) where E : Exception
        {
            try
            {
                action();
                return null;
            }
            catch (E ex)
            {
                return ex;
            }
        }

        [TestMethod()]
        public void DivideTest2()
        {
            var ex = GetException<Exception>(
                delegate() {
                    int x = 0;
                    int y = 1 / x;
                }
            );
            Assert.AreEqual("System.DivideByZeroException", ex.GetType().FullName);
        }

タイムアウトの設定

タイムアウトは、個々のメソッドと全体に対して指定することができる。

個々のテストメソッドにタイムアウトを指定する

Timeoutにてミリ秒を指定することで、テストメソッド毎にタイムアウトを指定できる。
テスト中に指定した期間を超えた場合、NGとなる。

        [TestMethod]
        [Timeout(2000)]  // Milliseconds
        public void TestTimeout1()
        {
            // NG
            System.Threading.Thread.Sleep(10000);
        }

        [TestMethod]
        [Timeout(TestTimeout.Infinite)]
        public void TestTimeout2()
        {
            // OK
            System.Threading.Thread.Sleep(10000);
        }

テスト全体のタイムアウトの指定

テスト全体にタイムアウトを指定するには、テスト設定ファイルを作成する必要がある。
まず、ソリューションの [ソリューション項目] フォルダーで、テストの設定ファイルを追加する。

unittest17.png

テスト設定ファイルを作成するウィザードが起動するので、タイムアウトの設定がでるまで、既定の値で次に進む。

unittest19.png

その後、ウィザードを終了すると、ソリューションにテスト設定ファイルが追加される。

unittest20.png

単体テストを実行する際に、この作成したテスト設定ファイルを適用して実行すると、タイムアウトが全体に適用される。

VisualStudioでテストする場合、「テスト」→「テスト設定」→「テスト設定ファイルの選択」を指定できる。

unittest21.png

コマンドラインから実行する場合に、テスト設定ファイルを指定するには「/testsettings」を使用する。

MSTest /testcontainer:UnitTestSample\bin\Debug\UnitTestSample.dll /testsettings:TestSettings1.testsettings

参考:
https://msdn.microsoft.com/ja-jp/library/ms243175.aspx

テスト結果に埋め込み可能な情報

いくつかの属性を使用することで、テスト結果のXMLに情報を埋め込むことが可能である。

Description属性

テストについての説明を指定するために使用される。

 [TestMethod]
 [Description("Test Case Description")]
 public void EnsureTestCaseValid()
 {      
 }

Owner属性

テストの維持、実行、およびデバッグの担当者を指定するために使用する。

        [TestMethod]
        [Owner("mitagaki")]
        public void 常にオッケーのテスト()
        {
            Assert.AreEqual(true, true, message: "常時オッケー");
            Console.WriteLine("常時OK");
        }

Priority属性

単体テストの優先順位を指定するために使用される。
MSTestはテスト実行時に優先順位で絞り込むことが可能である。

        [TestMethod]
        [Priority(1)]
        public void TestPriority1()
        {
        }

優先度の絞りこみには、minpriority,maxpriorityを用いる。

MSTest /testcontainer:UnitTestSample\bin\Debug\UnitTestSample.dll /minpriority:2 /maxpriority:3

Moq

単体テストを行う場合、クラス間の依存関係をなくすために、実際のオブジェクトの代わりにテスト用のオブジェクトを使用することがある。

Moqは、このテスト用のオブジェクトを簡単に作成するためのライブラリである。
https://github.com/Moq/moq4

インストール方法

パッケージマネージャコンソールから下記のコマンドを実行

PM> Install-Package Moq

簡単なサンプル

テスト対象

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CalcLib
{
    public interface IDataCalc
    {
        int GetData();
    }
    public class DataCalc : IDataCalc {
        virtual public int GetData() 
        {
            return 100;
        }
    }
    public class Calc
    {
        private IDataCalc _data;
        public Calc()
            : this(new DataCalc())
        {

        }

        public Calc(IDataCalc data)
        {
            _data = data;
        }

        public int IncData(int i)
        {
            return _data.GetData() + i;
        }
    }
}

既定のDataCalc をモックを使用して実装する。
モックで使うメソッドは、virtual でなければならない。

テストコード

        [TestMethod]
        public void モックのテスト()
        {
            var mock = new Mock<CalcLib.DataCalc>();
            mock.Setup(c => c.GetData()).Returns(
               5
            );
            var con = mock.Object;
            var o = new CalcLib.Calc(con);
            Assert.AreEqual(10, o.IncData(5));
        }

Microsoft Fakes

Microsoft Fakesでテストでのコードの分離がおこなえる。
https://msdn.microsoft.com/ja-jp/library/hh549175.aspx

しかし、Visual Studio Ultimate または Premiumが必要になる。

詳細は下記を参照。
http://qiita.com/mima_ita/items/9ebb0f40d3209f33a45d

コード カバレッジの計測

Ultimate、または、Premiumを購入しないと無理。
https://www.visualstudio.com/ja-jp/products/compare-visual-studio-products-vs.aspx

コードカバレッジを100%目指そうとすると、まず頓挫するので、相応のコストを支払う覚悟がなければ気にしなくていいかもしれない。

実際の使いかたは下記参照。
http://qiita.com/mima_ita/items/05ce44c3eb1fd6e9dd46#テストコードで保障されていない箇所を調べてテストコードを書く

Pex

Microsoftが開発中のPexを使用すると、検査対象のメソッドの単体テストのひな形を作成してくれる。

http://research.microsoft.com/en-us/projects/pex/

対象のメソッドを選択して「Generate Inputs /Outputs Table」を実行する。

unittest22.png

すると、そのメソッドに対して入力と結果の一覧が作成できる。
unittest23.png

これにより、単体テストのコードを作成したり、メソッドの内容を調べることが可能になる。
しかし、VisualStudio2013時点では、自動生成された項目をユニットテストとして保存できないようなので、導入するなら2015が出るまで待った方が良いだろう。

Using Pex and Microsoft Code Digger to Better Understand and Test Your Code
http://www.codeproject.com/Articles/583520/UsingplusPexplusandplusMicrosoftplusCodeplusDigger

Visual Studio 2015 の新機能: Pex はユニットテストの福音となるか!?
http://www.slideshare.net/yasuhikoy/pex-44098704

Pythonで国土数値情報のShapeFileを操作してデータベースにインポートしてみる

ShapeFileは空間データの位置と形に関する情報と、その属性情報を格納するためのデータ形式です。
国土数値情報などからダウンロードするとついてくる.shp拡張子のファイルが該当します。

今回はこのShapeFileをPythonで読み取り、Spatialiteに格納するのを目標とします。

ShapeFileの詳細

ShapeFileは3つのファイルにより構成されます。
メインファイル、インデックスファイル、属性ファイルの3つです。
これらのファイル名は拡張子を除き同じものになります。

■メイン・ファイル:counties.shp
■インデックス・ファイル: counties.shx
■属性ファイル: counties.dbf

メインファイルは空間データが格納されています。
インデックスファイルは、各空間データへのアクセスを容易にするためのインデックスになります。
属性ファイルは、属性値が格納されています。

これらの詳細の仕様は下記を参考にしてください。

シェープファイルの技術情報
http://www.esrij.com/cgi-bin/wp/wp-content/uploads/documents/shapefile_j.pdf

PythonでShapeFileを操作する

PythonでShapeFileを操作するには下記のライブラリを用いると良いでしょう。

https://github.com/GeospatialPython/pyshp

インストール方法
shapefile.py を任意のフォルダに配置してimportする。

このライブラリはpython2.4-3.x系で利用できます。

国土数値情報の操作例

この例では、国土数値情報の鉄道路線情報のN02-05-g_RailroadSection.shpを操作してみます。

国土数値情報 鉄道データ
http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-N02-v2_2.html

# -*- coding: utf-8 -*-
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/pyshp')
import shapefile

sf = shapefile.Reader('original_data\\N02-05_GML\\N02-05\\N02-05-g_RailroadSection.shp')
shapeRecs = sf.iterShapeRecords()
for sr in shapeRecs:
  #属性値が入っている
  print ('attribute:' , sr.record)

  #型のタイプ
  #NULL = 0
  #POINT = 1
  #POLYLINE = 3
  #POLYGON = 5
  #MULTIPOINT = 8
  #POINTZ = 11
  #POLYLINEZ = 13
  #POLYGONZ = 15
  #MULTIPOINTZ = 18
  #POINTM = 21
  #POLYLINEM = 23
  #POLYGONM = 25
  #MULTIPOINTM = 28
  #MULTIPATCH = 31
  print ('shapeType:' ,sr.shape.shapeType)

  #座標ポイントのリスト
  print ('points:', sr.shape.points)

  #MultiLingやMultiPolygonの際に、pointsをどこで分割するか
  print ('parts:' ,sr.shape.parts)

iterShapeRecords()は、shpファイルを頭から解析していきます。
この際、メモリに展開するのは1データのみなので、大データの処理に適します。

しかしながら、iterShapeRecordはレコードヘッダに記録されているコンテンツ長の次のバイトに次のレコードがあることが前提になっています。
多くの場合は、この前提で解析できるのですが、この前提が正しくない場合があります。
たとえば、以下のA31-12_17_GML.shpを実行するとエラーになります。

国土数値情報 浸水想定区域データの石川県
http://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-A31.html

これは、レコードの間にゴミがはいっているため、shpファイルだけでは解析できず、インデックスファイルを利用する必要があります。

この場合は、以下のように、iterShapeRecordsを使用せずに実装することが可能です。

# -*- coding: utf-8 -*-
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)) + '/pyshp')
import shapefile

sf = shapefile.Reader('original_data\\A31-12\\output\\A31-12_17_GML\\old\\A31-12_17.shp')
try:
    # 国土数値情報のshpファイルが不正で、shpファイル中のコンテンツ長と実際の長さのつじつまが合っていない。
    # しかたないのでshxファイル経由で各レコードを取得する
    i = 0
    while True:
        shape = sf.shape(i)
        rec = sf.record(i)
        #属性値が入っている
        print ('attribute:' , rec)

        #型のタイプ
        #NULL = 0
        #POINT = 1
        #POLYLINE = 3
        #POLYGON = 5
        #MULTIPOINT = 8
        #POINTZ = 11
        #POLYLINEZ = 13
        #POLYGONZ = 15
        #MULTIPOINTZ = 18
        #POINTM = 21
        #POLYLINEM = 23
        #POLYGONM = 25
        #MULTIPOINTM = 28
        #MULTIPATCH = 31
        print ('shapeType:' ,shape.shapeType)

        #座標ポイントのリスト
        print ('points:', shape.points)

        #MultiLingやMultiPolygonの際に、pointsをどこで分割するか
        print ('parts:' , shape.parts)

        i += 1
except IndexError:
    pass

これを利用すれば、shapeファイルをPythonで解析してspatialiteにインポートするという処理が行えます。

以下のプログラムでは国土数値情報の土砂災害危険箇所データ、浸水想定区域データ、竜巻等の突風データをShapeファイルからspatialiteに格納しています。
https://github.com/mima3/kokudo/blob/master/kokudo_db.py

Demo
http://needtec.sakura.ne.jp/kokudo/

SPATIALITEの使い方については下記の記事を参考にしてください。
http://qiita.com/mima_ita/items/64f6c2b8bb47c4b5b391