用C語(yǔ)言對(duì)Gtk+應(yīng)用進(jìn)行功能測(cè)試
這個(gè)簡(jiǎn)單教程教你如何測(cè)試你應(yīng)用的功能。
自動(dòng)化測(cè)試用來(lái)保證你程序的質(zhì)量以及讓它以預(yù)想的運(yùn)行。單元測(cè)試只是檢測(cè)你算法的某一部分,而并不注重各組件間的適應(yīng)性。這就是為什么會(huì)有功能測(cè)試,它有時(shí)也稱為集成測(cè)試。
功能測(cè)試簡(jiǎn)單地與你的用戶界面進(jìn)行交互,無(wú)論它是網(wǎng)站還是桌面應(yīng)用。為了展示功能測(cè)試如何工作,我們以測(cè)試一個(gè) Gtk+ 應(yīng)用為例。為了簡(jiǎn)單起見(jiàn),這個(gè)教程里,我們使用 Gtk+ 2.0 教程的示例。
基礎(chǔ)設(shè)置
對(duì)于每一個(gè)功能測(cè)試,你通常需要定義一些全局變量,比如 “用戶交互時(shí)延” 或者 “失敗的超時(shí)時(shí)間”(也就是說(shuō),如果在指定的時(shí)間內(nèi)一個(gè)事件沒(méi)有發(fā)生,程序就要中斷)。
- #define TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION(f) ((TttFunctionalTestUtilIdleCondition)(f))
- #define TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME (125000)
- #define TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG (500000)
- typedef gboolean (*TttFunctionalTestUtilIdleCondition)(gpointer data);
- struct timespec ttt_functional_test_util_default_timeout = {
- 20,
- 0,
- };
現(xiàn)在我們可以實(shí)現(xiàn)我們自己的超時(shí)函數(shù)。這里,為了能夠得到期望的延遲,我們采用 usleep 函數(shù)。
- void
- ttt_functional_test_util_reaction_time()
- {
- usleep(TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME);
- }
- void
- ttt_functional_test_util_reaction_time_long()
- {
- usleep(TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG);
- }
直到獲得控制狀態(tài),超時(shí)函數(shù)才會(huì)推遲執(zhí)行。這對(duì)于一個(gè)異步執(zhí)行的動(dòng)作很有幫助,這也是為什么采用這么長(zhǎng)的時(shí)延。
- void
- ttt_functional_test_util_idle_condition_and_timeout(
- TttFunctionalTestUtilIdleCondition idle_condition,
- struct timespec *timeout,
- pointer data)
- {
- struct timespec start_time, current_time;
- clock_gettime(CLOCK_MONOTONIC,
- &start_time);
- while(TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION(idle_condition)(data)){
- ttt_functional_test_util_reaction_time();
- clock_gettime(CLOCK_MONOTONIC,
- ¤t_time);
- if(start_time.tv_sec + timeout->tv_sec < current_time.tv_sec){
- break;
- }
- }
- ttt_functional_test_util_reaction_time();
- }
與圖形化用戶界面交互
為了模擬用戶交互的操作, Gdk 庫(kù) 為我們提供了一些需要的函數(shù)。要完成我們的工作,我們只需要如下 3 個(gè)函數(shù):
- gdk_display_warp_pointer()
- gdk_test_simulate_button()
- gdk_test_simulate_key()
舉個(gè)例子,為了測(cè)試按鈕點(diǎn)擊,我們可以這么做:
- gboolean
- ttt_functional_test_util_button_click(GtkButton *button)
- {
- GtkWidget *widget;
- GdkWindow *window;
- gint x, y;
- gint origin_x, origin_y;
- if(button == NULL ||
- !GTK_IS_BUTTON(button)){
- return(FALSE);
- }
- widget = button;
- if(!GTK_WIDGET_REALIZED(widget)){
- ttt_functional_test_util_reaction_time_long();
- }
- /* retrieve window and pointer position */
- gdk_threads_enter();
- window = gtk_widget_get_window(widget);
- x = widget->allocation.x + widget->allocation.width / 2.0;
- y = widget->allocation.y + widget->allocation.height / 2.0;
- gdk_window_get_origin(window, &origin_x, &origin_y);
- gdk_display_warp_pointer(gtk_widget_get_display(widget),
- gtk_widget_get_screen(widget),
- origin_x + x, origin_y + y);
- gdk_threads_leave();
- /* click the button */
- ttt_functional_test_util_reaction_time();
- gdk_test_simulate_button(window,
- x,
- y,
- 1,
- GDK_BUTTON1_MASK,
- GDK_BUTTON_PRESS);
- ttt_functional_test_util_reaction_time();
- gdk_test_simulate_button(window,
- x,
- y,
- 1,
- GDK_BUTTON1_MASK,
- GDK_BUTTON_RELEASE);
- ttt_functional_test_util_reaction_time();
- ttt_functional_test_util_reaction_time_long();
- return(TRUE);
- }
我們想要保證按鈕處于激活狀態(tài),因此我們提供一個(gè)空閑條件函數(shù):
- gboolean
- ttt_functional_test_util_idle_test_toggle_active(
- GtkToggleButton **toggle_button)
- {
- gboolean do_idle;
- do_idle = TRUE;
- gdk_threads_enter();
- if(*toggle_button != NULL &&
- GTK_IS_TOGGLE_BUTTON(*toggle_button) &&
- gtk_toggle_button_get_active(*toggle_button)){
- do_idle = FALSE;
- }
- gdk_threads_leave();
- return(do_idle);
- }
測(cè)試場(chǎng)景
因?yàn)檫@個(gè) Tictactoe 程序非常簡(jiǎn)單,我們只需要確保點(diǎn)擊了一個(gè) GtkToggleButton 按鈕即可。一旦該按鈕肯定進(jìn)入了激活狀態(tài),功能測(cè)試就可以執(zhí)行。為了點(diǎn)擊按鈕,我們使用上面提到的很方便的 util 函數(shù)。
如圖所示,我們假設(shè),填滿***行,玩家 A 就贏,因?yàn)橥婕?B 沒(méi)有注意,只填充了第二行。
- GtkWindow *window;
- Tictactoe *ttt;
- void*
- ttt_functional_test_gtk_main(void *)
- {
- gtk_main();
- pthread_exit(NULL);
- }
- void
- ttt_functional_test_dumb_player_b()
- {
- GtkButton *buttons[3][3];
- guint i;
- /* to avoid race-conditions copy the buttons */
- gdk_threads_enter();
- memcpy(buttons, ttt->buttons, 9 * sizeof(GtkButton *));
- gdk_threads_leave();
- /* TEST 1 - the dumb player B */
- for(i = 0; i < 3; i++){
- /* assert player A clicks the button successfully */
- if(!ttt_functional_test_util_button_click(buttons[0][i])){
- exit(-1);
- }
- functional_test_util_idle_condition_and_timeout(
- ttt_functional_test_util_idle_test_toggle_active,
- ttt_functional_test_util_default_timeout,
- &buttons[0][i]);
- /* assert player B clicks the button successfully */
- if(!ttt_functional_test_util_button_click(buttons[1][i])){
- exit(-1);
- }
- functional_test_util_idle_condition_and_timeout(
- ttt_functional_test_util_idle_test_toggle_active,
- ttt_functional_test_util_default_timeout,
- &buttons[1][i]);
- }
- }
- int
- main(int argc, char **argv)
- {
- pthread_t thread;
- gtk_init(&argc, &argv);
- /* start the tictactoe application */
- window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- ttt = tictactoe_new();
- gtk_container_add(window, ttt);
- gtk_widget_show_all(window);
- /* start the Gtk+ dispatcher */
- pthread_create(&thread, NULL,
- ttt_functional_test_gtk_main, NULL);
- /* launch test routines */
- ttt_functional_test_dumb_player_b();
- /* terminate the application */
- gdk_threads_enter();
- gtk_main_quit();
- gdk_threads_leave();
- return(0);
- }
(題圖:opensource.com)