檢視原始碼 事件處理

一般

Common Test 系統的操作員可以在測試執行期間持續接收事件通知。例如,Common Test 會報告測試案例何時開始和停止、目前成功、失敗和跳過的案例計數等等。這些資訊可以用於不同的目的,例如以 HTML 以外的格式記錄進度和結果、將統計資料儲存到資料庫以產生報告,以及測試系統監管。

Common Test 有一個基於 OTP 事件管理器概念和 gen_event 行為的事件處理框架。當 Common Test 伺服器啟動時,它會產生一個事件管理器。在測試執行期間,當發生任何潛在的感興趣事件時,管理器會從伺服器收到通知。任何插入事件管理器的事件處理程式都可以匹配感興趣的事件、採取動作或傳遞資訊。事件處理程式是 Erlang 模組,由 Common Test 使用者根據 gen_event 行為實作(詳細資訊請參閱模組 gen_event 以及系統文件中 OTP 設計原則的 gen_event 行為 部分)。

Common Test 伺服器總是會啟動一個事件管理器。伺服器還會插入一個預設事件處理程式,其唯一目的是將通知中繼到全域註冊的 Common Test Master 事件管理器(如果系統中正在執行 Common Test Master 伺服器)。Common Test Master 也在啟動時產生一個事件管理器。插入此管理器的事件處理程式會接收來自所有測試節點的事件,以及來自 Common Test Master 伺服器的資訊。

使用者特定的事件處理程式可以插入 Common Test 事件管理器中,方法是在測試執行之前告知 Common Test 安裝它們(稍後說明),或在測試執行期間使用 gen_event:add_handler/3gen_event:add_sup_handler/3 動態新增處理程式。在後一種情況下,需要 Common Test 事件管理器的參考。若要取得它,請呼叫 ct:get_event_mgr_ref/0 或(在 Common Test Master 節點上)ct_master:get_event_mgr_ref/0

使用

事件處理程式可以透過 event_handler 啟動旗標(ct_run)或選項 ct:run_test/1 安裝,其中引數指定一個或多個事件處理程式模組的名稱。

範例

$ ct_run -suite test/my_SUITE -event_handler handlers/my_evh1 handlers/my_evh2 -pa $PWD/handlers

若要將啟動引數傳遞給事件處理程式的 init 函數,請使用選項 ct_run -event_handler_init 而不是 -event_handler

注意

所有事件處理程式模組都必須具有 gen_event 行為。這些模組必須預先編譯,並且它們的位置必須明確地新增至 Erlang 程式碼伺服器搜尋路徑(如先前的範例所示)。

