其他

用 C 语言对 Gtk+ 应用进行功能测试

2017-10-14 译者sugarfillet Linux中国
这个简单教程教你如何测试你应用的功能。
-- Joël Krähemann


本文导航
编译自 | https://opensource.com/article/17/7/functional-testing 
 作者 | Joël Krähemann
 译者 | sugarfillet

这个简单教程教你如何测试你应用的功能。

自动化测试用来保证你程序的质量以及让它以预想的运行。单元测试只是检测你算法的某一部分,而并不注重各组件间的适应性。这就是为什么会有功能测试,它有时也称为集成测试。

功能测试简单地与你的用户界面进行交互,无论它是网站还是桌面应用。为了展示功能测试如何工作,我们以测试一个 Gtk+ 应用为例。为了简单起见,这个教程里,我们使用 Gtk+ 2.0 教程的示例。

基础设置

对于每一个功能测试,你通常需要定义一些全局变量,比如 “用户交互时延” 或者 “失败的超时时间”(也就是说,如果在指定的时间内一个事件没有发生,程序就要中断)。

  1. #define TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION(f) ((TttFunctionalTestUtilIdleCondition)(f))

  2. #define TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME (125000)

  3. #define TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG (500000)

  4. typedef gboolean (*TttFunctionalTestUtilIdleCondition)(gpointer data);

  5. struct timespec ttt_functional_test_util_default_timeout = {

  6.   20,

  7.   0,

  8. };

现在我们可以实现我们自己的超时函数。这里,为了能够得到期望的延迟,我们采用 usleep函数。

  1. void

  2. ttt_functional_test_util_reaction_time()

  3. {

  4.   usleep(TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME);

  5. }

  6. void

  7. ttt_functional_test_util_reaction_time_long()

  8. {

  9.   usleep(TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG);

  10. }

直到获得控制状态,超时函数才会推迟执行。这对于一个异步执行的动作很有帮助,这也是为什么采用这么长的时延。

  1. void

  2. ttt_functional_test_util_idle_condition_and_timeout(

  3.      TttFunctionalTestUtilIdleCondition idle_condition,

  4.      struct timespec *timeout,

  5.      pointer data)

  6. {

  7.   struct timespec start_time, current_time;

  8.   clock_gettime(CLOCK_MONOTONIC,

  9.                 &start_time);

  10.   while(TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION(idle_condition)(data)){

  11.     ttt_functional_test_util_reaction_time();

  12.     clock_gettime(CLOCK_MONOTONIC,

  13.                   &current_time);

  14.     if(start_time.tv_sec + timeout->tv_sec < current_time.tv_sec){

  15.       break;

  16.     }

  17.   }

  18.   ttt_functional_test_util_reaction_time();

  19. }

与图形化用户界面交互

为了模拟用户交互的操作, Gdk 库[1] 为我们提供了一些需要的函数。要完成我们的工作,我们只需要如下 3 个函数:

◈ gdk_display_warp_pointer()◈ gdk_test_simulate_button()◈ gdk_test_simulate_key()

举个例子,为了测试按钮点击,我们可以这么做:

  1. gboolean

  2. ttt_functional_test_util_button_click(GtkButton *button)

  3. {

  4.   GtkWidget *widget;

  5.   GdkWindow *window;

  6.   gint x, y;

  7.   gint origin_x, origin_y;

  8.   if(button == NULL ||

  9.      !GTK_IS_BUTTON(button)){

  10.     return(FALSE);

  11.   }

  12.   widget = button;

  13.   if(!GTK_WIDGET_REALIZED(widget)){

  14.     ttt_functional_test_util_reaction_time_long();

  15.   }

  16.   /* retrieve window and pointer position */

  17.   gdk_threads_enter();

  18.   window = gtk_widget_get_window(widget);

  19.   x = widget->allocation.x + widget->allocation.width / 2.0;

  20.   y = widget->allocation.y + widget->allocation.height / 2.0;

  21.   gdk_window_get_origin(window, &origin_x, &origin_y);

  22.   gdk_display_warp_pointer(gtk_widget_get_display(widget),

  23.                            gtk_widget_get_screen(widget),

  24.                            origin_x + x, origin_y + y);

  25.   gdk_threads_leave();

  26.   /* click the button */

  27.   ttt_functional_test_util_reaction_time();

  28.   gdk_test_simulate_button(window,

  29.                            x,

  30.                            y,

  31.                            1,

  32.                            GDK_BUTTON1_MASK,

  33.                            GDK_BUTTON_PRESS);

  34.   ttt_functional_test_util_reaction_time();

  35.   gdk_test_simulate_button(window,

  36.                            x,

  37.                            y,

  38.                            1,

  39.                            GDK_BUTTON1_MASK,

  40.                            GDK_BUTTON_RELEASE);

  41.   ttt_functional_test_util_reaction_time();

  42.   ttt_functional_test_util_reaction_time_long();

  43.   return(TRUE);

  44. }

