2013-03-20 19 views
12

Cách tốt nhất để viết một bài kiểm tra Delphi DUnit cho một hậu duệ TThread khi FreeOnTerminate = True là gì? Các TThread hậu duệ trả về một tài liệu tham khảo mà tôi cần để kiểm tra, nhưng tôi không thể tìm ra cách để chờ cho thread để kết thúc trong các thử nghiệm ...Kiểm tra đơn vị Delphi cho một TThread với FreeOnTerminate = True

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. 

ý tưởng Bất kỳ sẽ được hoan nghênh.

Delphi 2010.

Trả lời

3

Phân lớp chủ đề để làm cho nó dễ kiểm tra hơn. TThreadTObject cung cấp đủ móc mà bạn có thể thêm các biến cảm biến để quan sát thấy nó đạt đến một số điểm nhất định với các trạng thái bạn muốn nó có.

tôi thấy ba khía cạnh để lớp học đặc biệt này mà bạn có thể muốn kiểm tra:

  1. Nó tính toán giá trị cho tài sản Id của nó dựa trên giá trị gửi đến các nhà xây dựng.
  2. Nó tính toán thuộc tính mới Id trong chuỗi mới, chứ không phải là chuỗi gọi hàm tạo.
  3. Nó tự giải phóng khi hoàn tất.

Tất cả những điều đó đều có thể kiểm tra từ phân lớp, nhưng khó kiểm tra nếu không thay đổi giao diện của luồng. (Tất cả các câu trả lời khác cho đến nay yêu cầu thay đổi giao diện của luồng, chẳng hạn như bằng cách thêm các đối số hàm tạo khác hoặc bằng cách thay đổi cách nó bắt đầu. Điều đó có thể làm cho luồng khó khăn hơn, hoặc ít nhất là cồng kềnh hơn để sử dụng trong chương trình thực.)

type 
    PTestData = ^TTestData; 
    TTestData = record 
    Event: TEvent; 
    OriginalId: Integer; 
    FinalId: Integer; 
    end; 

    TTestableMyThread = class(TMyThread) 
    private 
    FData: PTestData; 
    public 
    constructor Create(AId: Integer; AData: PTestData); 
    destructor Destroy; override; 
    procedure AfterConstruction; override; 
    end; 

constructor TTestableMyThread.Create(AId: Integer; const AData: PTestData); 
begin 
    inherited Create(AId); 
    FData := AData; 
end; 

destructor TestableMyThread.Destroy; 
begin 
    inherited; 
    FData.FinalId := Id; 
    // Tell the test that the thread has been freed 
    FData.Event.SetEvent; 
end; 

procedure TTestableMyThread.AfterConstruction; 
begin 
    FData.OriginalId := Id; 
    inherited; // Call this last because this is where the thread starts running 
end; 

sử dụng lớp con đó, nó có thể viết một bài kiểm tra để kiểm tra ba phẩm chất xác định trước đó:

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; 
+0

Cảm ơn câu trả lời Rob. Tôi chưa có thời gian để kiểm tra giải pháp nhưng sẽ thử vào ngày mai. –

+0

Xin chào Rob, cảm ơn câu trả lời của bạn dường như đang hoạt động tốt. Tôi đã chấp nhận điều này như là câu trả lời. Ngoài ra, có một câu trả lời thú vị được đăng bên dưới bằng cách sử dụng các chuỗi ẩn danh. Tôi vẫn còn trên Delphi 2010 vì vậy tôi không có quyền truy cập vào nó, nhưng trông giống như một giải pháp thanh lịch. Sẽ kiểm tra điều đó một khi tôi đã nâng cấp. Chúc mừng. –

2

Tạo thread trong trạng thái lơ lửng, sau đó đặt OnTerminate và cuối cùng Resume thread.

Trong lớp thử nghiệm của bạn, hãy xác định trường boolean riêng FThreadDone được khởi tạo bằng false và được đặt thành true bởi Tổ chức sự kiện OnTerminate.

Ngoài ra, logic của hàm tạo của bạn hơi bẩn, vì bạn không nên khởi tạo trường trước khi gọi hàm tạo được kế thừa.

Vì vậy:

constructor TMyThread.Create(AId: Integer); 
begin 
    inherited Create(true); 
    FreeOnTerminate := True; 
    FId := AId; 
