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