这个简单教程教你如何测试你应用的功能。
自动化测试用来保证你程序的质量以及让它以预想的运行。单元测试只是检测你算法的某一部分,而并不注重各组件间的适应性。这就是为什么会有功能测试,它有时也称为集成测试。
功能测试简单地与你的用户界面进行交互,无论它是网站还是桌面应用。为了展示功能测试如何工作,我们以测试一个 Gtk+ 应用为例。为了简单起见,这个教程里,我们使用 Gtk+ 2.0 教程的示例。
基础设置 对于每一个功能测试,你通常需要定义一些全局变量,比如 “用户交互时延” 或者 “失败的超时时间”(也就是说,如果在指定的时间内一个事件没有发生,程序就要中断)。
1 2 3 4 5 6 7 8 9 #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
函数。
1 2 3 4 5 6 7 8 9 10 11 12 voidttt_functional_test_util_reaction_time () { usleep (TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME); } voidttt_functional_test_util_reaction_time_long () { usleep (TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG); }
直到获得控制状态,超时函数才会推迟执行。这对于一个异步执行的动作很有帮助,这也是为什么采用这么长的时延。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 voidttt_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 库 为我们提供了一些需要的函数。要完成我们的工作,我们只需要如下 3 个函数:
gdk_display_warp_pointer()
gdk_test_simulate_button()
gdk_test_simulate_key()
举个例子,为了测试按钮点击,我们可以这么做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 gboolean ttt_functional_test_util_button_click(GtkButton GdkWindow 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(); } 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(); 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); }
我们想要保证按钮处于激活状态,因此我们提供一个空闲条件函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 没有注意,只填充了第二行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 GtkWindow Tictactoe void pthread_exit(NULL ); } void ttt_functional_test_dumb_player_b() { GtkButton guint i; gdk_threads_enter(); memcpy(buttons, ttt->buttons, 9 gdk_threads_leave(); for(i = 0; i < 3; i++){ 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]); 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 gtk_init(&argc , &argv ); window = gtk_window_new(GTK_WINDOW_TOPLEVEL); ttt = tictactoe_new(); gtk_container_add(window , ttt); gtk_widget_show_all(window ); pthread_create(&thread , NULL , ttt_functional_test_gtk_main, NULL ); ttt_functional_test_dumb_player_b(); 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中国 荣誉推出