Pesterの結果からFailureのみを抽出する

PowerShellには、Pesterというユニットテスト用のフレームワークが標準で用意されています。
Pesterのテスト結果はXMLファイルで出力されるのですが、テストケースが多い場合、また多数のサーバーでテストをする場合、テスト結果の中からエラーになった結果(Failure)を捕捉しにくいという欠点がありました。

そこで、複数のテスト結果のXMLファイルの中から、エラーになった結果(Failure)のみを抽出して、Excelファイルに一覧化して出力する方法を記載します。

using ClosedXML.Excel;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
using System.Xml.XPath;

namespace FailureExtractFromXML
{
    class Program
    {
        static void Main(string[] args)
        {
            string stCurrentDir = Directory.GetCurrentDirectory();

            IEnumerable<string> xmlFiles = Directory.EnumerateDirectories(
                    stCurrentDir, "*", SearchOption.AllDirectories)
                    .SelectMany(d => Directory.EnumerateFiles(d))
                    .Where(f => new[] { ".xml" }.Contains(Path.GetExtension(f)));

            var exportFile = Path.Combine(stCurrentDir,$"UTFailureResult_{DateTime.Now:yyyyMMddHHmmss}.xlsx");

            using(var workbook = new XLWorkbook())
            {
                var worksheet = workbook.Worksheets.Add("UTFailureResult");
                worksheet.Style.Font.FontName = "Meiryo UI";

                worksheet.Cell("A1").Value = "nodename";
                worksheet.Cell("B1").Value = "name";
                worksheet.Cell("C1").Value = "description";
                worksheet.Cell("D1").Value = "message";
                worksheet.Cell("E1").Value = "stacktrace";

                var i = 2;
                foreach (var xmlFile in xmlFiles)
                {
                    var nodeName = Path.GetFileNameWithoutExtension(xmlFile);
                    var xdocText = XDocument.Load(xmlFile).ToString();
                    var xfailures = XDocument.Parse(xdocText).XPathSelectElements($"/test-results/test-suite/results").Descendants("failure");

                    foreach (var xfailure in xfailures)
                    {
                        worksheet.Cell(i,1).Value = nodeName;
                        worksheet.Cell(i,2).Value = xfailure.Ancestors().First().Attribute("name").Value;
                        worksheet.Cell(i,3).Value = xfailure.Ancestors().First().Attribute("description").Value;
                        worksheet.Cell(i,4).Value = xfailure.Element("message").Value.Replace("\n"," ");
                        worksheet.Cell(i,5).Value = xfailure.Element("stack-trace").Value.Replace("\n"," ");
                        i++;
                    }

                }

                workbook.SaveAs(exportFile);

            }
        }
    }
}

参考サイト
qiita.com

コレクションを分割してマルチスレッド処理を実行する

foreachのループ処理でコレクション内データを取得し、ループ内でシングルスレッド処理を行うことがあるかと思います。
foreach内でや大量データのバッチ処理や重い処理(APIによるCRUD処理など)を行う場合に、
予めコレクションを分割し、マルチスレッド処理にする方法を記載します。

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

namespace CollectionSplit
{
    class Program
    {
        static void Main(string[] args)
        {
            //0~11000までの連番をListに追加
            var allList = Enumerable.Range(0, 11000).ToList();

            var splitLists = GetSplitList(allList).Select(i => i.ToList()).ToList();

            List<Task> taskList = new List<Task>();

            //分割したコレクションをループし、マルチスレッド処理を実行
            foreach (var splitList in splitLists.Select((item, index) => new { item, index }))
            {
                taskList.Add(Task.Factory.StartNew(() =>
                {
                    foreach (var splitItem in splitList.item)
                    {
                       //実際はここで重い処理(APIによるCRUD処理など)を実行
                        Console.WriteLine($"index: {splitList.index}, value: {splitItem}");
                    }
                },
                TaskCreationOptions.LongRunning));
            }
            Task.WaitAll(taskList.ToArray());

            Console.ReadLine();
        }

        /// <summary>
        /// コレクション分割メソッド
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="allList"></param>
        /// <returns></returns>
        private static IEnumerable<IEnumerable<T>> GetSplitList<T>(List<T> allList)
        {
            //コレクションを分割する単位を指定(この例では16分割する)
            int parallelCount = 16;

            var cntPerTask = allList.Count < parallelCount ? allList.Count : allList.Count / parallelCount;
            var cntMod = allList.Count % parallelCount;

            int count = 0;
            for (int i = 0; i < parallelCount; i++)
            {
                if (i != parallelCount - 1)
                {
                    yield return allList.Skip(count).Take(cntPerTask);
                    count += cntPerTask;
                }
                else
                {
                    //余りは全て最後のコレクションに追加
                    if (cntMod != 0)
                        cntPerTask += cntMod;

                    yield return allList.Skip(count).Take(cntPerTask);
                }
            }
        }
    }
}

参考サイト
ufcpp.net

実行結果

f:id:katharsis1721:20190117233530p:plain

マッピングクラスを使わないCsvHelperによるCSV取り込み方法

以下のサイトにあるように
CsvHelperによるCSV取り込みには、マッピングクラスを使わない方法もありますが、
CsvHelperのクラス構成がv7.0.0以降大きく変わったのに伴い、変わっていたのでメモしておきます。

kageura.hatenadiary.jp

参考サイト
github.com

ClosedXMLを使ってExcelファイルからデータを読み取る方法

CSVファイルやテキストファイルがデータを読み取る場合は、File.ReadAllLinesメソッドなどを使用して簡単に読み取りができます。

