用 C 语言对 Gtk+ 应用进行功能测试
这个简单教程教你如何测试你应用的功能。自动化测试用来保证你程序的质量以及让它以预想的运行。单元测试只是检测你算法的某一部分,而并不注重各组件间的适应性。这就是为什么会有功能测试,它有时也称为集成测试。
功能测试简单地与你的用户界面进行交互,无论它是网站还是桌面应用。为了展示功能测试如何工作,我们以测试一个 Gtk+ 应用为例。为了简单起见,这个教程里,我们使用 Gtk+ 2.0 教程的示例。
基础设置
对于每一个功能测试,你通常需要定义一些全局变量,比如 “用户交互时延” 或者 “失败的超时时间”(也就是说,如果在指定的时间内一个事件没有发生,程序就要中断)。
<span class="com">#</span><span class="kwd">define</span><span class="pln"> TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION</span><span class="pun">(</span><span class="pln">f</span><span class="pun">)</span><span class="pun">((</span><span class="typ">TttFunctionalTestUtilIdleCondition</span><span class="pun">)(</span><span class="pln">f</span><span class="pun">))</span>
<span class="com">#</span><span class="kwd">define</span><span class="pln"> TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME </span><span class="pun">(</span><span class="lit">125000</span><span class="pun">)</span>
<span class="com">#</span><span class="kwd">define</span><span class="pln"> TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG </span><span class="pun">(</span><span class="lit">500000</span><span class="pun">)</span>
<span class="kwd">typedef</span><span class="pln"> gboolean </span><span class="pun">(*</span><span class="typ">TttFunctionalTestUtilIdleCondition</span><span class="pun">)(</span><span class="pln">gpointer data</span><span class="pun">);</span>
<span class="kwd">struct</span><span class="pln"> timespec ttt_functional_test_util_default_timeout </span><span class="pun">=</span><span class="pun">{</span>
<span class="pln"> </span><span class="lit">20</span><span class="pun">,</span>
<span class="pln"> </span><span class="lit">0</span><span class="pun">,</span>
<span class="pun">};</span>
现在我们可以实现我们自己的超时函数。这里,为了能够得到期望的延迟,我们采用 usleep
函数。
<span class="kwd">void</span>
<span class="pln">ttt_functional_test_util_reaction_time</span><span class="pun">()</span>
<span class="pun">{</span>
<span class="pln"> usleep</span><span class="pun">(</span><span class="pln">TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME</span><span class="pun">);</span>
<span class="pun">}</span>
<span class="kwd">void</span>
<span class="pln">ttt_functional_test_util_reaction_time_long</span><span class="pun">()</span>
<span class="pun">{</span>
<span class="pln"> usleep</span><span class="pun">(</span><span class="pln">TTT_FUNCTIONAL_TEST_UTIL_REACTION_TIME_LONG</span><span class="pun">);</span>
<span class="pun">}</span>
直到获得控制状态,超时函数才会推迟执行。这对于一个异步执行的动作很有帮助,这也是为什么采用这么长的时延。
<span class="kwd">void</span>
<span class="pln">ttt_functional_test_util_idle_condition_and_timeout</span><span class="pun">(</span>
<span class="pln"> </span><span class="typ">TttFunctionalTestUtilIdleCondition</span><span class="pln"> idle_condition</span><span class="pun">,</span>
<span class="pln"> </span><span class="kwd">struct</span><span class="pln"> timespec </span><span class="pun">*</span><span class="kwd">timeout</span><span class="pun">,</span>
<span class="pln"> pointer data</span><span class="pun">)</span>
<span class="pun">{</span>
<span class="pln"> </span><span class="kwd">struct</span><span class="pln"> timespec start_time</span><span class="pun">,</span><span class="pln"> current_time</span><span class="pun">;</span>
<span class="pln"> clock_gettime</span><span class="pun">(</span><span class="pln">CLOCK_MONOTONIC</span><span class="pun">,</span>
<span class="pln"> </span><span class="pun">&</span><span class="pln">start_time</span><span class="pun">);</span>
<span class="pln"> </span><span class="kwd">while</span><span class="pun">(</span><span class="pln">TTT_FUNCTIONAL_TEST_UTIL_IDLE_CONDITION</span><span class="pun">(</span><span class="pln">idle_condition</span><span class="pun">)(</span><span class="pln">data</span><span class="pun">)){</span>
<span class="pln"> ttt_functional_test_util_reaction_time</span><span class="pun">();</span>
<span class="pln"> clock_gettime</span><span class="pun">(</span><span class="pln">CLOCK_MONOTONIC</span><span class="pun">,</span>
<span class="pln"> </span><span class="pun">&</span><span class="pln">current_time</span><span class="pun">);</span>
<span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">start_time</span><span class="pun">.</span><span class="pln">tv_sec </span><span class="pun">+</span><span class="kwd">timeout</span><span class="pun">-></span><span class="pln">tv_sec </span><span class="pun"><</span><span class="pln"> current_time</span><span class="pun">.</span><span class="pln">tv_sec</span><span class="pun">){</span>
<span class="pln"> </span><span class="kwd">break</span><span class="pun">;</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pln"> ttt_functional_test_util_reaction_time</span><span class="pun">();</span>
<span class="pun">}</span>
与图形化用户界面交互
为了模拟用户交互的操作, Gdk 库 为我们提供了一些需要的函数。要完成我们的工作,我们只需要如下 3 个函数:
gdk_display_warp_pointer()
gdk_test_simulate_button()
gdk_test_simulate_key()
举个例子,为了测试按钮点击,我们可以这么做:
<span class="pln">gboolean</span>
<span class="pln">ttt_functional_test_util_button_click</span><span class="pun">(</span><span class="typ">GtkButton</span><span class="pun">*</span><span class="pln">button</span><span class="pun">)</span>
<span class="pun">{</span>
<span class="pln"> </span><span class="typ">GtkWidget</span><span class="pun">*</span><span class="pln">widget</span><span class="pun">;</span>
<span class="pln"> </span><span class="typ">GdkWindow</span><span class="pun">*</span><span class="pln">window</span><span class="pun">;</span>
<span class="pln"> gint x</span><span class="pun">,</span><span class="pln"> y</span><span class="pun">;</span>
<span class="pln"> gint origin_x</span><span class="pun">,</span><span class="pln"> origin_y</span><span class="pun">;</span>
<span class="pln"> </span><span class="kwd">if</span><span class="pun">(</span><span class="pln">button </span><span class="pun">==</span><span class="pln"> NULL </span><span class="pun">||</span>
<span class="pln"> </span><span class="pun">!</span><span class="pln">GTK_IS_BUTTON</span><span class="pun">(</span><span class="pln">button</span><span class="pun">)){</span>
<span class="pln"> </span><span class="kwd">return</span><span class="pun">(</span><span class="pln">FALSE</span><span class="pun">);</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pln"> widget </span><span class="pun">=</span><span class="pln"> button</span><span class="pun">;</span>
<span class="pln"> </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">GTK_WIDGET_REALIZED</span><span class="pun">(</span><span class="pln">widget</span><span class="pun">)){</span>
<span class="pln"> ttt_functional_test_util_reaction_time_long</span><span class="pun">();</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pln"> </span><span class="com">/* retrieve window and pointer position */</span>
<span class="pln"> gdk_threads_enter</span><span class="pun">();</span>
<span class="pln"> window </span><span class="pun">=</span><span class="pln"> gtk_widget_get_window</span><span class="pun">(</span><span class="pln">widget</span><span class="pun">);</span>
<span class="pln"> x </span><span class="pun">=</span><span class="pln"> widget</span><span class="pun">-></span><span class="pln">allocation</span><span class="pun">.</span><span class="pln">x </span><span class="pun">+</span><span class="pln"> widget</span><span class="pun">-></span><span class="pln">allocation</span><span class="pun">.</span><span class="pln">width </span><span class="pun">/</span><span class="lit">2.0</span><span class="pun">;</span>
<span class="pln"> y </span><span class="pun">=</span><span class="pln"> widget</span><span class="pun">-></span><span class="pln">allocation</span><span class="pun">.</span><span class="pln">y </span><span class="pun">+</span><span class="pln"> widget</span><span class="pun">-></span><span class="pln">allocation</span><span class="pun">.</span><span class="pln">height </span><span class="pun">/</span><span class="lit">2.0</span><span class="pun">;</span>
<span class="pln"> gdk_window_get_origin</span><span class="pun">(</span><span class="pln">window</span><span class="pun">,</span><span class="pun">&</span><span class="pln">origin_x</span><span class="pun">,</span><span class="pun">&</span><span class="pln">origin_y</span><span class="pun">);</span>
<span class="pln"> gdk_display_warp_pointer</span><span class="pun">(</span><span class="pln">gtk_widget_get_display</span><span class="pun">(</span><span class="pln">widget</span><span class="pun">),</span>
<span class="pln"> gtk_widget_get_screen</span><span class="pun">(</span><span class="pln">widget</span><span class="pun">),</span>
<span class="pln"> origin_x </span><span class="pun">+</span><span class="pln"> x</span><span class="pun">,</span><span class="pln"> origin_y </span><span class="pun">+</span><span class="pln"> y</span><span class="pun">);</span>
<span class="pln"> gdk_threads_leave</span><span class="pun">();</span>
<span class="pln"> </span><span class="com">/* click the button */</span>
<span class="pln"> ttt_functional_test_util_reaction_time</span><span class="pun">();</span>
<span class="pln"> gdk_test_simulate_button</span><span class="pun">(</span><span class="pln">window</span><span class="pun">,</span>
<span class="pln"> x</span><span class="pun">,</span>
<span class="pln"> y</span><span class="pun">,</span>
<span class="pln"> </span><span class="lit">1</span><span class="pun">,</span>
<span class="pln"> GDK_BUTTON1_MASK</span><span class="pun">,</span>
<span class="pln"> GDK_BUTTON_PRESS</span><span class="pun">);</span>
<span class="pln"> ttt_functional_test_util_reaction_time</span><span class="pun">();</span>
<span class="pln"> gdk_test_simulate_button</span><span class="pun">(</span><span class="pln">window</span><span class="pun">,</span>
<span class="pln"> x</span><span class="pun">,</span>
<span class="pln"> y</span><span class="pun">,</span>
<span class="pln"> </span><span class="lit">1</span><span class="pun">,</span>
<span class="pln"> GDK_BUTTON1_MASK</span><span class="pun">,</span>
<span class="pln"> GDK_BUTTON_RELEASE</span><span class="pun">);</span>
<span class="pln"> ttt_functional_test_util_reaction_time</span><span class="pun">();</span>
<span class="pln"> ttt_functional_test_util_reaction_time_long</span><span class="pun">();</span>
<span class="pln"> </span><span class="kwd">return</span><span class="pun">(</span><span class="pln">TRUE</span><span class="pun">);</span>
<span class="pun">}</span>
我们想要保证按钮处于激活状态,因此我们提供一个空闲条件函数:
<span class="pln">gboolean</span>
<span class="pln">ttt_functional_test_util_idle_test_toggle_active</span><span class="pun">(</span>
<span class="pln"> </span><span class="typ">GtkToggleButton</span><span class="pun">**</span><span class="pln">toggle_button</span><span class="pun">)</span>
<span class="pun">{</span>
<span class="pln"> gboolean do_idle</span><span class="pun">;</span>
<span class="pln"> do_idle </span><span class="pun">=</span><span class="pln"> TRUE</span><span class="pun">;</span>
<span class="pln"> gdk_threads_enter</span><span class="pun">();</span>
<span class="pln"> </span><span class="kwd">if</span><span class="pun">(*</span><span class="pln">toggle_button </span><span class="pun">!=</span><span class="pln"> NULL </span><span class="pun">&&</span>
<span class="pln"> GTK_IS_TOGGLE_BUTTON</span><span class="pun">(*</span><span class="pln">toggle_button</span><span class="pun">)</span><span class="pun">&&</span>
<span class="pln"> gtk_toggle_button_get_active</span><span class="pun">(*</span><span class="pln">toggle_button</span><span class="pun">)){</span>
<span class="pln"> do_idle </span><span class="pun">=</span><span class="pln"> FALSE</span><span class="pun">;</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pln"> gdk_threads_leave</span><span class="pun">();</span>
<span class="pln"> </span><span class="kwd">return</span><span class="pun">(</span><span class="pln">do_idle</span><span class="pun">);</span>
<span class="pun">}</span>
测试场景
因为这个 Tictactoe 程序非常简单,我们只需要确保点击了一个 GtkToggleButton 按钮即可。一旦该按钮肯定进入了激活状态,功能测试就可以执行。为了点击按钮,我们使用上面提到的很方便的 util
函数。
如图所示,我们假设,填满第一行,玩家 A 就赢,因为玩家 B 没有注意,只填充了第二行。
<span class="typ">GtkWindow</span><span class="pun">*</span><span class="pln">window</span><span class="pun">;</span>
<span class="typ">Tictactoe</span><span class="pun">*</span><span class="pln">ttt</span><span class="pun">;</span>
<span class="kwd">void</span><span class="pun">*</span>
<span class="pln">ttt_functional_test_gtk_main</span><span class="pun">(</span><span class="kwd">void</span><span class="pun">*)</span>
<span class="pun">{</span>
<span class="pln"> gtk_main</span><span class="pun">();</span>
<span class="pln"> pthread_exit</span><span class="pun">(</span><span class="pln">NULL</span><span class="pun">);</span>
<span class="pun">}</span>
<span class="kwd">void</span>
<span class="pln">ttt_functional_test_dumb_player_b</span><span class="pun">()</span>
<span class="pun">{</span>
<span class="pln"> </span><span class="typ">GtkButton</span><span class="pun">*</span><span class="pln">buttons</span><span class="pun">[</span><span class="lit">3</span><span class="pun">][</span><span class="lit">3</span><span class="pun">];</span>
<span class="pln"> guint i</span><span class="pun">;</span>
<span class="pln"> </span><span class="com">/* to avoid race-conditions copy the buttons */</span>
<span class="pln"> gdk_threads_enter</span><span class="pun">();</span>
<span class="pln"> memcpy</span><span class="pun">(</span><span class="pln">buttons</span><span class="pun">,</span><span class="pln"> ttt</span><span class="pun">-></span><span class="pln">buttons</span><span class="pun">,</span><span class="lit">9</span><span class="pun">*</span><span class="kwd">sizeof</span><span class="pun">(</span><span class="typ">GtkButton</span><span class="pun">*));</span>
<span class="pln"> gdk_threads_leave</span><span class="pun">();</span>
<span class="pln"> </span><span class="com">/* TEST 1 - the dumb player B */</span>
<span class="pln"> </span><span class="kwd">for</span><span class="pun">(</span><span class="pln">i </span><span class="pun">=</span><span class="lit">0</span><span class="pun">;</span><span class="pln"> i </span><span class="pun"><</span><span class="lit">3</span><span class="pun">;</span><span class="pln"> i</span><span class="pun">++){</span>
<span class="pln"> </span><span class="com">/* assert player A clicks the button successfully */</span>
<span class="pln"> </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">ttt_functional_test_util_button_click</span><span class="pun">(</span><span class="pln">buttons</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="pln">i</span><span class="pun">])){</span>
<span class="pln"> </span><span class="kwd">exit</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">);</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pln"> functional_test_util_idle_condition_and_timeout</span><span class="pun">(</span>
<span class="pln"> ttt_functional_test_util_idle_test_toggle_active</span><span class="pun">,</span>
<span class="pln"> ttt_functional_test_util_default_timeout</span><span class="pun">,</span>
<span class="pln"> </span><span class="pun">&</span><span class="pln">buttons</span><span class="pun">[</span><span class="lit">0</span><span class="pun">][</span><span class="pln">i</span><span class="pun">]);</span>
<span class="pln"> </span><span class="com">/* assert player B clicks the button successfully */</span>
<span class="pln"> </span><span class="kwd">if</span><span class="pun">(!</span><span class="pln">ttt_functional_test_util_button_click</span><span class="pun">(</span><span class="pln">buttons</span><span class="pun">[</span><span class="lit">1</span><span class="pun">][</span><span class="pln">i</span><span class="pun">])){</span>
<span class="pln"> </span><span class="kwd">exit</span><span class="pun">(-</span><span class="lit">1</span><span class="pun">);</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pln"> functional_test_util_idle_condition_and_timeout</span><span class="pun">(</span>
<span class="pln"> ttt_functional_test_util_idle_test_toggle_active</span><span class="pun">,</span>
<span class="pln"> ttt_functional_test_util_default_timeout</span><span class="pun">,</span>
<span class="pln"> </span><span class="pun">&</span><span class="pln">buttons</span><span class="pun">[</span><span class="lit">1</span><span class="pun">][</span><span class="pln">i</span><span class="pun">]);</span>
<span class="pln"> </span><span class="pun">}</span>
<span class="pun">}</span>
<span class="kwd">int</span>
<span class="pln">main</span><span class="pun">(</span><span class="kwd">int</span><span class="pln"> argc</span><span class="pun">,</span><span class="kwd">char</span><span class="pun">**</span><span class="pln">argv</span><span class="pun">)</span>
<span class="pun">{</span>
<span class="pln"> </span><span class="typ">pthread_t</span><span class="pln"> thread</span><span class="pun">;</span>
<span class="pln"> gtk_init</span><span class="pun">(&</span><span class="pln">argc</span><span class="pun">,</span><span class="pun">&</span><span class="pln">argv</span><span class="pun">);</span>
<span class="pln"> </span><span class="com">/* start the tictactoe application */</span>
<span class="pln"> window </span><span class="pun">=</span><span class="pln"> gtk_window_new</span><span class="pun">(</span><span class="pln">GTK_WINDOW_TOPLEVEL</span><span class="pun">);</span>
<span class="pln"> ttt </span><span class="pun">=</span><span class="pln"> tictactoe_new</span><span class="pun">();</span>
<span class="pln"> gtk_container_add</span><span class="pun">(</span><span class="pln">window</span><span class="pun">,</span><span class="pln"> ttt</span><span class="pun">);</span>
<span class="pln"> gtk_widget_show_all</span><span class="pun">(</span><span class="pln">window</span><span class="pun">);</span>
<span class="pln"> </span><span class="com">/* start the Gtk+ dispatcher */</span>
<span class="pln"> pthread_create</span><span class="pun">(&</span><span class="pln">thread</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">,</span>
<span class="pln"> ttt_functional_test_gtk_main</span><span class="pun">,</span><span class="pln"> NULL</span><span class="pun">);</span>
<span class="pln"> </span><span class="com">/* launch test routines */</span>
<span class="pln"> ttt_functional_test_dumb_player_b</span><span class="pun">();</span>
<span class="pln"> </span><span class="com">/* terminate the application */</span>
<span class="pln"> gdk_threads_enter</span><span class="pun">();</span>
<span class="pln"> gtk_main_quit</span><span class="pun">();</span>
<span class="pln"> gdk_threads_leave</span><span class="pun">();</span>
<span class="pln"> </span><span class="kwd">return</span><span class="pun">(</span><span class="lit">0</span><span class="pun">);</span>
<span class="pun">}</span>
(题图:opensource.com)
作者简介:
Joël Krähemann - 精通 C 语言编程的自由软件爱好者。不管代码多复杂,它也是一点点写成的。作为高级的 Gtk+ 程序开发者,我知道多线程编程有多大的挑战性,有了多线程编程,我们就有了未来需求的良好基础。
摘自: https://opensource.com/article/17/7/functional-testing
作者:Joël Krähemann 译者:sugarfillet 校对:wxy