多くのテストは行われません





少し前に、私はゆっくりと自動化されたテストとTDDを練習に導入することにしました。 正直に言って、すべてが成功を収めてさまざまでした。 しかし、人生がもっと面白くなったという事実は、議論の余地のない事実です。 さまざまな冒険が私に起こり始めました。 そして、すべての冒険のように、時々少し怖くなった。 そのようなケースについてお話ししたいと思います。



私が参加したプロジェクトでは、1分から1年の時間間隔で多くの仕事をしました。 人生で日付を扱う単一のライブラリーを書いていない悪い(またはその逆も良い)プログラマー。 私は他の人よりも悪くも悪くもないので、頭を伸ばしてコードを作成することにしました。



それがすべて始まった方法



プロジェクトはDelphiで行われました。 OOPに固執することにしたTTimeIterator



TTimeIterator



という抽象クラスを作成しました。このクラスから、残りのクラスは、分、時間、日、月、年単位で移動するために生成されました。 これらのイテレータの考え方は、開始点と終了点を受け取ったら、それらを間隔の正しい境界に合わせて、適切なタイミングで明確に移動できるようにすることです。 プロジェクトで実装されたため、カットなしの抽象クラスの発表を行います。



 TTimeIterator=class(TObject) private StartPoint, FinishPoint:TDateTime; CurrentPoint:TDateTime; CurrentPointNumber:integer; function DTRound(p:TDateTime):TDateTime; virtual; abstract; function DTNext(p:TDateTime):TDateTime; virtual; abstract; function DTPrev(p:TDateTime):TDateTime; virtual; abstract; public constructor Create;overload; function Dump:string; function GetTotalPoints:integer; virtual; abstract; function GetCurrentPointNumber:integer; function GetCurrentPoint:TDateTime; procedure MoveNextPoint; function IsCurrentPoint:boolean; procedure SetStartPoint(DateTime:TDateTime; IncludeMode:TTimeIncludeModeType=INCLUDE_MODE); procedure SetFinishPoint(DateTime:TDateTime; IncludeMode:TTimeIncludeModeType=INCLUDE_MODE); end;
      
      





次に、関数の実装について説明します



  function GetTotalPoints:integer; virtual; abstract;
      
      





この関数は、開始点と終了点を確立した後、イテレーターが通過するポイントの総数を取得できます。 古典的なイテレータパラダイムの場合、必須ではありませんが、プロジェクト内の一部の操作では、このような関数は非常に便利です。



日付を扱った人なら誰でも知っているように、1分、1時間、1日、さらには1週間の間隔でもそれほど問題はありません。 問題は数か月で始まります。 タスクは、 GetTotalPoints



関数を月ごとの反復GetTotalPoints



に実装することGetTotalPoints







Strange MonthsBetween関数



さらに苦労せずに、標準ライブラリで、間違いのない名前と引数を持つ関数を見つけました。



 function MonthsBetween(const ANow, AThen: TDateTime): Integer;
      
      





非常に難しいテストはバタンと合格しました。 ある種の漠然とした感じが、テストを書くのをより難しくしました。 これらのテストの1つが失敗し、突然です。



少し調べたところ、 MonthsBetween



関数によると、 MonthsBetween



年1月1日01.05.2012 0:00



年5月1日01.05.2012 0:00



4か月(1月、2月、3月、4月)ではなく、3か月のみであることが01.05.2012 0:00



ました。 有名なことわざにあるように、他のすべてが失敗したら、ドキュメントを読んでください。 ヘルプを開き、恐怖で読みました:



MonthsBetweenを呼び出して、2つのTDateTime値の差を月単位で取得します。 月はすべて同じ長さではないため、MonthsBetweenは、1か月あたり30.4375日という仮定に基づいた近似値を返します。


行くぞ! この関数はおおよそ動作し、平均月の長さを30.4375日とします。



ボーランドを正当化するいくつかの言葉



最初のショックが終わった後、私は頭をひっくり返した。 実際、関数の実装は奇妙です。 しかし、これにはロジックがあります。 また、このロジックはヘルプに直接表示されます。 問題は、いくつかの時点を設定するときに、それらの間に何ヶ月が経過したかを明確に判断することが難しいことです。 そして結果は問題の見方によって異なります。 1か月間何を受け入れますか? 2月28日から3月31日までに何ヶ月が過ぎましたか? そして、2月28日から3月28日の間? 土は非常に滑りやすいです。



ボーランドがまったく正しくないと思う理由



まず、すべてのポイントの関数の値を正しく決定できない場合でも、正確に月の境界内に収まるポイントの場合、必要な結果は明らかです。 そして、これらのポイントで、関数は日常のロジックと矛盾しない値を返す必要があります。



純粋に実用的な性質の2番目の異論-ボーランドによって作成された形式の機能が役立つ単一のケースを思い付くことができませんでした。 あなたが知っているように、平均で動作するのが好きな統計には適していません。



なぜ怖がったの



私はボーランドという会社を怖がっていませんでした。 さらに、正しい実装には数行のコードが必要です。



ドキュメントを実際に読まずにコードに関数呼び出しを含めたとき、私は自分の軽薄さを恐れませんでした。



多くの場合、この関数は正しい値を返し、間違った値を返す場合は1つだけ異なるため、私は恐れていました。 このエラーの影響がシステム全体にどのように見えるかをおおよそ推定しました。 これは、現在のレポートではなく前の月のレポートでデータを受け取ることがあることを意味します(また、異なる月のレポートは既に双子として互いに類似しています)。