CSVファイルやテキストファイルからだけでなく、Excelファイルからデータを読み取り、
Dynamics CRMに何らかのデータ処理を行いたい要件も比較的多いと思いますが、
ExcelファイルについてもClosedXMLライブラリを使うことで簡単に読み取りができるようになります。
他にもNPOI やEPPlusといったオープンソースライブラリがよく使用されています。

読み取りしたExcelファイルのサンプルデータ
f:id:katharsis1721:20180409225846p:plain

この例ではエンティティの作成をしていますが、もちろんデータ処理を行うこともできます。
読み取ったデータごとにエンティティの作成、更新、削除をそれぞれ分けたい場合は、フラグ用のカラム(Create,Update,Delete)を追加して処理を分けるしかないかと思います。

また組織サービス(OrganizationService)によるDynamics CRMへの接続は、CrmConnectionクラスを使用するより、以下のようにapp.configに設定したconnectionStringをパラメーターとしてそのまま渡すほうがより簡単に接続ができます。

参考サイト
qiita.com

Microsoft Dynamics CRM 2015 へのより簡単な接続

Dynamics CRMグローバルオプションセット一覧がほしい時に実行するSQL

Dynamics CRMのカスタムグローバルオプションセットの一覧がほしい時に実行するSQLです。

SELECT
	PicklistValue.Label,
	PicklistValue.Name,
	--PicklistValue.OptionSetId,
	LLLV2.Label,
	PicklistValue.Value
FROM LocalizedLabelLogicalView AS LLLV2
INNER JOIN
(
	-- カスタムグローバルオプションセットのPickListIDを表示
	SELECT
		OSIdLabel.OptionSetId,
		OSIdLabel.Label,
		OSIdLabel.Name,
		APVLV.Value,
		APVLV.AttributePicklistValueId
	FROM   AttributePicklistValueLogicalView AS APVLV
	INNER JOIN 
	(
		-- カスタムグローバルオプションセットの表示名と名前を表示
		SELECT
			OSLV.OptionSetId,
			LLLV.Label,
			OSLV.Name
		FROM OptionSetLogicalView AS OSLV
		INNER JOIN LocalizedLabelLogicalView AS LLLV ON OSLV.OptionSetId = LLLV.ObjectId
		WHERE LLLV.ObjectColumnName = 'DisplayName'
		AND OSLV.IsGlobal = 1
		AND OSLV.IsCustomOptionSet = 1
	) AS OSIdLabel
	ON OSIdLabel.OptionSetId = APVLV.OptionSetId
) AS PicklistValue
ON LLLV2.ObjectId = PicklistValue.AttributePicklistValueId
WHERE LLLV2.ObjectColumnName = 'DisplayName' 


LocalizedLabelLogicalViewやMetadataSchema.LocalizedLabelには、
オプションセットのプルダウン内の各ラベルや値はもちろんですが、オプションセット自体の表示名と名前もデータが入っているんですね。
LabelTypeCode列の値が1の場合、プルダウン内の各ラベルの表示で、
LabelTypeCode列の値が10の場合、オプションセット自体の表示名を表します。


参考サイト

How to query CRM Global OptionSet Values in SQL? | Arun Potti's MS CRM blog

WSDLによるSOAP連携

外部システムからWSDLによるSOAP連携によりデータを取得する機会があったのですが、以外とどこにも連携方法の情報がまとまっていなかった気がするのでまとめます。

WSDLからWebサービスプロキシクラスを作成する。
プロキシクラスを作成する方法は2つあって、1つはSrcUtil.exeをコマンドラインから実行する方法であり、もう1つはVisualStudioの「サービス参照の追加」のアドレスにエンドポイントのアドレスを指定する方法です。
※移動ボタンを押してもサービスが見つからない場合、アドレスの末尾に「?wsdl」を付けて再度移動ボタンを押してみてください。

f:id:katharsis1721:20170314000509p:plain

サービスが見つかりOKボタンを押すと、Webサービスプロキシクラス(CSファイル)とapp.configに設定が追加されます。

②①で作成したプロキシクラスを使用してWebサービスへ接続、データ取得する。

       //Webサービスプロキシクラスを使用して、Webサービスとの接続を行う。
       BasicHttpBinding myBinding = new BasicHttpBinding();
       myBinding.Name = "任意のBinding名";
       EndpointAddress endPointAddress = new EndpointAddress("公開されているエンドポイントのアドレス");
       ChannelFactory<IService> factory = new ChannelFactory<IService>(myBinding,endPointAddress);
       IService channel = factory.CreateChannel();

       //ここからは各WSDLの仕様に依存する。
       //各WSDLが用意しているメソッドを指定することでほしいデータを取得する。
       //ただ任意の引数を指定して、Responseデータを取得する方法に大きな違いはないはず。
       getHogeHogeResponse hogehogeResponse = new getHogeHogeResponse();
       hogehogeResponse = channel.getHogeHoge(任意の引数);

       factory.Close();


参考サイト

code.msdn.microsoft.com

www.smartllc.jp

Azure Recommendations APIを使ってDynamics CRM上から推奨製品をだしてみた

こちらに書かれているようにまだプレビュー版ですが(2016年9月現在)標準機能で「クロスセル製品推奨事項」機能が備わっています。

memo.tyoshida.me

この標準機能と似たようなことがMicrosoft Cognitive ServicesのAzure Recommendations APIを使ってできないかなと思い、以下のサイトを参考に試してみました。


blog.shibayan.jp

new_recommendationlink.htmlからnew_recommendation.htmlを読み出す形で作成し、
new_recommendationlink.htmlをWebリソースとしてCRM画面に埋め込んでいます。

new_recommendationlink.html

new_recommendation.html



f:id:katharsis1721:20160905224934p:plain

関連性(Rating)の高い上位10件の製品を出力することができました。

f:id:katharsis1721:20160905224016p:plain