redwarrior’s diary

C#, ASP.NET, WEB, G*などの雑多な情報

(小ネタ)C#のユニットテストで public でないメソッドをテストする方法(MSTest)

Javaでは、publicでないメソッド(かつ、privateでもない)をテストする場合は、packageを一緒にすれば良かったのですが、C#ではどうやるんだろうと思って調べました。

参照先の通りにやったら出来たので、特に追記することはありません。

ちなみに、privateメソッドはこの方法でもテストできません。この場合は、リフレクションを使うのでしょう。

(小ネタ)C#でメソッドの引数に値渡しが行われるのは、値型のみ

自作クラスなどの参照型の引数に ref とか out とかつけるのは意味がなく、m9(^Д^)プギャーされるのでやめた方が良いですね。

自作の構造体や列挙型は値型なので、参照渡しが必要ならばつけましょう。

LINQを使用したメソッドの共通化の試行錯誤

LINQを使用して、DBから取得したデータでDTOを構築するメソッドを作成していると、インターフェイスは違うのに処理の流れがほぼ同じになる事ってありませんか?

どういうことかと言うと、片方の処理では、単純なLINQを使用しているのでIQueryable<T>を返します。もう片方の処理では、GroupByを使用しているので、IQueryable<IGrouping<int, T>>を返します。

このIQueryable<T>とIGrouping<int, T>の変数に対して呼び出すメソッドと処理フローがソースコード上は、同じにすることが出来ます。

この処理フローをメソッドとして抽出すると、パラメータの型だけが異なり中身が同じメソッドが出来上がります。

CirculationDto BuildDto(IQueryable<Circulation> circulations)
{
    var dto = new CirculationDto();
    var first = circulations.First();
    dto.DistrictId = first.Agency.DistrictId;
    dto.AgencyId = first.AgencyId;
    dto.Daily =
        circulations.Where(circulation => circulation.PaperId == 1)
            .Sum(circulation => circulation.value);
    dto.Weekly =
        circulations.Where(circulation => circulation.PaperId == 2)
            .Sum(circulation => circulation.value);

    return dto;
}

IQueryable<T>を引数に取るメソッド

CirculationDto BuildDto(IGrouping<int, Circulation> circulations)
{
    var dto = new CirculationDto();
    var first = circulations.First();
    dto.DistrictId = first.Agency.DistrictId;
    dto.AgencyId = first.AgencyId;
    dto.Daily =
        circulations.Where(circulation => circulation.PaperId == 1)
            .Sum(circulation => circulation.value);
    dto.Weekly =
        circulations.Where(circulation => circulation.PaperId == 2)
            .Sum(circulation => circulation.value);

    return dto;
}

IGrouping<int, T>を引数に取るメソッド

このメソッドを共通化できるか試行錯誤してみました。

共通の親インターフェイスであるIEnumerable<T>をパラメータに持つメソッドを作成する

最初、これを試してみましたが、IEnumerable<T>のLINQメソッドを使用すると、DBから全件を取得してメモリ上で絞り込みを行うという情報を見つけたため、やめました。

IQueryable<T>をパラメータに持つメソッドを使用し、IGrouping<int, T>型の変数は呼び出すときにAsQueryableメソッドを使用する

基本的には上手くいきそうでしたが、別の問題が発生してしまいました。IQueryable<T>でSelectメソッドでインデックスのついた版を実行したところ、DBから取得するときに例外が発生したため、元に戻してしまいました。

共通化しない

もしかしたらできるのかもしれませんが、今回は共通化しない事にしました。

処理フローは、「IQueryable<T>は、foreach文の中でほとんどの処理を行う。」、
「IGrouping<int, T>は、共通プロパティはFirstメソッドで最初の1データを取得して使用し、集計するプロパティはforeach文で集計を行う。」 といった感じで、それぞれのインターフェイスを考慮したフローに変更しました。

以下が変更後のソースです。

