マインドストリーム。 FireMonkeyでソフトウェアを作成するとき。 パート3. DUnit + FireMonkey

パート1

パート2



こんにちは。



この記事では、VCLコードをFireMonkeyに移植するプロセスを読者に紹介します。 バージョン2009からの私の意見から始まる標準のDelphiパッケージでは、 DUnitプロジェクトがすぐに使用できます。



ただし、VCLによって古代に書かれました。 また、FireMonkey用に記述されたコードをテストすることもできます(コンソール出力のおかげ) 「今すぐ」起動します。



画像












DUnitに精通している、またはほとんど知らない人向け。 通常のモードでは、ドキュメントでは、File-> New-> Other-> Unit Test-> TestProjectの実行が推奨されています。 次に、GUIまたはコンソールオプションを選択する必要があります。 これらのそれほど複雑ではない操作のおかげで、GUI用に次のような(少なくとも「私の」XE7、まさにこのコードを生成した)新しいプロジェクトができます。



program Project1Tests; { Delphi DUnit Test Project ------------------------- This project contains the DUnit test framework and the GUI/Console test runners. Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options to use the console test runner. Otherwise the GUI test runner will be used by default. } {$IFDEF CONSOLE_TESTRUNNER} {$APPTYPE CONSOLE} {$ENDIF} uses DUnitTestRunner, TestUnit1 in 'TestUnit1.pas', Unit1 in '..\DUnit.VCL\Unit1.pas'; {$R *.RES} begin DUnitTestRunner.RunRegisteredTests; end.
      
      





次に、TestCaseを追加します。これも行われます(ファイル->新規->その他->単体テスト-> TestCase)。結果は次のようになります。



 unit TestUnit1; { Delphi DUnit Test Case ---------------------- This unit contains a skeleton test case class generated by the Test Case Wizard. Modify the generated code to correctly setup and call the methods from the unit being tested. } interface uses TestFramework, System.SysUtils, Vcl.Graphics, Winapi.Windows, System.Variants, System.Classes, Vcl.Dialogs, Vcl.Controls, Vcl.Forms, Winapi.Messages, Unit1; type // Test methods for class TForm1 TestTForm1 = class(TTestCase) strict private FForm1: TForm1; public procedure SetUp; override; procedure TearDown; override; published procedure TestDoIt; end; implementation procedure TestTForm1.SetUp; begin FForm1 := TForm1.Create; end; procedure TestTForm1.TearDown; begin FForm1.Free; FForm1 := nil; end; procedure TestTForm1.TestDoIt; var ReturnValue: Integer; begin ReturnValue := FForm1.DoIt; // TODO: Validate method results end; initialization // Register any test cases with the test runner RegisterTest(TestTForm1.Suite); end.
      
      





一般に、私の例は、Delphi7であってもテストを追加することがどれほど簡単かを示しています。 必要なのは、DUnitTestRunner.RunRegisteredTestsを呼び出すことだけです。 そして、TestCaseで新しいファイルをプロジェクトに追加します。 詳細については、DUnitを使用したテストの問題について説明します



実装のために、DUnitを実行した人を繰り返す必要があると判断しました。

私が遭遇した最初の問題(TTreeNodeとTTreeViewItemは「まったく兄弟ではない」という事実さえ、私は言いません、ドキュメントは全員を救います):



 type TfmGUITestRunner = class(TForm) ... protected FSuite: ITest; procedure SetSuite(Value: ITest); ... public property Suite: ITest read FSuite write SetSuite; end; procedure RunTestModeless(aTest: ITest); var l_GUI: TfmGUITestRunner; begin Application.CreateForm(TfmGUITestRunner, l_GUI); l_GUI.Suite := aTest; l_GUI.Show; end; procedure TfmGUITestRunner.SetSuite(Value: ITest); begin FSuite := Value; // AV  if FSuite <> nil then InitTree; end;
      
      





問題は、いつものように、デバッグ、またはドキュメントで「認識」されています:)。 FireMonkeyでは、Application.CreateForm();はフォームを作成しません。 はい、奇妙なことに。 TApplication.CreateForm



私が見つけたときのコミットに関する私のコメント:)
FSuite、まだ作成されていません。Application.CreateFormは、実際に明示的にキックしない場合、「雌犬、通常のフォームを作成せず、将来のクラスへのリンクのみを作成します。 したがって、これは、まったくないクラスメンバーに影響します。