引數 Opts 中的 event_handler 元組具有以下定義(請參閱 ct:run_test/1

{event_handler,EventHandlers}

EventHandlers = EH | [EH]
EH = atom() | {atom(),InitArgs} | {[atom()],InitArgs}
InitArgs = [term()]

在以下範例中,安裝了 my_SUITE 測試的兩個事件處理程式

1> ct:run_test([{suite,"test/my_SUITE"},{event_handler,[my_evh1,{my_evh2,[node()]}]}]).

事件處理程式 my_evh1[] 作為 init 函數的引數啟動。事件處理程式 my_evh2 以目前節點的名稱在 init 引數清單中啟動。

事件處理程式也可以使用以下其中一個 測試規格 詞彙插入

  • {event_handler, EventHandlers}
  • {event_handler, EventHandlers, InitArgs}
  • {event_handler, NodeRefs, EventHandlers}
  • {event_handler, NodeRefs, EventHandlers, InitArgs}

EventHandlers 是模組名稱的清單。在測試階段開始之前,會呼叫每個插入的事件處理程式的 init 函數(以 InitArgs 清單作為引數,如果未指定啟動引數,則使用 [] 作為引數)。

若要將處理程式插入 Common Test Master 事件管理器,請在 NodeRefs 中指定 master 作為節點。

為了能夠匹配事件,事件處理程式模組必須包含標頭檔 ct_event.hrl。事件是具有以下定義的記錄

#event{name, node, data}

  • name - 事件的標籤(類型)。

  • node - 事件來源的節點名稱(僅與 Common Test Master 事件處理程式相關)。

  • data - 特定於事件。

一般事件

一般事件如下

  • #event{name = start_logging, data = LogDir} - LogDir = string(),測試執行的頂層記錄目錄。

    此事件表示 Common Test 的記錄程序已成功啟動,並準備好接收 I/O 訊息。

  • #event{name = stop_logging, data = []} - 此事件表示 Common Test 的記錄程序在測試執行結束時關閉。

  • #event{name = test_start, data = {StartTime,LogDir}} - StartTime = {date(),time()},測試執行開始的日期和時間。

    LogDir = string(),測試執行的頂層記錄目錄。

    此事件表示 Common Test 已完成初始準備,並開始執行測試案例。

  • #event{name = test_done, data = EndTime} - EndTime = {date(),time()},測試執行完成的日期和時間。

    此事件表示最後一個測試案例已執行,且 Common Test 正在關閉。

  • #event{name = start_info, data = {Tests,Suites,Cases}} - Tests = integer(),測試數量。

    Suites = integer(),套件總數。

    Cases = integer() | unknown,測試案例總數。

    此事件提供初始測試執行資訊,可以解釋為:「此測試執行將執行 Tests 個獨立測試,總共包含 Cases 個測試案例,在 Suites 個套件中」。但是,如果任何測試中存在具有重複屬性的測試案例群組,則無法計算測試案例的總數(未知)。

  • #event{name = tc_start, data = {Suite,FuncOrGroup}} - Suite = atom(),測試套件的名稱。

    FuncOrGroup = Func | {Conf,GroupName,GroupProperties}

    Func = atom(),測試案例或設定函數的名稱。

    Conf = init_per_group | end_per_group,群組設定函數。

    GroupName = atom(),群組的名稱。

    GroupProperties = list(),群組的執行屬性清單。

    此事件通知測試案例或群組設定函數的開始。也會針對 init_per_suiteend_per_suite 送出事件,但不針對 init_per_testcaseend_per_testcase 送出。如果群組設定函數開始,也會指定群組名稱和執行屬性。

  • #event{name = tc_logfile, data = {{Suite,Func},LogFileName}} - Suite = atom(),測試套件的名稱。

    Func = atom(),測試案例或設定函數的名稱。

    LogFileName = string(),測試案例記錄檔的完整名稱。

    此事件會在每個測試案例(以及設定函數,除了 init/end_per_testcase 之外)開始時送出,並攜帶目前測試案例記錄檔完整名稱的資訊(也就是包含絕對目錄路徑的檔案名稱)。

  • #event{name = tc_done, data = {Suite,FuncOrGroup,Result}} - Suite = atom(),套件的名稱。

    FuncOrGroup = Func | {Conf,GroupName,GroupProperties}

    Func = atom(),測試案例或設定函數的名稱。

    Conf = init_per_group | end_per_group,群組設定函數。

    GroupName = unknown | atom(),群組的名稱(如果 init 或 end 函數逾時則為 unknown)。

    GroupProperties = list(),群組的執行屬性清單。

    Result = ok | {auto_skipped,SkipReason} | {skipped,SkipReason} | {failed,FailReason},結果。

    SkipReason = {require_failed,RequireInfo} | {require_failed_in_suite0,RequireInfo} | {failed,{Suite,init_per_testcase,FailInfo}} | UserTerm,案例為何被跳過。

    FailReason = {error,FailInfo} | {error,{RunTimeError,StackTrace}} | {timetrap_timeout,integer()} | {failed,{Suite,end_per_testcase,FailInfo}},失敗原因。

    RequireInfo = {not_available,atom() | tuple()},require 為何失敗。

    FailInfo = {timetrap_timeout,integer()} | {RunTimeError,StackTrace} | UserTerm,錯誤詳細資訊。

    RunTimeError = term(),執行階段錯誤,例如,badmatchundef

    StackTrace = list(),執行階段錯誤之前的函數呼叫清單。

    UserTerm = term(),使用者指定的任何資料,或 exit/1 資訊。

    此事件通知測試案例或配置函數的結束(有關元素 FuncOrGroup 的詳細資訊,請參閱事件 tc_start)。此事件會帶有相關函數的最終結果。可以根據 Result 的最上層判斷函數是成功、跳過(由使用者跳過)還是失敗。

    也可以更深入地研究,例如,對跳過或失敗的各種原因執行模式比對。請注意,{'EXIT',Reason} 元組會轉換為 {error,Reason}。另請注意,如果收到 {failed,{Suite,end_per_testcase,FailInfo} 結果,則表示測試案例成功,但該案例的 end_per_testcase 失敗。

  • #event{name = tc_auto_skip, data = {Suite,TestName,Reason}} - Suite = atom(),套件的名稱。

    TestName = init_per_suite | end_per_suite | {init_per_group,GroupName} | {end_per_group,GroupName} | {FuncName,GroupName} | FuncName

    FuncName = atom(),測試案例或配置函數的名稱。

    GroupName = atom(),測試案例群組的名稱。

    Reason = {failed,FailReason} | {require_failed_in_suite0,RequireInfo},自動跳過 Func 的原因。

    FailReason = {Suite,ConfigFunc,FailInfo}} | {Suite,FailedCaseInSequence},失敗原因。

    RequireInfo = {not_available,atom() | tuple()},require 為何失敗。

    ConfigFunc = init_per_suite | init_per_group

    FailInfo = {timetrap_timeout,integer()} | {RunTimeError,StackTrace} | bad_return | UserTerm,錯誤詳細資訊。

    FailedCaseInSequence = atom(),序列中失敗的案例名稱。

    RunTimeError = term(),執行階段錯誤,例如 badmatchundef

    StackTrace = list(),執行階段錯誤之前的函數呼叫清單。

    UserTerm = term(),使用者指定的任何資料,或 exit/1 資訊。

    Common Test 因為 init_per_suiteinit_per_group 失敗、suite/0 中的 require 失敗或序列中的測試案例失敗而自動跳過每個測試案例或配置函數時,會傳送此事件。請注意,此事件永遠不會因為 init_per_testcase 失敗而導致測試案例跳過而接收到,因為該資訊會與事件 tc_done 一起傳送。如果失敗的測試案例屬於測試案例群組,則第二個資料元素是元組 {FuncName,GroupName},否則只有函數名稱。

  • #event{name = tc_user_skip, data = {Suite,TestName,Comment}} - Suite = atom(),套件的名稱。

    TestName = init_per_suite | end_per_suite | {init_per_group,GroupName} | {end_per_group,GroupName} | {FuncName,GroupName} | FuncName

    FuncName = atom(),測試案例或配置函數的名稱。

    GroupName = atom(),測試案例群組的名稱。

    Comment = string(),測試案例被跳過的原因。

    此事件指定測試案例是由使用者跳過。僅當跳過是在測試規格中宣告時才會收到。否則,使用者跳過資訊會以測試案例的事件 tc_done 中的 {skipped,SkipReason} 結果收到。如果跳過的測試案例屬於測試案例群組,則第二個資料元素是元組 {FuncName,GroupName},否則只有函數名稱。

  • #event{name = test_stats, data = {Ok,Failed,Skipped}} - Ok = integer(),目前成功測試案例的數量。

    Failed = integer(),目前失敗測試案例的數量。

    Skipped = {UserSkipped,AutoSkipped}

    UserSkipped = integer(),目前使用者跳過的測試案例數量。

    AutoSkipped = integer(),目前自動跳過的測試案例數量。

    這是一個統計事件,其中包含目前成功、跳過和失敗測試案例的計數。此事件會在每個測試案例結束後立即傳送,緊接著事件 tc_done

內部事件

內部事件如下

  • #event{name = start_make, data = Dir} - Dir = string(),在此目錄中執行 make。

    此內部事件表示 Common Test 開始在目錄 Dir 中編譯模組。

  • #event{name = finished_make, data = Dir} - Dir = string(),在此目錄中執行 make 完成。

    此內部事件表示 Common Test 已完成在目錄 Dir 中編譯模組。

  • #event{name = start_write_file, data = FullNameFile} - FullNameFile = string(),檔案的完整名稱。

    此內部事件由 Common Test 主程序用於同步特定的檔案操作。

  • #event{name = finished_write_file, data = FullNameFile} - FullNameFile = string(),檔案的完整名稱。

    此內部事件由 Common Test 主程序用於同步特定的檔案操作。

附註

這些事件也記錄在 ct_event.erl 中。此模組可以作為 Common Test 事件管理器的事件處理程式範例。

注意

為了確保列印到 stdout 的內容(或使用 ct:log/2,3ct:pal,2,3 進行的列印)會寫入測試案例記錄檔,而不是 Common Test 框架記錄,您可以透過比對事件 tc_starttc_doneCommon Test 伺服器同步。在這些事件之間的時間內,所有 I/O 都會導向到測試案例記錄檔。這些事件會同步傳送,以避免潛在的時序問題(例如,測試案例記錄檔在外部程序的 I/O 訊息傳遞之前關閉)。了解這一點後,您需要小心您的 handle_event/2 回呼函數不會停滯測試執行,並可能因此導致意想不到的行為。