我们想要保证按钮处于激活状态,因此我们提供一个空闲条件函数:

  1. gboolean

  2. ttt_functional_test_util_idle_test_toggle_active(

  3.      GtkToggleButton **toggle_button)

  4. {

  5.   gboolean do_idle;

  6.   do_idle = TRUE;

  7.   gdk_threads_enter();

  8.   if(*toggle_button != NULL &&

  9.      GTK_IS_TOGGLE_BUTTON(*toggle_button) &&

  10.      gtk_toggle_button_get_active(*toggle_button)){

  11.     do_idle = FALSE;

  12.   }

  13.   gdk_threads_leave();

  14.   return(do_idle);

  15. }

测试场景

因为这个 Tictactoe 程序非常简单,我们只需要确保点击了一个 GtkToggleButton[2] 按钮即可。一旦该按钮肯定进入了激活状态,功能测试就可以执行。为了点击按钮,我们使用上面提到的很方便的 util 函数。

如图所示,我们假设,填满第一行,玩家 A 就赢,因为玩家 B 没有注意,只填充了第二行。

  1. GtkWindow *window;

  2. Tictactoe *ttt;

  3. void*

  4. ttt_functional_test_gtk_main(void *)

  5. {

  6.   gtk_main();

  7.   pthread_exit(NULL);

  8. }

  9. void

  10. ttt_functional_test_dumb_player_b()

  11. {

  12.   GtkButton *buttons[3][3];

  13.   guint i;

  14.   /* to avoid race-conditions copy the buttons */

  15.   gdk_threads_enter();

  16.   memcpy(buttons, ttt->buttons, 9 * sizeof(GtkButton *));

  17.   gdk_threads_leave();

  18.   /* TEST 1 - the dumb player B */

  19.   for(i = 0; i < 3; i++){

  20.     /* assert player A clicks the button successfully */

  21.     if(!ttt_functional_test_util_button_click(buttons[0][i])){

  22.       exit(-1);

  23.     }

  24.     functional_test_util_idle_condition_and_timeout(

  25.          ttt_functional_test_util_idle_test_toggle_active,

  26.          ttt_functional_test_util_default_timeout,

  27.          &buttons[0][i]);

  28.     /* assert player B clicks the button successfully */

  29.     if(!ttt_functional_test_util_button_click(buttons[1][i])){

  30.       exit(-1);

  31.     }

  32.     functional_test_util_idle_condition_and_timeout(

  33.          ttt_functional_test_util_idle_test_toggle_active,

  34.          ttt_functional_test_util_default_timeout,

  35.          &buttons[1][i]);

  36.   }

  37. }

  38. int

  39. main(int argc, char **argv)

  40. {

  41.   pthread_t thread;

  42.   gtk_init(&argc, &argv);

  43.   /* start the tictactoe application */

  44.   window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  45.   ttt = tictactoe_new();

  46.   gtk_container_add(window, ttt);

  47.   gtk_widget_show_all(window);

  48.   /* start the Gtk+ dispatcher */

  49.   pthread_create(&thread, NULL,

  50.                  ttt_functional_test_gtk_main, NULL);

  51.   /* launch test routines */

  52.   ttt_functional_test_dumb_player_b();

  53.   /* terminate the application */

  54.   gdk_threads_enter();

  55.   gtk_main_quit();

  56.   gdk_threads_leave();

  57.   return(0);

  58. }

(题图:opensource.com)


作者简介:

Joël Krähemann - 精通 C 语言编程的自由软件爱好者。不管代码多复杂,它也是一点点写成的。作为高级的 Gtk+ 程序开发者,我知道多线程编程有多大的挑战性,有了多线程编程,我们就有了未来需求的良好基础。

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

作者:Joël Krähemann[4] 译者:sugarfillet 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

LCTT 译者
sugarfillet共计翻译:2 篇贡献时间:75 天

推荐文章

< 左右滑动查看相关文章 >

点击图片、输入文章 ID 或识别二维码直达



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存