CirculationDto BuildDto(IQueryable<Circulation> circulations)
{
    var dto = new CirculationDto();

    foreach (var circulation in circulations)
    {
        var agency = circulation.Agency;

        dto.DistrictId = agency.DistrictId;
        dto.AgencyId = agency.AgencyId;

        switch (circulation.OrganPaperId)
        {
            case 1:
                dto.Daily = circulation.IncreaseAndDecrease;
                dto.CurrentDaily = circulation.Current;
                break;
            case 2:
                dto.Weekly = circulation.IncreaseAndDecrease;
                dto.CurrentWeekly = circulation.Current;
                break;
        }
    }

    return dto;
}

IQueryable<T>を引数に取るメソッド

CirculationDto BuildDto(IGrouping<int, Circulation> circulations)
{
    var dto = new CirculationDto
                  {
                      DistrictId = circulations.First().Agency.DistrictId,
                      AgencyId = circulations.Key
                  };

    foreach (var circulation in circulations)
    {
        switch (circulation.OrganPaperId)
        {
            case 1:
                dto.Daily = circulation.IncreaseAndDecrease;
                dto.CurrentDaily = circulation.Current;
                break;
            case 2:
                dto.Weekly = circulation.IncreaseAndDecrease;
                dto.CurrentWeekly = circulation.Current;
                break;
        }
    }

    return dto;
}

IGrouping<int, T>を引数に取るメソッド

まだまだ、LINQに慣れていない部分もあるので、もっといい方法があるのかもしれないですね。情報があれば教えてください。

PowerShellでGit操作が楽になるposh-gitをインストールしてみた。

以前の投稿で、PowerShellでGitコマンドを実行できるようになりました。

redwarrior.hateblo.jp

その時に行ったposh-gitのインストールと設定についてまとめておこうと思います。

PowerShellの設定

PowerShellを使用して、プロキシ経由でファイルをダウンロードできるようにする設定をします。

Powershell Proxy経由でファイルをダウンロードする - Yoshimopedia

PsGetのインストール

posh-gitのリポジトリをgit cloneしても良いのですが、どこにダウンロードするのかを考えるのが面倒だったので、PsGetを使用することにしました。

以下のコマンドでインストールします。

(new-object Net.WebClient).DownloadString("http://psget.net/GetPsGet.ps1") | iex

PowerShellでGitShellを使う | MBA-HACK

posh-gitのインストール

PsGetを使用してposh-gitをインストールします。

PsGet - PowerShell Modules Directory - posh-git

プロファイルの設定

以下の「3. posh-git フォルダを消しても動くようにする」を参照して、Microsoft.PowerShell_profile.ps1ファイルを編集します。

PsGetでインストールするとフォルダの作成や、profile.example.ps1ファイルの配置は既に行われていました。

Windows PowerShell 使いは posh git を入れよう - ハトネコエ Web がくしゅうちょう

確認方法

PowerShellのコンソールで、Gitリポジトリのフォルダに移動すると表示が変化します。

以上です。

(小ネタ)GitコマンドをPowerShellスクリプトに組み込む最も簡単な方法

前回の記事で、ClickOnceアプリケーションをコマンドラインから発行する方法を書きましたが、
どうせならば、バージョン管理システムからソースコードをダウンロードして、ビルドが出来るようにしたいと思い、GitコマンドをPowerShellスクリプトに組み込む方法を調べました。

調べてみると、posh-gitという便利なものがあり、それを使ってみましたが、

github.com

インストール時に気がついたことが……。

Gitのbinディレクトリを環境変数PATHに設定すれば、コマンドプロンプトでもPowerShellでも関係なく使えるんじゃ……?

そうです。Gitの操作をするには、Git Bashや、TortoiseGitや、SourceTreeや、VisualStudioのGit拡張機能を使わないと快適に操作が出来ませんが、スクリプトに組み込むならば、コマンドが実行できれば良いのです。

つまり、GitコマンドをPowerShellスクリプトに組み込む最も簡単な方法は、環境変数PATHに追加する」でした。

以上です。

ClickOnceアプリケーションをコマンドライン(MSBuild)から発行する方法

ClickOnceアプリケーションをコマンドラインもしくはPowerShellから、MSBuildを使用して発行する方法がわかったので、忘備録としてまとめておく。

MSDN@ITで必要な情報は足ります。