end; 
... 
procedure TestTMyThread.TestMyThread; 
begin 
    FThreadDone := False; 
    with TMyThread.Create(1) do begin // Note: Thread is suspended... 
    OnTerminate := OnThreadTerminate; 
    // Resume;       // ... and finally started here! 
    Start; 

    end; 
    While not FThreadDone do Application.ProcessMessages; 
    CheckEquals(2, FMyId); 
end; 

procedure TestTMyThread.OnThreadTerminate(Sender: TObject); 
begin 
    FMyId := (Sender as TMyThread).Id; 
    FThreadDone := True; 
end; 

này nên thực hiện công việc.

CHỈNH SỬA: Sửa lỗi chính xác, đã kiểm tra, hoạt động.

+0

Nơi duy nhất tôi truy cập vào các hoạt động luồng nằm trong trình xử lý 'OnTerminate' được gọi là * trước * chuỗi được giải phóng. Xin lưu ý rằng tôi tạo chuỗi trong trạng thái bị tạm ngưng và tiếp tục thủ công. – alzaimar

+0

@DavidHeffernan: Không sao cả. – alzaimar

+2

Tiếp tục không được chấp nhận trong các phiên bản Delphi mới hơn. Trong đó, bạn nên sử dụng Start sau khi tạo một thread trong trạng thái treo. –

2

Vì bạn đã tự tạo chủ đề khi chấm dứt sau đó bạn đã yêu cầu nó hủy mọi dấu vết của chính nó ngay sau khi hoàn thành. Vì bạn không thể gây ảnh hưởng khi nó kết thúc, bạn có thể tham khảo bất kỳ thứ gì bên trong chuỗi sau khi bạn bắt đầu nó.

Các giải pháp được đề xuất bởi người khác, cụ thể là yêu cầu chủ đề báo hiệu cho bạn khi kết thúc, là tốt. Cá nhân tôi có lẽ sẽ chọn làm theo cách đó. Nếu bạn sử dụng sự kiện làm tín hiệu thì bạn có thể đợi sự kiện đó.

Tuy nhiên, có một cách khác để thực hiện điều đó.

  1. Tạo chủ đề bị tạm ngưng.
  2. Sao chép trình xử lý chuỗi.
  3. Bắt đầu chuỗi.
  4. Chờ trên tay cầm được sao chép.

Vì bạn sở hữu tay cầm được sao chép, thay vì chỉ chuỗi, bạn có thể an tâm chờ đợi. Nó có vẻ phức tạp hơn một chút, nhưng tôi cho rằng nó tránh tạo ra một đối tượng đồng bộ hóa bổ sung, nơi không cần thiết. Lưu ý rằng tôi không ủng hộ cách tiếp cận này qua phương pháp sử dụng sự kiện để hoàn thành tín hiệu.

Dù sao, đây là một minh chứng đơn giản về ý tưởng.

{$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. 
+0

Cảm ơn David, tôi đã không kiểm tra giải pháp của bạn chủ yếu là do thiếu thời gian vào lúc này. Tôi đã đi với câu trả lời thứ hai của Rob cho bây giờ, nhưng khi tôi nâng cấp từ Delphi 2010 tôi cũng sẽ thử câu trả lời dưới đây bằng cách sử dụng các chủ đề ẩn danh trông rất thú vị. Cảm ơn một lần nữa. –

1

Dưới đây là một ví dụ sử dụng một chủ đề vô danh.

  • Một sự kiện (TSimpleEvent) được tạo ra
  • Một chủ đề mang tính chất thực hiện các chủ đề thử nghiệm và
  • Waits cho sự kiện này, mà các tín hiệu trong xử lý OnTerminate của thread kiểm tra
  • Sợi chỉ mang tính chất là trên giữ cho đến khi thực hiện với một wAITFOR
  • kết quả được chọn của các handler OnTerminate

điều quan trọng ở đây là sự kiện được chờ đợi trong một chuỗi. Không có tình trạng khóa chết.


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; 

Update, kể từ Delphi năm 2010 không có một lớp chủ đề mang tính chất đây là một sự thay thế mà bạn có thể thực hiện:

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; 
+0

Muốn thử điều này, nhưng có vẻ như là các chủ đề ẩn danh đến với Delphi XE, nhưng tôi vẫn còn trên Delphi 2010. Một lý do khác để nâng cấp ... Cảm ơn câu trả lời của bạn, khi tôi nâng cấp, tôi sẽ xem lại và xem nếu nó hoạt động. –