Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
FreeOnTerminate=True的TThread的Delphi单元测试_Delphi_Unit Testing_Delphi 2010_Dunit_Tthread - Fatal编程技术网

FreeOnTerminate=True的TThread的Delphi单元测试

FreeOnTerminate=True的TThread的Delphi单元测试,delphi,unit-testing,delphi-2010,dunit,tthread,Delphi,Unit Testing,Delphi 2010,Dunit,Tthread,当freeontermine=True时,为TThread子体编写Delphi DUnit测试的最佳方法是什么?TThread子体返回一个我需要测试的引用,但我不知道如何等待线程在测试中完成 unit uThreadTests; interface uses Classes, TestFramework; type TMyThread = class(TThread) strict private FId: Integer; protected proce

当freeontermine=True时,为TThread子体编写Delphi DUnit测试的最佳方法是什么?TThread子体返回一个我需要测试的引用,但我不知道如何等待线程在测试中完成

unit uThreadTests;

interface

uses
  Classes, TestFramework;

type

  TMyThread = class(TThread)
  strict private
    FId: Integer;
  protected
    procedure Execute; override;
  public
    constructor Create(AId: Integer);
    property Id: Integer read FId;
  end;

  TestTMyThread = class(TTestCase)
  strict private
    FMyId: Integer;
    procedure OnThreadTerminate(Sender: TObject);
  protected
    procedure SetUp; override;
    procedure TearDown; override;
  published
    procedure TestMyThread;
  end;

implementation

{ TMyThread }

constructor TMyThread.Create(AId: Integer);
begin
  FreeOnTerminate := True;
  FId := AId;

  inherited Create(False);
end;

procedure TMyThread.Execute;
begin
  inherited;

  FId := FId + 1;
end;

{ TestTMyThread }

procedure TestTMyThread.TestMyThread;
//var
//  LThread: TMyThread;
begin
//  LThread := TMyThread.Create(1);
//  LThread.OnTerminate := OnThreadTerminate;
//  LThread.WaitFor;
//  CheckEquals(2, FMyId);
//  LThread.Free;
///// The above commented out code is only useful of FreeOnTerminate = False;

  with TMyThread.Create(1) do
  begin
    OnTerminate := OnThreadTerminate;
    WaitFor; /// Not sure how else to wait for the thread to finish?
  end;

  CheckEquals(2, FMyId);
end;

procedure TestTMyThread.OnThreadTerminate(Sender: TObject);
begin
  FMyId := (Sender as TMyThread).Id;
end;  /// When FreeOnTerminate = True - THIS LINE CAUSES ERROR: Thread Error the handle is invalid

procedure TestTMyThread.SetUp;
begin
  inherited;

end;

procedure TestTMyThread.TearDown;
begin
  inherited;

end;

initialization
  RegisterTests([TestTMyThread.Suite]);


end.
任何想法都会受到欢迎


Delphi 2010。

在挂起状态下创建线程,然后设置
OnTerminate
,最后
恢复线程

在测试类中,定义一个私有布尔字段
FThreadDone
,该字段用
false
初始化,并由
OnTerminate
Eventhandler设置为
true

另外,构造函数逻辑有点脏,因为在调用继承的构造函数之前不应该初始化字段

因此:

这应该可以完成任务


编辑:更正了愚蠢的更正,已测试,有效。

因为您在终止时释放了线程本身,所以您要求它在完成后立即销毁自身的所有痕迹。由于您无法影响线程完成的时间,因此在启动线程后引用线程内部的任何内容都是错误的

其他人提出的解决方案,即要求线程在终止时向您发出信号,都是好的。我个人可能会选择这样做。如果使用事件作为信号,则可以等待该事件