このエラーが単体テストのブロックを巧みに通過することを想像しました。 その後、統合テストを克服します。 手動テストと受け入れテストをそれほど困難なく克服します。 また、システムは実世界で何十年も動作しており、時々偽のデータを生成しています。



信頼性の理論の観点からは、原因不明の浮遊する、診断が困難な欠陥が発生します。 安全性と重要な機能を保証するシステムには、このような欠陥がいくつありますか? 知りません この欠陥を発見したのはちょうどラッキーでした。



次の考えが少し私を慰めました。



まず、テストしなければ、この種のエラーは絶対に見つかりません。

第二に、私たちの政治家の一人の言葉を思い出しました。 彼は尋ねられた:「ロシアが石油価格で幸運だったというのは本当ですか?」 彼は答えた:「愚か者は幸運であり、私達は朝から夜まで働く。」



それがすべて終わった方法



私は自分の要件の観点から正しい関数を書きました。



 function DeviceTimeExactMonthsBetween(StartDate, EndDate: TDateTime):integer; const BASE_YEAR=1990; var y1,y2,m1,m2,d1,d2 : word; StartMonths, EndMonths:integer; begin DecodeDate(StartDate,y1,m1,d1); DecodeDate(EndDate,y2,m2,d2); StartMonths:=(y1-BASE_YEAR)*12+m1; EndMonths:=(y2-BASE_YEAR)*12+m2; Result:=EndMonths-StartMonths; if d2<d1 then dec(Result); if Result<0 then Result:=0; end;
      
      





また、単体テストでは、他のテストとともに次のコードを追加しました。



 CheckNotEquals( // Headfire function DeviceTimeExactMonthsBetween( StrToDateTime('01.01.2012'), StrToDateTime('01.05.2012')), // BorlandFunction MonthsBetween( StrToDateTime('01.01.2012'), StrToDateTime('01.05.2012')), //Test name 'Month BorladFailureTest' );
      
      





Borland社はもはや存在しませんが、知っている人は、突然MonthsBetween関数がいつかより正確に機能するようになります。



私はどれだけ幸運ですか?



すでに、このケースを記事として作成することにしたとき、エラーを見つけることがどれほど幸運だったのかと思いました。 10分で、小さな視覚化をスケッチしました。 以下のテキストを引用します(ちなみに、実際に日付反復子がどのように使用されるかを示しています)。



 procedure TMainForm.DrawFaults(BeginDate,EndDate:TDateTime); var IteratorForBegin,IteratorForEnd:TTimeIterator; x,y,diff:Integer; color:TColor; begin IteratorForBegin:=CreateDeviceTimeIterator(MONTH_INTERVAL); IteratorForEnd:=CreateDeviceTimeIterator(MONTH_INTERVAL); IteratorForBegin.SetStartPoint(BeginDate , INCLUDE_MODE); IteratorForBegin.SetFinishPoint(EndDate, INCLUDE_MODE); while (IteratorForBegin.IsCurrentPoint) do begin IteratorForEnd.SetStartPoint(IteratorForBegin.GetCurrentPoint, INCLUDE_MODE); IteratorForEnd.SetFinishPoint(EndDate, INCLUDE_MODE); while (IteratorForEnd.IsCurrentPoint) do begin diff:=DeviceTimeExactMonthsBetween(IteratorForBegin.GetCurrentPoint,IteratorForEnd.GetCurrentPoint) - MonthsBetween(IteratorForBegin.GetCurrentPoint,IteratorForEnd.GetCurrentPoint); color:=clBlack; //  ,    -  -   if diff=0 then color:=clGreen; if diff=1 then color:=clRed; y:=IteratorForBegin.GetCurrentPointNumber; x:=y+IteratorForEnd.GetCurrentPointNumber; Canvas.Brush.Color:=color; Canvas.Ellipse(10+x*12,10+y*12,20+x*12,20+y*12); //   // Canvas.Pixels[10+x,10+y]:=color; IteratorForEnd.MoveNextPoint; end; IteratorForBegin.MoveNextPoint; end; end;
      
      





これは、2012年(プロジェクトを作成したとき)におけるMonthsBetween



関数の操作を反映した写真です。 緑色のものは、 MonthsBetween



が正しい結果を与える場合、赤色の場合は1つ少ない場合に対応します。





特に下半期にプロジェクトを書いて以来、状況はあまり良くありません。 (テスト時には、現在の日付にできるだけ近い日付を使用しようとします)。 テストに合格する可能性があります。



以下は、20年の範囲(2010-2020)での関数の視覚化です。





エラーの数が増加し、約50〜50になることがわかります。したがって、テストに長い間隔が含まれると、エラーを検出する確率が高くなります。 実際に何が起こったのか。 私があまりにも怠zyでなく、より長い間隔をとったとき、エラーが明らかになりました。



おわりに



はい、もちろんです。 理由は、どのプロジェクトでもテストの最適な数とテスト範囲のサイズがあることを理解しています。 プログラムの目的、使用される技術、業界、および信頼性の要件によって異なります。

しかし、最初に打ち上げロケットを見ると、完成したロケットは宇宙に行きます。 核兵器を搭載した潜水艦のように、それはハッチをand化させ、半年ごとのキャンペーンに向けて没頭の準備をします。 近隣の通りに新しいショッピングセンターがどのように建設されているかを見ると、その強度計算は現代のクラスターで実行されました。 私はこれを見るとき、できるだけ多くの、良い、異なるテストがあるべきだと心から願っています。










All Articles