マむンドストリヌム。 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

Source: https://habr.com/ru/post/J241301/


All Articles