AVはSystem._IntfCopy(var Dest:IInterface; const Source:IInterface);にクロールします。

そして、Destにごみがあり、インターフェイスやnilではないために出てきます。 そして、これは、前のインターフェイス(// nilでない場合)で1を減算するときに表示されます。



こんな行を書いてもたわごとです

FSuite:= nil;





このテーマに関する別のリンクを次に示します-。 それは言うことをしません! 正直に言うと、Make Formというメソッドがそれをしないという事実にも少しショックを受けました。

フォームを明示的に作成して問題を解決し(l_GUI:= TfmGUITestRunner.create(nil);)、次に進みます。



次に、テスト用に追加されたTestCaseに基づいてテストツリーを構築する必要があります。 お気づきの方は、フォームの作成プロセスはRunRegisteredTestsModelessメソッドから始まります。



 procedure RunRegisteredTestsModeless; begin RunTestModeless(registeredTests) end;
      
      





このメソッドをDUnitの作成者などの別のモジュールに配置しないことにしました。そのため、fmGUITestRunnerを接続するには、プロジェクトコードでモジュールを指定し、実際に目的のメソッドを呼び出す必要があります。 私の場合、プロジェクトコードは次のようになります。



 program FMX.DUnit; uses FMX.Forms, //   u_fmGUITestRunner in 'u_fmGUITestRunner.pas' {fmGUITestRunner}, //  u_FirstTest in 'u_FirstTest.pas', u_TCounter in 'u_TCounter.pas', u_SecondTest in 'u_SecondTest.pas'; {$R *.res} begin Application.Initialize; //      u_fmGUITestRunner.RunRegisteredTestsModeless; Application.Run; end.
      
      





気配りのある読者は、registeredTestsを追加せず、追加するテストをまったく示していないことに注意してください。 RegisteredTestsはフォームに接続された「グローバルな」TestFrameWorkメソッドであり、グローバル変数__TestRegistry:ITestSuite;を返します。



TestCaseがこの変数にどのように「陥る」かについては、特にDUnitの作成者が作業を行ったため、この記事の範囲から除外します。 ただし、読者がこのトピックに関心を示している場合は、コメントで回答します。 だから、ツリーに戻ります。 ツリーを初期化する方法:



 procedure TfmGUITestRunner.InitTree; begin FTests.Clear; FillTestTree(Suite); TestTree.ExpandAll; end;
      
      





FTests、これはテストのリストを保存するインターフェイスオブジェクトのリストです。 次に、FillTestTreeメソッドがオーバーロードされます。これは、ツリーのルート要素と通常のノードのどちらで作業しているかわからないため、行われます。



 ... procedure FillTestTree(aTest: ITest); overload; procedure FillTestTree(aRootNode: TTreeViewItem; aTest: ITest); overload; ... procedure TfmGUITestRunner.FillTestTree(aRootNode: TTreeViewItem; aTest: ITest); var l_TestTests: IInterfaceList; l_Index: Integer; l_TreeViewItem: TTreeViewItem; begin if aTest = nil then Exit; l_TreeViewItem := TTreeViewItem.Create(self); l_TreeViewItem.IsChecked := True; //    ,    Tag   .        :) l_TreeViewItem.Tag := FTests.Add(aTest); l_TreeViewItem.Text := aTest.Name; //   ,   if aRootNode = nil then TestTree.AddObject(l_TreeViewItem) else aRootNode.AddObject(l_TreeViewItem); // ITest,   Tests,   (IInterfaceList) ""  //      l_TestTests := aTest.Tests; for l_Index := 0 to l_TestTests.Count - 1 do FillTestTree(l_TreeViewItem, l_TestTests[l_Index] as ITest); end;
      
      





ご覧のとおり、このメソッドでは、ツリーを埋めるだけでなく、各ノードに対応するテストの情報も提供しました。 ノードごとにテストを取得するには、NodeToTestメソッドを記述します。



 function TfmGUITestRunner.NodeToTest(aNode: TTreeViewItem): ITest; var l_Index: Integer; begin assert(aNode.Tag >= 0); l_Index := aNode.Tag; Result := FTests[l_Index] as ITest; end;
      
      





次に、テストに「知識」を追加します。 各テストには、TObjectなどのGUIObject変数があります。 FormShowでSetupGUINodesを呼び出します。



 procedure TfmGUITestRunner.SetupGUINodes(aNode: TTreeViewItem); var l_Test: ITest; l_Index: Integer; begin for l_Index := 0 to Pred(aNode.Count) do begin //   l_Test := NodeToTest(aNode.Items[l_Index]); assert(assigned(l_Test)); //      l_Test.GUIObject := aNode.Items[l_Index]; SetupGUINodes(aNode.Items[l_Index]); end; end;
      
      





テストからノードを取得するために、メソッドを記述します:



 function TfmGUITestRunner.TestToNode(test: ITest): TTreeViewItem; begin assert(assigned(test)); Result := test.GUIObject as TTreeViewItem; assert(assigned(Result)); end;
      
      





私がテストをツリーに「接続」する方法は、私とシニア同僚が好まなかった。 なぜDUnit開発者がこのように進んだのかと思います。 DUnitは長い間書かれていましたが、ジェネリックはありませんでした。 将来的には、これを確実にやり直します。 記事の最後に、次の改善点とウィッシュリストについて書きます。



だから-私たちのツリーは建設中、テストはFTestsにあります。 テストとツリーは相互接続されています。 テストを実行し、結果を処理します。 フォームでこれを行うには、TestFrameWorkで説明されているITestListenerインターフェイスの実装を追加します。



  { ITestListeners get notified of testing events. See TTestResult.AddListener() } ITestListener = interface(IStatusListener) ['{114185BC-B36B-4C68-BDAB-273DBD450F72}'] procedure TestingStarts; procedure StartTest(test: ITest); procedure AddSuccess(test: ITest); procedure AddError(error: TTestFailure); procedure AddFailure(Failure: TTestFailure); procedure EndTest(test: ITest); procedure TestingEnds(testResult :TTestResult); function ShouldRunTest(test :ITest):Boolean; end;
      
      





これらのメソッドをクラスの説明に追加して実装します。



 procedure TfmGUITestRunner.TestingStarts; begin FTotalTime := 0; end; procedure TfmGUITestRunner.StartTest(aTest: ITest); var l_Node: TTreeViewItem; begin assert(assigned(TestResult)); assert(assigned(aTest)); l_Node := TestToNode(aTest); assert(assigned(l_Node)); end; procedure TfmGUITestRunner.AddSuccess(aTest: ITest); begin assert(assigned(aTest)); SetTreeNodeFont(TestToNode(aTest), c_ColorOk) end; procedure TfmGUITestRunner.AddError(aFailure: TTestFailure); var l_ListViewItem: TListViewItem; begin SetTreeNodeFont(TestToNode(aFailure.failedTest), c_ColorError); l_ListViewItem := AddFailureNode(aFailure); end; procedure TfmGUITestRunner.AddFailure(aFailure: TTestFailure); var l_ListViewItem: TListViewItem; begin SetTreeNodeFont(TestToNode(aFailure.failedTest), c_ColorFailure); l_ListViewItem := AddFailureNode(aFailure); end; procedure TfmGUITestRunner.EndTest(test: ITest); begin // ,          // .     . //    ,     ,    //  ,  TODO // assert(False); end; procedure TfmGUITestRunner.TestingEnds(aTestResult: TTestResult); begin FTotalTime := aTestResult.TotalTime; end; function TfmGUITestRunner.ShouldRunTest(aTest: ITest): Boolean; var l_Test: ITest; begin //  ,    .    ""  ""   l_Test := aTest; Result := l_Test.Enabled end;
      
      





説明する特別なものはありません。 質問があれば、詳細に答えます。 元のDUnitRunnerでは、テスト結果を「受け取る」ときに、対応するツリーノードの画像を変更しました。 すぐに写真を使用できるようになったため、写真をだましてはいけないことにしました。ノードに写真を追加することは、どうやらスタイルを通して台無しになります。 したがって、私は各ノードのFontColorとFontStyleの変更のみに制限することにしました。



1分間のビジネスのように思えますが、すべてのドキュメントを数時間掘り下げました。



 procedure TfmGUITestRunner.SetTreeNodeFont(aNode: TTreeViewItem; aColor: TAlphaColor); begin //          ,     aNode.StyledSettings := aNode.StyledSettings - [TStyledSetting.ssFontColor, TStyledSetting.ssStyle]; aNode.Font.Style := [TFontStyle.fsBold]; aNode.FontColor := aColor; end;
      
      





ListViewを使用して結果を表示します。 FireMonkeyのTListViewの機能は、リストがモバイルアプリケーション用に完全に調整されるようなものです。 そして素晴らしいColumnsプロパティを失いました。 エラーを追加するには、AddFailureNodeメソッドを追加します。



 function TfmGUITestRunner.AddFailureNode(aFailure: TTestFailure): TListViewItem; var l_Item: TListViewItem; l_Node: TTreeViewItem; begin assert(assigned(aFailure)); l_Item := lvFailureListView.Items.Add; l_Item.Text := aFailure.failedTest.Name + '; ' + aFailure.thrownExceptionName + '; ' + aFailure.thrownExceptionMessage + '; ' + aFailure.LocationInfo + '; ' + aFailure.AddressInfo + '; ' + aFailure.StackTrace; l_Node := TestToNode(aFailure.failedTest); while l_Node <> nil do begin l_Node.Expand; l_Node := l_Node.ParentItem; end; Result := l_Item; end;
      
      





ボタンと起動メソッドを追加するテストを実行します。



 procedure TfmGUITestRunner.btRunAllTestClick(Sender: TObject); begin if Suite = nil then Exit; ClearResult; RunTheTest(Suite); end; procedure TfmGUITestRunner.RunTheTest(aTest: ITest); begin TestResult := TTestResult.Create; try TestResult.addListener(self); aTest.run(TestResult); finally FreeAndNil(FTestResult); end; end;
      
      





ランナーを起動し、テスト実行ボタンをクリックすると、結果が表示されます:



画像








最後に、ノードの状態が変化したときにユーザーのアクションを処理します。



 procedure TfmGUITestRunner.TestTreeChangeCheck(Sender: TObject); begin SetNodeEnabled(Sender as TTreeViewItem, (Sender as TTreeViewItem).IsChecked); end; procedure TfmGUITestRunner.SetNodeEnabled(aNode: TTreeViewItem; aValue: Boolean); var l_Test: ITest; begin l_Test := NodeToTest(aNode); if l_Test <> nil then l_Test.Enabled := aValue; end;
      
      





一部のノードのチェックボックスの状態を変更します。



画像








実際にテストしたテストコード:



 unit u_SecondTest; interface uses TestFrameWork; type TSecondTest = class(TTestCase) published procedure DoIt; procedure OtherDoIt; procedure ErrorTest; procedure SecondErrorTest; end; // TFirstTest implementation procedure TSecondTest.DoIt; begin Check(true); end; procedure TSecondTest.ErrorTest; begin raise ExceptionClass.Create('Error Message'); end; procedure TSecondTest.OtherDoIt; begin Check(true); end; procedure TSecondTest.SecondErrorTest; begin Check(False); end; initialization TestFrameWork.RegisterTest(TSecondTest.Suite); end.
      
      





要約すると、この段階で、使い慣れたGUIRunnerを使用してFireMonkeyコードをテストするための非常に機能するアプリケーションができました。 プロジェクトは開いているため、誰でも使用できます。



今後の計画:

lambdaを受け取るツリートラバーサルメソッドを記述します。 ツリーは常にバイパスする必要がありますが、各ブランチでのアクションは異なるため、ラムダは私にとって適切なようです。



先輩からのコメントと提案:

TDictionary <TTreeViewItem、ITest> docwiki.embarcadero.com/Libraries/XE7/en/System.Generics.Collections.TDictionaryでテストノードの関連付けをやり直します。

「テストに合格」のグラフィカルインジケータを追加します。 ボタン-すべて選択、すべて削除など テスト結果の結論(リードタイム、成功および失敗の数など)。

「松葉杖」GUIObjectを取り除くために、 デコレータパターンを追加します。



近い将来、メインプロジェクトであるMindStreamのテストを開始し、Runnerを少し思い起こさせます。 最後まで読んでくれたみんなに感謝します。 コメントと批評、いつもコメントで歓迎します。



リポジトリへのリンク。



psプロジェクトはMindStream \ FMX.DUnitリポジトリにあります



私が見つけた、そしてその過程で私にとって有用だったリンク:

sourceforge.net/p/radstudiodemos/code/HEAD/tree/branches/RadStudio_XE5_Update/FireMonkey/Delphi

fire-monkey.ru

18delphi.blogspot.ru

www.gunsmoker.ru

「ロシア語」でのGUIテスト。 テストレベルノート

もう一度「テストレベル」について

そしてもちろん

docwiki.embarcadero.com/RADStudio/XE7/en/Main_Page



パート3.1



All Articles