序章
私がこの記事を読んで見つけた正しい方法
それは長い話です、あなたのほとんどはそれをスキップすることができます:)しかし、問題と解決策を理解したい人は、これをすべて読まなければなりません!
私はQAです。しばらく前に、クリックした製品の自動化を担当するようになりました。幸い、このオートマトンは一部のテストツールではなく、Visual Studioで実行されるため、開発に最大限に近づいています。
自動化には、MbUnit(ランナーとしてのGallio)とMINT(一緒に作業する顧客によって作成されたMbUnitへの追加)で構成されるフレームワークを使用します。MbUnitはテストフィクスチャとテストを提供し、MINTはさらに小さなレイヤー(テスト内のアクション)を追加します。例。フィクスチャは「FilteringFixture」と呼ばれます。これは、「TestingFilteringById」や「TestingFilteringWithSpecialChars」などのテストの量で構成されます。各テストは、テストのアトミックユニットであるアクションで構成されます。アクションの例は次のとおりです-'アプリを開く(パラメーター)'、'OpenFilterDialog'など。
私たちはすでにたくさんのアクションを含むたくさんのテストを持っています、それは混乱です。彼らは私たちがQAした製品の内部APIを使用しています。また、新しい自動化アプローチであるMicrosoft UIオートメーションによるUI自動化の調査を開始します(トートロジーについては申し訳ありません)。そのため、管理者にとって「エクスポーター」または「レポーター」ツールの必要性が厳しくなりました。
少し前に、DLL(すべてのフィクスチャ、テスト、アクションを含む)を解析し、その構造を人間が読める形式(TXT、HTML、CSV、XML、その他)でエクスポートできるアプリケーションを開発するタスクがありました。 )。でも、その直後、休暇(2週間)に行きました。
たまたま、ガールフレンドは休暇まで家族のところに行き(彼女もそれを手に入れました)、私は一人で家にいました。この間(2週間)何をすべきかを考えて、その「エクスポートツールの作成タスク」と、WPFの学習を開始する予定の期間を思い出します。そこで、休暇中に仕事をすることにしました。また、WPFにアプリケーションをドレスアップすることにしました。その時、MVVMについて何か聞いたので、純粋なMVVMを使用して実装することにしました。
フィクスチャなどを使用してDLLを解析できるDLLは、かなり高速に記述されていました(〜1〜2日)。その後、私はWPFを使い始めました。この記事では、WPFがどのように終了したかを説明します。
私は休暇の大部分(ほぼ8日!)を過ごし、頭とコードでそれを整理しようとしました、そして最後に、それは(ほぼ)完了しました。私のガールフレンドは私がずっとやっていたことを信じませんでしたが、私には証拠があります!
他の人が同様の問題を回避できるように、疑似コードでソリューションを段階的に共有します。この答えは、チュートリアル=)のように見えます(本当に?)。WPFを最初から学習するときに最も複雑なことを知りたい場合は、すべてをMVVMとf *gTreeViewバインディングにしてください。
ソリューションを含むアーカイブファイルが必要な場合は、少し後で、私が決定したときに、それだけの価値があると判断できます。制限の1つは、アクションをもたらすMINT.dllを共有できるかどうかわからないことです。これは、当社の顧客によって開発されたものです。しかし、私はそれを削除して、アプリケーションを共有することができます。アプリケーションは、フィクスチャとテストに関する情報のみを表示できますが、アクションに関する情報は表示できません。
自慢の言葉。ほんの少しのC#/ WinForms / HTMLのバックグラウンドと練習なしで、私はこのバージョンのアプリケーションをほぼ1週間で実装することができました(そしてこの記事を書きます)。だから、不可能は可能です!私のような休暇を取って、それをWPF学習に費やしてください!
ステップバイステップのチュートリアル(添付ファイルなし)
タスクの短い繰り返し:
少し前に、DLL(テストフィクスチャ、テストメソッド、アクション-単体テストベースの自動化フレームワークのユニットを含む)を解析し、その構造を人間が読める形式(TXT)でエクスポートできるアプリケーションを開発するタスクがありました。 、HTML、CSV、XML、その他)。WPFと純粋なMVVMを使用して実装することにしました(どちらも私にとってはまったく新しいものでした)。私にとって最も難しい2つの問題は、MVVMアプローチ自体であり、次にMVVMがTreeViewコントロールにバインドすることでした。MVVM分割についての部分はスキップします。これは、別の記事のテーマです。以下の手順は、MVVMの方法でTreeViewにバインドすることについてです。
- それほど重要ではありません:ユニットテストでDLLを開き、リフレクションを使用してフィクスチャ、テストメソッド、およびアクション(より小さなレベルのユニットテスト、当社で作成)を見つけることができるDLLを作成します。それがどのように行われたかに興味がある場合は、ここを見てください:Reflectionを使用した関数/メソッドコンテンツの解析
- DLL:フィクスチャ、テスト、アクション(データモデル、エンティティモデル?)の両方に対して個別のクラスが作成されます。これらをバインドに使用します。ツリーのエンティティモデルは何になるかを自分で考える必要があります。主なアイデア-ツリーの各レベルは、ツリー内のモデルを表すのに役立つプロパティを使用して、適切なクラスで公開する必要があります(理想的には、モデルまたはモデルの一部として、MVVMで適切に配置されます)。私の場合、エンティティ名、子のリスト、序数に興味がありました。序数は、DLL内のコード内のエンティティの順序を表す番号です。TreeViewに序数を表示するのに役立ちますが、それが正しいアプローチかどうかはわかりませんが、機能します。
public class MintFixutre : IMintEntity
{
private readonly string _name;
private readonly int _ordinalNumber;
private readonly List<MintTest> _tests = new List<MintTest>();
public MintFixutre(string fixtureName, int ordinalNumber)
{
_name = fixtureName;
if (ordinalNumber <= 0)
throw new ArgumentException("Ordinal number must begin from 1");
_ordinalNumber = ordinalNumber;
}
public List<MintTest> Tests
{
get { return _tests; }
}
public string Name { get { return _name; }}
public bool IsParent { get { return true; } }
public int OrdinalNumber { get { return _ordinalNumber; } }
}
public class MintTest : IMintEntity
{
private readonly string _name;
private readonly int _ordinalNumber;
private readonly List<MintAction> _actions = new List<MintAction>();
public MintTest(string testName, int ordinalNumber)
{
if (string.IsNullOrWhiteSpace(testName))
throw new ArgumentException("Test name cannot be null or space filled");
_name = testName;
if (ordinalNumber <= 0)
throw new ArgumentException("OrdinalNumber must begin from 1");
_ordinalNumber = ordinalNumber;
}
public List<MintAction> Actions
{
get { return _actions; }
}
public string Name { get { return _name; } }
public bool IsParent { get { return true; } }
public int OrdinalNumber { get { return _ordinalNumber; } }
}
public class MintAction : IMintEntity
{
private readonly string _name;
private readonly int _ordinalNumber;
public MintAction(string actionName, int ordinalNumber)
{
_name = actionName;
if (ordinalNumber <= 0)
throw new ArgumentException("Ordinal numbers must begins from 1");
_ordinalNumber = ordinalNumber;
}
public string Name { get { return _name; } }
public bool IsParent { get { return false; } }
public int OrdinalNumber { get { return _ordinalNumber; } }
}
ところで、私はすべてのエンティティを実装する以下のインターフェースも作成しました。このようなインターフェースは、将来的に役立ちます。Childrens
それでもわからないのですが、タイプのプロパティなども追加する必要がありますList<IMintEntity>
か?
public interface IMintEntity
{
string Name { get; }
bool IsParent { get; }
int OrdinalNumber { get; }
}
- DLL-データモデルの構築:DLLには、単体テストとデータの列挙でDLLを開くメソッドがあります。列挙中に、以下のようなデータモデルを構築します。実際のメソッドの例を示します。リフレクションコア+Mono.Reflection.dllが使用されていますが、複雑さと混同しないでください。
_fixtures
必要なものはすべて-メソッドがリストをエンティティで埋める方法を確認してください。
private void ParseDllToEntityModel()
{
_fixutres = new List<MintFixutre>();
// enumerating Fixtures
int f = 1;
foreach (Type fixture in AssemblyTests.GetTypes().Where(t => t.GetCustomAttributes(typeof(TestFixtureAttribute), false).Length > 0))
{
var tempFixture = new MintFixutre(fixture.Name, f);
// enumerating Test Methods
int t = 1;
foreach (var testMethod in fixture.GetMethods().Where(m => m.GetCustomAttributes(typeof(TestAttribute), false).Length > 0))
{
// filtering Actions
var instructions = testMethod.GetInstructions().Where(
i => i.OpCode.Name.Equals("newobj") && ((ConstructorInfo)i.Operand).DeclaringType.IsSubclassOf(typeof(BaseAction))).ToList();
var tempTest = new MintTest(testMethod.Name, t);
// enumerating Actions
for ( int a = 1; a <= instructions.Count; a++ )
{
Instruction action = instructions[a-1];
string actionName = (action.Operand as ConstructorInfo).DeclaringType.Name;
var tempAction = new MintAction(actionName, a);
tempTest.Actions.Add(tempAction);
}
tempFixture.Tests.Add(tempTest);
t++;
}
_fixutres.Add(tempFixture);
f++;
}
}
- DLL:このタイプのパブリックプロパティ
Fixtures
は、List<MintFixutre>
作成されたばかりのデータモデル(アクションのリストを含むテストのリストを含むフィクスチャのリスト)を返すために作成されます。これがのバインディングソースになりますTreeView
。
public List<MintFixutre> Fixtures
{
get { return _fixtures; }
}
- MainWindowのViewModel(TreeViewを内部に含む):ユニットテストDLLを解析できるDLLのオブジェクト/クラスが含まれています。タイプ
Fixtures
のDLLからパブリックプロパティも公開します。List<MintFixutre>
MainWindowのXAMLからバインドします。そのようなもの(簡略化):
var _exporter = MySuperDllReaderExporterClass ();
// public property of ViewModel for TreeView, which returns property from #4
public List<MintFixture> Fixtures { get { return _exporter.Fixtures; }}
// Initializing exporter class, ParseDllToEntityModel() is called inside getter
// (from step #3). Cool, we have entity model for binding.
_exporter.PathToDll = @"open file dialog can help";
// Notifying all those how are bound to the Fixtures property, there are work for them, TreeView, are u listening?
// will be faced later in this article, anticipating events
OnPropertyChanged("Fixtures");
MainWindowのXAML-セットアップデータテンプレート:TreeViewを含むグリッド内に、<Grid.Resources>
のテンプレートのセットを含むセクションを作成しますTreeViewItem
。HierarchicalDataTemplate
(フィクスチャとテスト)は子アイテムを持っている人にDataTemplate
使用され、「リーフ」アイテム(アクション)に使用されます。テンプレートごとに、そのコンテンツ(テキスト、TreeViewItem画像など)、ItemsSource(このアイテムに子がある場合、たとえばフィクスチャの場合)を指定します。{Binding Path=Tests}
)、およびItemTemplate(ここでも、このアイテムに子がある場合にのみ、テンプレート間のリンクを設定します-FixtureTemplateはその子にTestTemplateを使用し、TestTemplateはその子にActionTemplateを使用し、アクションテンプレートは何も使用せず、リーフです!)重要:「one」テンプレートを「another」に「リンク」するには、XAMLで「one」の上に「another」テンプレートを定義する必要があることを忘れないでください。(私自身の過ちを列挙するだけです:))
XAML-TreeViewリンケージ:TreeViewをセットアップします:ViewModelからのデータモデルとのリンク(パブリックプロパティを覚えていますか?)と、コンテンツ、外観、データソース、およびツリーアイテムのネストを表す準備されたテンプレートとのリンク!もう1つの重要な注意事項。ViewModelをXAML内の「静的」リソースとして定義しないでください<Window.Resources><MyViewModel x:Key="DontUseMeForThat" /></Window.Resources>
。これを行うと、プロパティが変更されたときに通知することができなくなります。なんで?静的リソースは静的リソースであり、初期化され、その後は不変のままです。私はここで間違っているかもしれませんが、それは私の失敗の1つでした。したがって、TreeViewの場合はItemsSource="{Binding Fixtures}"
代わりに使用しますItemsSource="{StaticResource myStaticViewModel}"
ViewModel-ViewModelBase-プロパティが変更されました:ほぼすべて。止まる!ユーザーがアプリケーションを開くと、ユーザーはまだDLLを開いていないため、最初はもちろんTreeViewは空です。ユーザーがDLLを開くまで待ってから、バインドを実行する必要があります。OnPropertyChanged
それはイベントを介して行われます。作業を楽にするために、すべてのViewModelはViewModelBaseから継承されます。これにより、この機能がすべてのViewModelに正しく公開されます。
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs args)
{
var handler = PropertyChanged;
if (handler != null)
handler(this, args);
}
}
XAML-OnPropertyChangedおよびコマンド。ユーザーがボタンをクリックして、単体テストデータを含むDLLを開きます。を使用しているMVVM
ので、クリックはコマンドを介して処理されます。OpenDllExecuted
ハンドラーの最後にOnPropertyChanged("Fixtures")
実行され、バインド先のプロパティが変更されたことをツリーに通知します。これで、ハンドラー自体を更新します。RelayCommand
ヘルパークラスは、たとえばそこから取得できます)。ところで、私が知っているように、いくつかのヘルパーライブラリとツールキットが存在します。XAMLでそのようなことが起こります。
そしてViewModel-コマンド
private ICommand _openDllCommand;
//...
public ICommand OpenDllCommand
{
get { return _openDllCommand ?? (_openDllCommand = new RelayCommand(OpenDllExecuted, OpenDllCanExecute)); }
}
//...
// decides, when the <OpenDll> button is enabled or not
private bool OpenDllCanExecute(object obj)
{
return true; // always true for Open DLL button
}
//...
// in fact, handler
private void OpenDllExecuted(object obj)
{
var openDlg = new OpenFileDialog { ... };
_pathToDll = openDlg.FileName;
_exporter.PathToDll = _pathToDll;
// Notifying TreeView via binding that the property <Fixtures> has been changed,
// thereby forcing the tree to refresh itself
OnPropertyChanged("Fixtures");
}
- 最終的なUI(ただし、私にとっては最終的なものではありません。多くのことを実行する必要があります!)。拡張WPFツールキットがどこかで使用されました:http ://wpftoolkit.codeplex.com/
