24

DUnit を使用して Delphi ライブラリをテストしています。関数への複数の入力をチェックするために、いくつかの非常によく似たテストを作成する場合があります。

DUnit でパラメータ化されたテストを (似たようなもの) 書く方法はありますか? たとえば、入力と期待される出力を適切なテスト プロシージャに指定し、テスト スイートを実行して、テストの複数回の実行のうちどれが失敗したかについてのフィードバックを取得しますか?

(編集:例)

たとえば、次のような 2 つのテストがあるとします。

procedure TestMyCode_WithInput2_Returns4();
var
  Sut: TMyClass;
  Result: Integer;
begin
  // Arrange:
  Sut := TMyClass.Create;

  // Act:
  Result := sut.DoStuff(2);

  // Assert
  CheckEquals(4, Result);
end;

procedure TestMyCode_WithInput3_Returns9();
var
  Sut: TMyClass;
  Result: Integer;
begin
  // Arrange:
  Sut := TMyClass.Create;

  // Act:
  Result := sut.DoStuff(3);

  // Assert
  CheckEquals(9, Result);
end;

まったく同じことを行うが、入力と期待が異なるこれらのテストがさらに増える可能性があります。それらを個別に合格または不合格にしたいので、それらを 1 つのテストにマージしたくありません。

4

4 に答える 4

22

DSharp を使用して、DUnit テストを改善できます。特に新しいユニットDSharp.Testing.DUnit.pas (Delphi 2010 以降)。

TestFramework の後に使用するように追加するだけで、テスト ケースに属性を追加できます。次に、次のようになります。

unit MyClassTests;

interface

uses
  MyClass,
  TestFramework,
  DSharp.Testing.DUnit;

type
  TMyClassTest = class(TTestCase)
  private
    FSut: TMyClass;
  protected
    procedure SetUp; override;
    procedure TearDown; override;
  published
    [TestCase('2;4')]
    [TestCase('3;9')]
    procedure TestDoStuff(Input, Output: Integer);
  end;

implementation

procedure TMyClassTest.SetUp;
begin
  inherited;
  FSut := TMyClass.Create;
end;

procedure TMyClassTest.TearDown;
begin
  inherited;
  FSut.Free;
end;

procedure TMyClassTest.TestDoStuff(Input, Output: Integer);
begin
  CheckEquals(Output, FSut.DoStuff(Input));
end;

initialization
  RegisterTest(TMyClassTest.Suite);

end.

実行すると、テストは次のようになります。

ここに画像の説明を入力

Delphi の属性は定数を受け入れるだけなので、値がセミコロンで区切られた文字列として引数を取ります。しかし、「魔法の」文字列を防ぐために、正しい型の複数の引数を取る独自の属性クラスを作成することを妨げるものは何もありません。とにかく、const にできる型に制限されています。

メソッドの各引数に Values 属性を指定することもでき、可能な組み合わせで呼び出されます ( NUnit のように)。

他の回答を個人的に参照すると、単体テストを作成するときにできるだけ少ないコードを作成したいと考えています。また、実装部分を掘り下げずにインターフェース部分を見たときにテストが何をするかを見たいと思います(「 BDDをやろう」とは言いません)。それが私が宣言的な方法を好む理由です。

于 2012-01-25T16:56:49.340 に答える
13

私はあなたがこのようなものを探していると思います:

unit TestCases;

interface

uses
  SysUtils, TestFramework, TestExtensions;

implementation

type
  TArithmeticTest = class(TTestCase)
  private
    FOp1, FOp2, FSum: Integer;
    constructor Create(const MethodName: string; Op1, Op2, Sum: Integer);
  public
    class function CreateTest(Op1, Op2, Sum: Integer): ITestSuite;
  published
    procedure TestAddition;
    procedure TestSubtraction;
  end;

{ TArithmeticTest }

class function TArithmeticTest.CreateTest(Op1, Op2, Sum: Integer): ITestSuite;
var
  i: Integer;
  Test: TArithmeticTest;
  MethodEnumerator: TMethodEnumerator;
  MethodName: string;
begin
  Result := TTestSuite.Create(Format('%d + %d = %d', [Op1, Op2, Sum]));
  MethodEnumerator := TMethodEnumerator.Create(Self);
  Try
    for i := 0 to MethodEnumerator.MethodCount-1 do begin
      MethodName := MethodEnumerator.NameOfMethod[i];
      Test := TArithmeticTest.Create(MethodName, Op1, Op2, Sum);
      Result.addTest(Test as ITest);
    end;
  Finally
    MethodEnumerator.Free;
  End;
