Linux中國

用 C 語言對 Gtk+ 應用進行功能測試

自動化測試用來保證你程序的質量以及讓它以預想的運行。單元測試只是檢測你演算法的某一部分,而並不注重各組件間的適應性。這就是為什麼會有功能測試,它有時也稱為集成測試。

功能測試簡單地與你的用戶界面進行交互,無論它是網站還是桌面應用。為了展示功能測試如何工作,我們以測試一個 Gtk+ 應用為例。為了簡單起見,這個教程里,我們使用 Gtk+ 2.0 教程的示例。

基礎設置

對於每一個功能測試,你通常需要定義一些全局變數,比如 「用戶交互時延」 或者 「失敗的超時時間」(也就是說,如果在指定的時間內一個事件沒有發生,程序就要中斷)。

#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,
};

現在我們可以實現我們自己的超時函數。這裡,為了能夠得到期望的延遲,我們採用 usleep 函數。

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);
}

直到獲得控制狀態,超時函數才會推遲執行。這對於一個非同步執行的動作很有幫助,這也是為什麼採用這麼長的時延。

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,
                  &current_time);

    if(start_time.tv_sec + timeout->tv_sec < current_time.tv_sec){
      break;
    }
  }

  ttt_functional_test_util_reaction_time();
}

與圖形化用戶界面交互

為了模擬用戶交互的操作, Gdk 庫 為我們提供了一些需要的函數。要完成我們的工作,我們只需要如下 3 個函數:

  • gdk_display_warp_pointer()
  • gdk_test_simulate_button()
  • gdk_test_simulate_key()

舉個例子,為了測試按鈕點擊,我們可以這麼做:

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);
}

我們想要保證按鈕處於激活狀態,因此我們提供一個空閑條件函數:

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);
}

測試場景

因為這個 Tictactoe 程序非常簡單,我們只需要確保點擊了一個 GtkToggleButton 按鈕即可。一旦該按鈕肯定進入了激活狀態,功能測試就可以執行。為了點擊按鈕,我們使用上面提到的很方便的 util 函數。

如圖所示,我們假設,填滿第一行,玩家 A 就贏,因為玩家 B 沒有注意,只填充了第二行。

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)

作者簡介:

Joël Krähemann - 精通 C 語言編程的自由軟體愛好者。不管代碼多複雜,它也是一點點寫成的。作為高級的 Gtk+ 程序開發者,我知道多線程編程有多大的挑戰性,有了多線程編程,我們就有了未來需求的良好基礎。

摘自: https://opensource.com/article/17/7/functional-testing

作者:Joël Krähemann 譯者:sugarfillet 校對:wxy

本文由 LCTT 原創編譯,Linux中國 榮譽推出


本文轉載來自 Linux 中國: https://github.com/Linux-CN/archive

對這篇文章感覺如何?

太棒了
0
不錯
0
愛死了
0
不太好
0
感覺很糟
0
雨落清風。心向陽

    You may also like

    Leave a reply

    您的電子郵箱地址不會被公開。 必填項已用 * 標註

    此站點使用Akismet來減少垃圾評論。了解我們如何處理您的評論數據

    More in:Linux中國