然而,还有另一种方法

  • 创建挂起的线程
  • 复制线程句柄
  • 启动线程
  • 等待复制的句柄
  • 因为您拥有复制的句柄,而不是线程,所以您可以安全地等待它。它看起来有点复杂,但我想它避免了在不需要的地方创建额外的同步对象。请注意,与使用事件来表示完成的方法相比,我并不提倡这种方法

    无论如何,这里有一个简单的想法演示

    {$APPTYPE CONSOLE}
    
    uses
      SysUtils, Windows, Classes;
    
    type
      TMyThread = class(TThread)
      protected
        procedure Execute; override;
      public
        destructor Destroy; override;
      end;
    
    destructor TMyThread.Destroy;
    begin
      Writeln('I''m dead!');
      inherited;
    end;
    
    procedure TMyThread.Execute;
    begin
    end;
    
    var
      DuplicatedHandle: THandle;
    
    begin
      with TMyThread.Create(True) do // must create suspended
      begin
        FreeOnTerminate := True;
        Win32Check(DuplicateHandle(
          GetCurrentProcess,
          Handle,
          GetCurrentProcess,
          @DuplicatedHandle,
          0,
          False,
          DUPLICATE_SAME_ACCESS
        ));
        Start;
      end;
    
      Sleep(500);
      Writeln('I''m waiting');
      if WaitForSingleObject(DuplicatedHandle, INFINITE)=WAIT_OBJECT_0 then
        Writeln('Wait succeeded');
      CloseHandle(DuplicatedHandle);
      Readln;
    end.
    

    子类化线程以使其更易于测试
    TThread
    TObject
    提供了足够的钩子,您可以添加感测变量来观察它是否达到了您想要的状态

    我看到了您可能希望测试的这个特定类的三个方面:

  • 它根据发送给构造函数的值计算其
    Id
    属性的值
  • 它在新线程中计算新的
    Id
    属性,而不是调用构造函数的线程
  • 它完成后会自动释放
  • 所有这些东西都可以从子类进行测试,但如果不更改线程接口,则很难进行其他测试。(到目前为止,所有其他答案都需要更改线程的接口,例如添加更多构造函数参数或更改线程本身的启动方式。这可能会使线程更难在实际程序中使用,或者至少更麻烦。)

    使用该子类,可以编写一个测试来检查前面确定的三个质量:

    procedure TestTMyThread.TestMyThread;
    var
      Data: TTestData;
      WaitResult: TWaitResult;
    begin
      Data.OriginalId := -1;
      Data.FinalId := -1;
      Data.Event := TSimpleEvent.Create;
      try
        TTestableMyThread.Create(1, @Data);
    
        // We don't free the thread, and the event is only set in the destructor,
        // so if the event is signaled, it means the thread freed itself: That
        // aspect of the test implicitly passes. We don't want to wait forever,
        // though, so we fail the test if we have to wait too long. Either the
        // Execute method is taking too long to do its computations, or the thread
        // isn't freeing itself.
        // Adjust the timeout based on expected performance of Execute.
        WaitResult := Data.Event.WaitFor(5000);
        case WaitResult of
          wrSignaled: ; // This is the expected result
          wrTimeOut: Fail('Timed out waiting for thread');
          wrAbandoned: Fail('Event was abandoned');
          wrError: RaiseLastOSError(Data.Event.LastError);
          else Fail('Unanticipated error waiting for thread');
        end;
    
        CheckNotEquals(2, Data.OriginalId,
          'Didn''t wait till Execute to calculate Id');
        CheckEquals(2, Data.FinalId,
          'Calculated wrong Id value');
      finally
        Data.Event.Free;
      end;
    end;
    

    下面是一个使用匿名线程的示例

    • 将创建一个事件(TSimpleEvent)
    • 匿名线程执行测试线程并
    • 等待事件,该事件在测试线程的OnTerminate处理程序中发出信号
    • 匿名线程在使用WaitFor执行之前一直处于挂起状态
    • 结果由OnTerminate处理程序获取
    这里重要的一点是,事件在线程中等待。没有死锁的情况



    更新,因为Delphi-2010没有匿名线程类,这里有一个您可以实现的替代方案:

    Type
      TMyAnonymousThread = class(TThread)
        private
          FProc : TProc;
        protected
          procedure Execute; override;
        public
          constructor Create(CreateSuspended,SelfFree: Boolean; const aProc: TProc);
      end;
    
    constructor TMyAnonymousThread.Create(CreateSuspended,SelfFree: Boolean; 
      const aProc: TProc);
    begin
      Inherited Create(CreateSuspended);
      FreeOnTerminate := SelfFree;
      FProc := aProc;
    end;
    
    procedure TMyAnonymousThread.Execute;
    begin
      FProc();
    end;
    

    我访问正在运行的线程的唯一位置是
    OnTerminate
    处理程序,该处理程序在释放线程之前被调用。请注意,我创建的线程处于挂起状态,并手动恢复它。@DavidHeffernan:没问题。在较新的Delphi版本中,不推荐使用resume。在这些情况下,您应该在创建处于挂起状态的线程后使用Start。此代码将死锁。通过
    Synchronize
    调用
    OnTerminate
    事件,这意味着主线程需要继续检查新的同步方法。spin wait循环调用
    Sleep
    不会这样做,但是
    WaitFor
    调用
    CheckSynchronize
    以避免该问题。布尔值上的自旋等待循环正是事件的用途。这很有趣,谢谢。我想,‘OnTerminate’是从调用上下文调用的。我会纠正我的尝试。谢谢你的回答。我还没来得及测试解决方案,明天我会试试。嗨,罗布,谢谢你的答案似乎很有效。我接受了这个答案。另外,下面使用匿名线程发布了一个有趣的答案。我仍然使用Delphi2010,所以我没有访问它的权限,但它看起来是一个优雅的解决方案。一旦升级,我也会测试。干杯。我很想试试这个,但似乎匿名线程是用Delphi XE带来的,但我仍然使用Delphi 2010。升级的另一个原因。。。谢谢你的回答,当我升级时,我会重新访问,看看它是否有效。谢谢David,我没有测试你的解决方案,主要是因为目前时间不够。现在我同意Rob的第二个答案,但是当我从Delphi2010升级时,我也会使用匿名线程尝试下面的答案,这看起来非常有趣。再次感谢。
    procedure TestTMyThread.TestMyThread;
    var
      Data: TTestData;
      WaitResult: TWaitResult;
    begin
      Data.OriginalId := -1;
      Data.FinalId := -1;
      Data.Event := TSimpleEvent.Create;
      try
        TTestableMyThread.Create(1, @Data);
    
        // We don't free the thread, and the event is only set in the destructor,
        // so if the event is signaled, it means the thread freed itself: That
        // aspect of the test implicitly passes. We don't want to wait forever,
        // though, so we fail the test if we have to wait too long. Either the
        // Execute method is taking too long to do its computations, or the thread
        // isn't freeing itself.
        // Adjust the timeout based on expected performance of Execute.
        WaitResult := Data.Event.WaitFor(5000);
        case WaitResult of
          wrSignaled: ; // This is the expected result
          wrTimeOut: Fail('Timed out waiting for thread');
          wrAbandoned: Fail('Event was abandoned');
          wrError: RaiseLastOSError(Data.Event.LastError);
          else Fail('Unanticipated error waiting for thread');
        end;
    
        CheckNotEquals(2, Data.OriginalId,
          'Didn''t wait till Execute to calculate Id');
        CheckEquals(2, Data.FinalId,
          'Calculated wrong Id value');
      finally
        Data.Event.Free;
      end;
    end;
    
    Uses
      SyncObjs;
    
    type
    
      TMyThread = class(TThread)
      private
        FId : Integer;
      protected
        procedure Execute; override;
      public
        constructor Create( anInt : Integer);
        property Id : Integer read FId;
      end;
    
      TestTMyThread = class
      strict private
        FMyId: Integer;
        FMyEvent : TSimpleEvent;
        procedure OnThreadTerminate(Sender: TObject);
      protected
      public
        procedure TestMyThread;
      end;
    
    { TMyThread }
    
    constructor TMyThread.Create(anInt : Integer);
    begin
      inherited Create(True);
      FreeOnTerminate := True;
      FId := anInt;
    end;
    
    procedure TMyThread.Execute;
    begin
      Inc(FId);
    end;
    
    procedure TestTMyThread.TestMyThread;
    var
      AnonThread : TThread;
    begin
      FMyEvent := TSimpleEvent.Create(nil,true,false,'');
      try
        AnonThread :=
          TThread.CreateAnonymousThread(
            procedure
            begin
              With TMyThread.Create(1) do
              begin
                OnTerminate := Self.OnThreadTerminate;
                Start;
              end;
              FMyEvent.WaitFor; // Wait until TMyThread is ready
            end
          );
        AnonThread.FreeOnTerminate := False;
        AnonThread.Start;
    
        AnonThread.WaitFor; // Wait here until test is ready
        AnonThread.Free;
    
        Assert(FMyId = 2); // Check result
      finally
        FMyEvent.Free;
      end;
    end;
    
    procedure TestTMyThread.OnThreadTerminate(Sender: TObject);
    begin
      FMyId := (Sender as TMyThread).Id;
      FMyEvent.SetEvent; // Signal TMyThread ready
    end;
    
    Type
      TMyAnonymousThread = class(TThread)
        private
          FProc : TProc;
        protected
          procedure Execute; override;
        public
          constructor Create(CreateSuspended,SelfFree: Boolean; const aProc: TProc);
      end;
    
    constructor TMyAnonymousThread.Create(CreateSuspended,SelfFree: Boolean; 
      const aProc: TProc);
    begin
      Inherited Create(CreateSuspended);
      FreeOnTerminate := SelfFree;
      FProc := aProc;
    end;
    
    procedure TMyAnonymousThread.Execute;
    begin
      FProc();
    end;