MSDN

ClickOnce のセキュリティと配置

MSBuild

ClickOnce アプリケーションのコマンド ラインからのビルド

@IT

.NETビルド・エンジン「MSBuild」使いこなし術 − @IT

ClickOnceアプリをコマンドラインから発行するには?[2.0のみ、C#、VB] − @IT

作業の流れ

  1. プロジェクトを作成して、発行ウィザードから一度発行する

  2. MSBuildから、ClickOnceの発行を行う

  3. PowerShellMSBuildを実行するスクリプトを作成する

  4. 発行されたアプリケーションをアップロードする機能を作成する

  5. 発行とファイルアップロードを一度に行う

1.プロジェクトを作成して、発行ウィザードから一度発行する

Visual Studiow 2013を起動して、Windowsフォームアプリケーションを作成します。今回は、「SampleForms」とします。

作成したら、アプリを実行して動作することを確認します。その後、通常通り右クリックから「発行」を選んでウィザードに従って操作をします。

Webサーバー上からインストールできるようにしたいので、最初の画面には、配置先IISの仮想ディレクトリのパスを設定し、次の画面で「Webサイトから」を選択して、URLにWebサーバー上のアドレスを設定します。

このURLはインストールするアプリから参照できる必要があります。

そのまま次の画面で「はい」を選択して完了します。そうするとテスト用証明書が自動で作成されて、配置先IISにアプリケーションが配置されます。

2.MSBuildから、ClickOnceの発行を行う

コマンドラインからMSBuildを使用して、ClickOnceの発行を行ってみます。この場合、アプリケーションのアップロードまでは行ってくれないため、後でアプリケーションのアップロードを行う機能を作成します。

「開発者コマンド プロンプト for VS2013」というコマンドプロンプトを使用します。Visual Studio 2013と同じ場所にある「Visual Studio ツール」を開くと、ショートカットがあるので、それを実行します。

そうすると、「開発者コマンド プロンプト for VS2013」という名前がついたコマンドプロンプトが起動しますので、Windowsフォームアプリケーション「SampleForms」のディレクトリに移動します。

以下のコマンドを実行するとClickOnceの発行を行うことが出来ます。

>msbuild SampleForms.csproj /t:Publish

このコマンドを実行すると、プロジェクトのbin/構成名(DebugやRelease)フォルダの下のapp.publishフォルダに、発行用の各ファイルが作成されます。
(ウィザードを使用した発行でも同じフォルダを使うため、今回の場合はファイルが更新されます)

このファイル群を配置先IISのウィザードで指定したフォルダに持っていくと配置することが出来ます。

3.PowerShellMSBuildを実行するスクリプトを作成する

参考サイトではバッチファイルからMSBuildを実行していましたが、今風にPowerShellを使います。以下の内容を記入したファイルにps1という拡張子をつけて、プロジェクトの直下に保存します。

$env:Path += ";C:\Program Files (x86)\MSBuild\12.0\Bin\"
Set-Location (Split-Path ( & { $myInvocation.ScriptName } ) -parent)
msbuild SampleForms.csproj /t:Publish

1行目は、パスの設定をしています。2行目では、実行フォルダをPowerShellスクリプトのあるフォルダにします。3行目は上と同じコマンドです。

Visual Studio上からPowerShellを実行するために、「PowerShell Tools for Visual Studio 2013」という拡張機能を使いました。

4.発行されたアプリケーションをアップロードする機能を作成する

こちらも基本的な考え方は、参考サイトと同じです。新しいバージョンでは便利なタスクが増えているのでそれを使用しています。

csprojファイルの最後に以下を追加します。

  <Target Name="CopyPublishedApplication">
    <ItemGroup>
      <MySourceFiles Include="$(PublishDir)**\*.*" Exclude="$(PublishDir)$(MSBuildProjectName).exe" />
    </ItemGroup>
    <PropertyGroup>
      <AppricationDir>$(_DeploymentApplicationDir.Substring($(PublishDir.Length)))</AppricationDir>
    </PropertyGroup>
    <!-- Output Message
    <Message Text="PublishUrl = $(PublishUrl)"/>
    <Message Text="DeployDir  = $(_DeploymentApplicationDir)"/>
    <Message Text="AppDir     = $(AppricationDir)"/>
    -->
    <Message Text="PublishDir = $(PublishDir)" />
    <Warning Condition="Exists('$(PublishUrl)\$(AppricationDir)')" Text="$(PublishUrl)\$(AppricationDir) is Exists!" />
    <!--Copy Published Application-->
    <Copy SourceFiles="@(MySourceFiles)" DestinationFiles="@(MySourceFiles->'$(PublishUrl)%(RecursiveDir)%(Filename)%(Extension)')" />
  </Target>
</Project>

$(PublishDir)がapp.pulishフォルダのパスを示します。ウィザードで発行したIIS上のフォルダを参照すると、アプリケーション名.exeが含まれていないので、コピー対象から外しています。

$(AppricationDir)は、ClickOnceによって作られるバージョン番号付きのフォルダ名です。コピー先に同名フォルダの有無をチェックするために使用しています。

フォルダがある場合は、Warningタグで警告メッセージを出します。タグをErrorにすればエラーを発生させて処理を停止することも出来ます。

Copyタグで対象ファイルを指定したフォルダにコピーします。再帰的にフォルダの中身もコピーしてくれるのでとても便利です。

5.発行とファイルアップロードを一度に行う

あとは先程のPowerShellスクリプトMSBuild実行部分を以下のように変えれば、発行とファイルの配置を一度に行うことが出来ます。

msbuild SampleForms.csproj /t:Publish /t:CopyPublishedApplication /p:Configuration=Release /p:PublishUrl="C:\inetpub\wwwroot\SampleForms\" /p:InstallUrl="http://localhost/SampleForms/" /p:ApplicationVersion="1.0.2.0"

CopyPublishedApplicationがcsprojファイルに追加したTargetです。

ConfigurationでRelease構成を指定しています。「Configuration Transform」という拡張機能を導入することで構成によって設定値を変更することが出来ます。

PublishUrlがプロジェクトのプロパティ画面での「発行フォルダーの場所」で、InstallUrlが「インストールフォルダーのURL」です。
ローカルのIISを指定していますが、共有フォルダにあるIISならば、同じような記述で出来ると思います。

ApplicationVersionが「バージョンの発行」で、最後を * にするとリビジョン番号が代わりに設定されます。

オプションを設定しない場合は、csprojファイルに設定されている値が使用されます。

対応していない機能

リリース用に使おうと思っていたので、以下の機能は考慮していません。

  • htmlファイルの作成
  • リビジョンの自動インクリメント

以上です。

環境:

Windows 8.1 Pro

Visual Studio 2013 Professional

ClickOnceアプリケーションをMSBuildで発行しようとしたらエラー発生(したけど解決した)

ClickOnceアプリケーションをMSBuildを使用して発行しようとしたら、エラーに遭遇したのでメモを残しておきます。

エラーメッセージは、以下の通りです(見辛いので改行しています)

C:\windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets(4513,5)
: warning MSB3155
: 項目 '.NETFramework,Version=v4.5' が 'C:\Users\yiritani\Source\Repos\BuildTest\SampleForms\SampleForms' で見つかりませんでした。
[C:\Users\yiritani\Source\Repos\BuildTest\SampleForms\SampleForms\SampleForms.csproj]

C:\windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets(4513,5)
: error MSB3147
: 必要なファイル 'setup.bin' が 'C:\Users\yiritani\Source\Repos\BuildTest\SampleForms\SampleForms\Engine' で見つかりませんでした。
[C:\Users\yiritani\Source\Repos\BuildTest\SampleForms\SampleForms\SampleForms.csproj]

Visual Studio 2013 Professionalも入っているし、なんでだろうと思ったら、同じ症状が海外ブログにのっていました。

error MSB3147: Could not find required file ‘setup.bin’ in Alkampfer's Place |

どうやら、参照しているMSBuildが違っているみたいで、ブログの通りに使用するMSBuildを以下のように変更したら、解決しました。

環境:

Windows 8.1 Pro

Visual Studio 2013 Professional