end;

constructor TArithmeticTest.Create(const MethodName: string; Op1, Op2, Sum: Integer);
begin
  inherited Create(MethodName);
  FOp1 := Op1;
  FOp2 := Op2;
  FSum := Sum;
end;

procedure TArithmeticTest.TestAddition;
begin
  CheckEquals(FOp1+FOp2, FSum);
  CheckEquals(FOp2+FOp1, FSum);
end;

procedure TArithmeticTest.TestSubtraction;
begin
  CheckEquals(FSum-FOp1, FOp2);
  CheckEquals(FSum-FOp2, FOp1);
end;

function UnitTests: ITestSuite;
begin
  Result := TTestSuite.Create('Addition/subtraction tests');
  Result.AddTest(TArithmeticTest.CreateTest(1, 2, 3));
  Result.AddTest(TArithmeticTest.CreateTest(6, 9, 15));
  Result.AddTest(TArithmeticTest.CreateTest(-3, 12, 9));
  Result.AddTest(TArithmeticTest.CreateTest(4, -9, -5));
end;

initialization
  RegisterTest('My Test cases', UnitTests);

end.

GUI テスト ランナーでは次のようになります。

ここに画像の説明を入力

私がこれについて次善の方法で行ったかどうかを知りたいと思います。DUnit は信じられないほど一般的で柔軟性があるため、使用するたびに、問題を解決するためのより優れた簡単な方法を見逃しているといつも感じてしまいます。

于 2012-01-25T13:33:29.477 に答える
3

AddTestForDoStuff を呼び出すたびに、あなたの例と同様のテスト ケースが作成されるようなコードを DUnit で記述できれば十分でしょうか?

Suite.AddTestForDoStuff.With(2).Expect(4);
Suite.AddTestForDoStuff.With(3).Expect(9);

今日の後半にこれを行う方法の例を投稿しようと思います...


.Net の場合、すでに似たようなものがあります: Fluent Assertions

http://www.codeproject.com/Articles/784791/Introduction-to-Unit-Testing-with-MS-tests-NUnit-a

于 2012-01-25T12:06:24.620 に答える
0

以下は、TTestCase の子孫である実際の (公開された) テスト メソッド (:

procedure TTester.CreatedWithoutDisplayFactorAndDisplayString;
begin
  MySource := TMyClass.Create(cfSum);

  SendAndReceive;
  CheckDestinationAgainstSource;
end;

procedure TTester.CreatedWithDisplayFactorWithoutDisplayString;
begin
  MySource := TMyClass.Create(cfSubtract, 10);

  SendAndReceive;
  CheckDestinationAgainstSource;
end;

はい、いくつかの重複がありますが、コードの主な重複は、これらのメソッドから先祖クラスの SendAndReceive および CheckDestinationAgainstSource メソッドに取り出されました。

procedure TCustomTester.SendAndReceive;
begin
  MySourceBroker.CalculationObject := MySource;
  MySourceBroker.SendToProtocol(MyProtocol);
  Check(MyStream.Size > 0, 'Stream does not contain xml data');
  MyStream.Position := 0;
  MyDestinationBroker.CalculationObject := MyDestination;
  MyDestinationBroker.ReceiveFromProtocol(MyProtocol);
end;

procedure TCustomTester.CheckDestinationAgainstSource(const aCodedFunction: string = '');
var
  ok: Boolean;
  msg: string;
begin
  if aCodedFunction = '' then
    msg := 'Calculation does not match: '
  else
    msg := 'Calculation does not match. Testing CodedFunction ' + aCodedFunction + ': ';

  ok := MyDestination.IsEqual(MySource, MyErrors);
  Check(Ok, msg + MyErrors.Text);
end;

CheckDestinationAgainstSource のパラメーターは、次のタイプの使用も許可します。

procedure TAllTester.AllFunctions;
var
  CF: TCodedFunction;
begin
  for CF := Low(TCodedFunction) to High(TCodedFunction) do
  begin
    TearDown;
    SetUp;
    MySource := TMyClass.Create(CF);
    SendAndReceive;
    CheckDestinationAgainstSource(ConfiguredFunctionToString(CF));
  end;
end;

この最後のテストは、TRepeatedTest クラスを使用してコーディングすることもできますが、このクラスを使用するのはかなり直感的ではありません。上記のコードにより、チェックのコーディングと分かりやすい失敗メッセージの生成において、より大きな柔軟性が得られます。ただし、最初の失敗でテストを停止するという欠点があります。

于 2012-01-25T13:27:36.003 に答える