スタブ(Stub)の作り方で実装したアプリは、スタブの Return値が固定されていて、Return値を変えて試験する際、毎回ソースコードを修正してビルドし直す必要がありました。
今回は、本体アプリ実行中にも、スタブの Return値を動的に変更できる別アプリ(スタブコントローラー)を追加実装し、より柔軟な試験が出来るようにしました。
Visual Studio 2022 + .NET 6.0 から、.Net Framworkなみに Windowsフォームアプリの開発が出来るようになりました。
Visual Studio 2022 がなかなか安定せず、最近ようやく、Visual Studio 2022 + .NET 6.0 で Windowsフォームアプリを実装していて、変な動きに悩まされなくなってきました。
Visual Studio 2019 + .NET 5.0 の Windowsフォームアプリの開発では、DataGridViewのBindingが未完成で使い物になっていませんでしたが、Visual Studio 2022 + .NET 6.0 だと、Visual Studio 2019 + .Net Framwork 4.8.1 環境くらい、Windowsフォームアプリをサクサク実装できます。
ソースコードはGitHubで公開しています。
- InHouseProduct_onStub/InHouseProduct/InHouseProduct.csproj
- InHouseProduct_onStub/ThirdPartyProducts_Strub/ThirdPartyProducts.csproj
- InHouseProduct_onStub/InHouseProduct_onStub.sln
- InHouseProduct_onStub/StubController/StubController.csproj
- InHouseProduct_onStub/StubController/Form1.cs
- InHouseProduct_onStub/ThirdPartyProducts_Strub/StubSetting.cs
- InHouseProduct_onStub/ThirdPartyProducts_Strub/ThirdPartyProductOps.cs
- InHouseProduct_onStub/ThirdPartyProducts_Strub/StubSettingFile.cs
他社製品 I/Fライブラリ(DLL)
ThirdPartyProducts/ThirdPartyProducts/ThirdPartyProducts.csproj
前回からの差は、.NETのバージョンを .NET 5.0 から 6.0 へ上げただけです。
1 2 3 4 5 6 7 8 9 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> </Project> |
自社製品(本体)
InHouseProduct/InHouseProduct/InHouseProduct.csproj
前回からの差は、.NETのバージョンを .NET 5.0 から 6.0 へ上げただけです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net6.0-windows</TargetFramework> <Nullable>enable</Nullable> <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> <ItemGroup> <Reference Include="ThirdPartyProducts"> <HintPath>..\ClassLibrary_ThirdPartyProductsCapsule\ThirdPartyProductsLib\ThirdPartyProducts.dll</HintPath> </Reference> </ItemGroup> </Project> |
自社製品(Stub)
前回からの差は、.NETのバージョンを .NET 5.0 から 6.0 へ上げ、
Windowsフォームアプリの スタブコントローラープロジェクトを作成し、InHouseProduct_onStub.sln へ追加。
InHouseProduct_onStub.sln は、自社製品(Stub)の本体アプリ(InHouseProduct)とスタブコントローラー(StubController)とを、同時に起動してスタブの値が動的に反映されるようにしています。
InHouseProduct_onStub/InHouseProduct/InHouseProduct.csproj
.NETのバージョンを .NET 5.0 から 6.0 へ変更。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net6.0-windows</TargetFramework> <Nullable>enable</Nullable> <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> <ItemGroup> <Compile Include="..\..\InHouseProduct\InHouseProduct\Form1.cs" Link="Form1.cs" /> <Compile Include="..\..\InHouseProduct\InHouseProduct\Form1.Designer.cs" Link="Form1.Designer.cs" /> <Compile Include="..\..\InHouseProduct\InHouseProduct\Program.cs" Link="Program.cs" /> </ItemGroup> <ItemGroup> <EmbeddedResource Include="..\..\InHouseProduct\InHouseProduct\Form1.resx" Link="Form1.resx" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\ThirdPartyProducts_Strub\ThirdPartyProducts.csproj" /> </ItemGroup> </Project> |
InHouseProduct_onStub/ThirdPartyProducts_Strub/ThirdPartyProducts.csproj
.NETのバージョンを .NET 5.0 から 6.0 へ変更。
1 2 3 4 5 6 7 8 9 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> </PropertyGroup> </Project> |
InHouseProduct_onStub/InHouseProduct_onStub.sln
スタブコントローラー(StubController)を追加し、
デバッグ実行時、本体アプリプロジェクト(InHouseProduct)のWindowsフォーム画面と、スタブコントローラープロジェクト(StubController)のWindowsフォーム画面とが、同時に起動するように設定。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.32802.440 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InHouseProduct", "InHouseProduct\InHouseProduct.csproj", "{10C59127-32BB-4AAE-BA4C-91B6138CA1B4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ThirdPartyProducts", "ThirdPartyProducts_Strub\ThirdPartyProducts.csproj", "{AA135B0D-BAC1-4590-96E8-B9C65D49F65D}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StubController", "StubController\StubController.csproj", "{828FD915-4025-4B87-99DB-61040EF11AC7}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {10C59127-32BB-4AAE-BA4C-91B6138CA1B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {10C59127-32BB-4AAE-BA4C-91B6138CA1B4}.Debug|Any CPU.Build.0 = Debug|Any CPU {10C59127-32BB-4AAE-BA4C-91B6138CA1B4}.Release|Any CPU.ActiveCfg = Release|Any CPU {10C59127-32BB-4AAE-BA4C-91B6138CA1B4}.Release|Any CPU.Build.0 = Release|Any CPU {AA135B0D-BAC1-4590-96E8-B9C65D49F65D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AA135B0D-BAC1-4590-96E8-B9C65D49F65D}.Debug|Any CPU.Build.0 = Debug|Any CPU {AA135B0D-BAC1-4590-96E8-B9C65D49F65D}.Release|Any CPU.ActiveCfg = Release|Any CPU {AA135B0D-BAC1-4590-96E8-B9C65D49F65D}.Release|Any CPU.Build.0 = Release|Any CPU {828FD915-4025-4B87-99DB-61040EF11AC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {828FD915-4025-4B87-99DB-61040EF11AC7}.Debug|Any CPU.Build.0 = Debug|Any CPU {828FD915-4025-4B87-99DB-61040EF11AC7}.Release|Any CPU.ActiveCfg = Release|Any CPU {828FD915-4025-4B87-99DB-61040EF11AC7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {5674779C-E7A5-428C-9317-B0BAF65E1E89} EndGlobalSection EndGlobal |
InHouseProduct_onStub/StubController/StubController.csproj
Windowsフォームアプリとして新規作成した、スタブコントローラーのプロジェクト。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>WinExe</OutputType> <TargetFramework>net6.0-windows</TargetFramework> <Nullable>enable</Nullable> <UseWindowsForms>true</UseWindowsForms> </PropertyGroup> <ItemGroup> <ProjectReference Include="..\ThirdPartyProducts_Strub\ThirdPartyProducts.csproj" /> </ItemGroup> </Project> |
InHouseProduct_onStub/StubController/Form1.cs
スタブコントローラーの画面。
スタブの各プロパティ(MethodName列)が何を返すかを、ReturnCode列のコンボボックスで設定し、反映ボタンをクリックすることで、自社製品(本体)アプリに新しいスタブの値を渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
using System; using System.Collections.Generic; using System.Windows.Forms; using ThirdPartyProducts; namespace StubController { public partial class Form1 : Form { public List<StubSetting> _StubSettingList = new List<StubSetting>(); /// <summary> /// コンストラクタ /// </summary> public Form1() { InitializeComponent(); //各クラスとそのメンバを、設定項目としてリストアップ SetStubSettingList(typeof(ThirdPartyProductOps), ref _StubSettingList); //ReturnCodeの一覧をコンボボックスにセット this.returnCodeDataGridViewTextBoxColumn.DataSource = Enum.GetValues(typeof(ReturnCode)); this.returnCodeDataGridViewTextBoxColumn.DataPropertyName = "ReturnCode"; this.returnCodeDataGridViewTextBoxColumn.Name = "ReturnCode"; //I/F一覧をDataGridViewにセット this.dataGridView1.DataSource = _StubSettingList; //DataGridViewの各列の幅を調整 this.classNameDataGridViewTextBoxColumn.Width = 300; this.methodNameDataGridViewTextBoxColumn.Width = 150; this.returnCodeDataGridViewTextBoxColumn.Width = 150; } /// <summary> /// 反映ボタンクリック /// </summary> private void btnSave_Click(object sender, EventArgs e) { // 編集を確定 this.dataGridView1.EndEdit(); // 確定した値をファイルに反映 StubSettingFile.Write(_StubSettingList); } /// <summary> ///スタブの各クラスとそのメンバを、設定項目としてリストアップ /// </summary> private static void SetStubSettingList(Type tppClass, ref List<StubSetting> stubSettingList) { foreach (var member in tppClass.GetMembers()) { if (member.MemberType == System.Reflection.MemberTypes.Property) { stubSettingList.Add(new StubSetting() { ClassName = tppClass.FullName, MethodName = member.Name, ReturnCode = ReturnCode.Success }); } else if (member.MemberType == System.Reflection.MemberTypes.Method) { stubSettingList.Add(new StubSetting() { ClassName = tppClass.FullName, MethodName = member.Name, ReturnCode = ReturnCode.Success }); } } // メソッド名を昇順でソートしておく stubSettingList.Sort((a, b) => string.Compare(b.MethodName, a.MethodName) * -1); } } } |
※DataGridViewのコンボボックス型(DataGridViewComboBoxColumn)に、enumの値をリストアップする実装は、この書き方が一番シンプルだと思う。
returnCodeDataGridViewTextBoxColumn変数は、Form1.Designer.cs に定義されていて、
Form1.cs は、Designer.cs に定義されている変数から操作できます。
1 2 3 4 5 |
this.returnCodeDataGridViewTextBoxColumn.DataSource = Enum.GetValues(typeof(ReturnCode)); this.returnCodeDataGridViewTextBoxColumn.DataPropertyName = "ReturnCode"; this.returnCodeDataGridViewTextBoxColumn.Name = "ReturnCode"; |
InHouseProduct_onStub/ThirdPartyProducts_Strub/StubSetting.cs
スタブコントローラー画面のDataGridViewにバインドするデータクラス。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace ThirdPartyProducts { [Serializable] public class StubSetting { public string ClassName { get; set; } public string MethodName { get; set; } public ReturnCode ReturnCode { get; set; } } } |
InHouseProduct_onStub/ThirdPartyProducts_Strub/ThirdPartyProductOps.cs
スタブコントローラー画面の「反映」ボタンは、DataGridViewのデータを、jsonファイルとして保存し、スタブ本体画面のスタブは、その jsonファイルを読込むことで、スタブの値変更が動的に反映されるようにしています。
前回は固定で返していたスタブの値を、今回はjsonファイルから読込んだ値を返すように変更しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 |
using System; using System.Diagnostics; using System.Reflection; using System.Threading; namespace ThirdPartyProducts { public static class ThirdPartyProductOps { private static Status _IsStarted = Status.NotStarted; public static Status IsStarted { get { Debug.WriteLine("Call : " + typeof(ThirdPartyProductOps).FullName + "." + MethodBase.GetCurrentMethod().Name); Thread.Sleep(1000); return _IsStarted; } } public static ReturnCode Startup() { Debug.WriteLine("Call : " + typeof(ThirdPartyProductOps).FullName + "." + MethodBase.GetCurrentMethod().Name); Thread.Sleep(1000); return StubSettingFile.GetErrorCode(typeof(ThirdPartyProductOps).FullName, MethodBase.GetCurrentMethod().Name); } public static ReturnCode Shutdown() { Debug.WriteLine("Call : " + typeof(ThirdPartyProductOps).FullName + "." + MethodBase.GetCurrentMethod().Name); Thread.Sleep(1000); return StubSettingFile.GetErrorCode(typeof(ThirdPartyProductOps).FullName, MethodBase.GetCurrentMethod().Name); } public static ReturnCode Transaction1() { Debug.WriteLine("Call : " + typeof(ThirdPartyProductOps).FullName + "." + MethodBase.GetCurrentMethod().Name); Thread.Sleep(1000); return StubSettingFile.GetErrorCode(typeof(ThirdPartyProductOps).FullName, MethodBase.GetCurrentMethod().Name); } public static ReturnCode Transaction2() { Debug.WriteLine("Call : " + typeof(ThirdPartyProductOps).FullName + "." + MethodBase.GetCurrentMethod().Name); Thread.Sleep(1000); return StubSettingFile.GetErrorCode(typeof(ThirdPartyProductOps).FullName, MethodBase.GetCurrentMethod().Name); } } } |
InHouseProduct_onStub/ThirdPartyProducts_Strub/StubSettingFile.cs
jsonファイルを高速に処理する為のクラス。
※Jsonの Serialize/Deserializeは、JsonSerializerOptionsを指定して、複雑で巨大なJsonデータも扱えるようにし、ファイル書込み読込み最速の File.WriteAllText()/File.ReadAllText()を使ったこちらの実装方式、お勧めです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Serialization.Formatters.Binary; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Threading.Tasks; namespace ThirdPartyProducts { public static class StubSettingFile { private static readonly string StubSettingListFileName = Path.GetTempPath() + @"StubSettingList.json"; private static JsonSerializerOptions _JsonSerializerOptions = new JsonSerializerOptions() { ReferenceHandler = ReferenceHandler.Preserve, WriteIndented = true }; public static void Write(List<StubSetting> stubSettingList) { var jsonData = JsonSerializer.Serialize(stubSettingList, _JsonSerializerOptions); File.WriteAllText(StubSettingListFileName, jsonData); } public static ReturnCode GetErrorCode(string classname, string methodname) { if (!File.Exists(StubSettingListFileName)) { return ReturnCode.Success; } var jsonData = File.ReadAllText(StubSettingListFileName); var stubSettingList = JsonSerializer.Deserialize<List<StubSetting>>(jsonData, _JsonSerializerOptions); return stubSettingList.First(x => x.ClassName == classname && x.MethodName == methodname).ReturnCode; } } } |
コメント