标签 测试 下的文章

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

 title=

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

功能测试简单地与你的用户界面进行交互,无论它是网站还是桌面应用。为了展示功能测试如何工作,我们以测试一个 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中国 荣誉推出

我们 MySQL 数据库基础架构是 Github 关键组件。 MySQL 提供 Github.com、 GitHub 的 API 和验证等等的服务。每一次的 git 请求都以某种方式触及 MySQL。我们的任务是保持数据的可用性,并保持其完整性。即使我们 MySQL 集群是按流量分配的,但是我们还是需要执行深度清理、即时更新、在线 模式 schema 迁移、集群拓扑重构、 连接池化 pooling 和负载平衡等任务。 我们建有基础架构来自动化测试这些操作,在这篇文章中,我们将分享几个例子,来说明我们是如何通过持续测试打造我们的基础架构的。这是让我们一梦到天亮的根本保障。

备份

没有比备份数据更重要的了,如果您没有备份数据库,在它出事前这可能并不是什么问题。Percona 的 Xtrabackup 是我们一直用来完整备份 MySQL 数据库的工具。如果有专门需要备份的数据,我们就会备份到另一个专门备份数据的服务器上。

除了完整的二进制备份外,我们每天还会多次运行逻辑备份。这些备份数据可以让我们的工程师获取到最新的数据副本。有时候,他们希望从表中获取一整套数据,以便他们可以在一个生产级规模的表上测试索引的修改,或查看特定时间以来的数据。Hubot 可以让我们恢复备份的表,并且当表准备好使用时会通知我们。

tomkrouper

.mysql backup-list locations

Hubot

+-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
| Backup ID | Table Name | Donor Host    | Backup Start        | Backup End          | File Name                                    |
+-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+
|   1699494 | locations  | db-mysql-0903 | 2017-07-01 22:09:17 | 2017-07-01 22:09:17 | backup-mycluster-locations-1498593122.sql.gz |
|   1699133 | locations  | db-mysql-0903 | 2017-07-01 16:11:37 | 2017-07-01 16:11:39 | backup-mycluster-locations-1498571521.sql.gz |
|   1698772 | locations  | db-mysql-0903 | 2017-07-01 10:09:21 | 2017-07-01 10:09:22 | backup-mycluster-locations-1498549921.sql.gz |
|   1698411 | locations  | db-mysql-0903 | 2017-07-01 04:12:32 | 2017-07-01 04:12:32 | backup-mycluster-locations-1498528321.sql.gz |
|   1698050 | locations  | db-mysql-0903 | 2017-06-30 22:18:23 | 2017-06-30 22:18:23 | backup-mycluster-locations-1498506721.sql.gz |
| ...
|   1262253 | locations  | db-mysql-0088 | 2016-08-01 01:58:51 | 2016-08-01 01:58:54 | backup-mycluster-locations-1470034801.sql.gz |
|   1064984 | locations  | db-mysql-0088 | 2016-04-04 13:07:40 | 2016-04-04 13:07:43 | backup-mycluster-locations-1459494001.sql.gz |
+-----------+------------+---------------+---------------------+---------------------+----------------------------------------------+

tomkrouper

.mysql restore 1699133

Hubot

A restore job has been created for the backup job 1699133. You will be notified in #database-ops when the restore is complete.

Hubot

@tomkrouper: the locations table has been restored as locations_2017_07_01_16_11 in the restores database on db-mysql-0482

数据被加载到非生产环境的数据库,该数据库可供请求该次恢复的工程师访问。

我们保留数据的“备份”的最后一个方法是使用 延迟副本 delayed replica 。这与其说是备份,不如说是保护。对于每个生产集群,我们有一个延迟 4 个小时复制的主机。如果运行了一个不该运行的请求,我们可以在 chatops 中运行 mysql panic 。这将导致我们所有的延迟副本立即停止复制。这也将给值班 DBA 发送消息。从而我们可以使用延迟副本来验证是否有问题,并快速前进到二进制日志的错误发生之前的位置。然后,我们可以将此数据恢复到主服务器,从而恢复数据到该时间点。

备份固然好,但如果发生了一些未知或未捕获的错误破坏它们,它们就没有价值了。让脚本恢复备份的好处是它允许我们通过 cron 自动执行备份验证。我们为每个集群设置了一个专用的主机,用于运行最新备份的恢复。这样可以确保备份运行正常,并且我们能够从备份中检索数据。

根据数据集大小,我们每天运行几次恢复。恢复的服务器被加入到复制工作流,并通过复制保持数据更新。这测试不仅让我们得到了可恢复的备份,而且也让我们得以正确地确定备份的时间点,并且可以从该时间点进一步应用更改。如果恢复过程中出现问题,我们会收到通知。

我们还追踪恢复所需的时间,所以我们知道在紧急情况下建立新的副本或还原需要多长时间。

以下是由 Hubot 在我们的机器人聊天室中输出的自动恢复过程。

Hubot

gh-mysql-backup-restore: db-mysql-0752: restore_log.id = 4447 
gh-mysql-backup-restore: db-mysql-0752: Determining backup to restore for cluster 'prodcluster'. 
gh-mysql-backup-restore: db-mysql-0752: Enabling maintenance mode 
gh-mysql-backup-restore: db-mysql-0752: Setting orchestrator downtime 
gh-mysql-backup-restore: db-mysql-0752: Disabling Puppet 
gh-mysql-backup-restore: db-mysql-0752: Stopping MySQL 
gh-mysql-backup-restore: db-mysql-0752: Removing MySQL files 
gh-mysql-backup-restore: db-mysql-0752: Running gh-xtrabackup-restore 
gh-mysql-backup-restore: db-mysql-0752: Restore file: xtrabackup-notify-2017-07-02_0000.xbstream 
gh-mysql-backup-restore: db-mysql-0752: Running gh-xtrabackup-prepare 
gh-mysql-backup-restore: db-mysql-0752: Starting MySQL 
gh-mysql-backup-restore: db-mysql-0752: Update file ownership 
gh-mysql-backup-restore: db-mysql-0752: Upgrade MySQL 
gh-mysql-backup-restore: db-mysql-0752: Stopping MySQL 
gh-mysql-backup-restore: db-mysql-0752: Starting MySQL 
gh-mysql-backup-restore: db-mysql-0752: Backup Host: db-mysql-0034 
gh-mysql-backup-restore: db-mysql-0752: Setting up replication 
gh-mysql-backup-restore: db-mysql-0752: Starting replication 
gh-mysql-backup-restore: db-mysql-0752: Replication catch-up 
gh-mysql-backup-restore: db-mysql-0752: Restore complete (replication running) 
gh-mysql-backup-restore: db-mysql-0752: Enabling Puppet 
gh-mysql-backup-restore: db-mysql-0752: Disabling maintenance mode 
gh-mysql-backup-restore: db-mysql-0752: Setting orchestrator downtime 
gh-mysql-backup-restore: db-mysql-0752: Restore process complete. 

还有一件我们可以使用备份做的事情是向一组现有的 MySQL 服务器添加一个新的副本。我们初始化一个新的服务器,一旦我们得知它准备就绪,我们就可以开始恢复该特定集群的最新备份。我们有一个脚本,可以运行所有需要手动执行的恢复命令。我们的自动恢复系统基本上使用的是相同的脚本。这简化了系统构建过程,并允许我们在一个启动运行的主机上使用几个对话命令(chatops)来替代一堆手动执行命令。下面显示的是在该对话(chatops)中手动执行的恢复:

jessbreckenridge

.mysql backup-restore -H db-mysql-0007 -o -r magic_word=daily_rotating_word

Hubot

@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Determining backup to restore for cluster 'mycluster'. 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: restore_log.id = 4449 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Enabling maintenance mode
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Setting orchestrator downtime 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Disabling Puppet 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Stopping MySQL 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Removing MySQL files 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Running gh-xtrabackup-restore 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Restore file: xtrabackup-mycluster-2017-07-02_0015.xbstream 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Running gh-xtrabackup-prepare 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Update file ownership 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting MySQL 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Upgrade MySQL 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Stopping MySQL 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting MySQL 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Setting up replication 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Starting replication 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Backup Host: db-mysql-0201 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Replication catch-up 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Replication behind by 4589 seconds, waiting 1800 seconds before next check. 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Restore complete (replication running) 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Enabling puppet 
@jessbreckenridge gh-mysql-backup-restore: db-mysql-0007: Disabling maintenance mode 

故障转移

我们使用协调器 来为 主服务器 master 中间服务器 intermediate master 执行自动化故障切换。我们期望 协调器 orchestrator 能够正确检测主服务器故障,指定一个副本进行晋升,在所指定的副本下修复拓扑,完成晋升。我们预期 VIP(虚拟 IP)、连接池可以相应地进行变化、客户端进行重连、puppet 在晋升后的主服务器上运行基本组件等等。故障转移是一项复杂的任务,涉及到我们基础架构的许多方面。

为了建立对我们的故障转移的信赖,我们建立了一个类生产环境的测试集群,并且我们不断地崩溃它来观察故障转移情况。

这个类生产环境的测试集群是一套复制环境,与我们的生产集群的各个方面都相同:硬件类型、操作系统、MySQL 版本、网络环境、VIP、puppet 配置、haproxy 设置 等。与生产集群唯一不同的是它不发送/接收生产流量。

我们在测试集群上模拟写入负载,同时避免复制滞后。写入负载不会太大,但是有一些有意地写入相同数据集的竞争请求。这在正常情况下并不是很有用,但是事实证明这在故障转移中是有用的,我们将会稍后简要描述它。

我们的测试集群有来自三个数据中心的典型的服务器。我们希望故障转移能够从同一个数据中心内晋升替代副本。我们希望在这样的限制下尽可能多地恢复副本。我们要求尽可能地实现这两者。协调器对拓扑结构没有 先验假定 prior assumption ;它必须依据崩溃时的状态作出反应。

然而,我们有兴趣创建各种复杂而多变的故障恢复场景。我们的故障转移测试脚本为故障转移提供了基础:

  • 它能够识别现有的主服务器
  • 它能够重构拓扑结构,来代表主服务器下的所有的三个数据中心。不同的数据中心具有不同的网络延迟,并且预期会在不同的时间对主机崩溃做出反应。
  • 能够选择崩溃方式。可以选择干掉主服务器(kill -9)或网络隔离(比较好的方式: iptables -j REJECT 或无响应的方式: iptables -j DROP)方式。

脚本通过选择的方法使主机崩溃,并等待协调器可靠地检测到崩溃然后执行故障转移。虽然我们期望检测和晋升在 30 秒钟内完成,但脚本会稍微放宽这一期望,并在查找故障转移结果之前休眠一段指定的时间。然后它将检查:

  • 一个新的(不同的)主服务器是否到位
  • 集群中有足够的副本
  • 主服务器是可写的
  • 对主服务器的写入在副本上可见
  • 内部服务发现项已更新(如预期般识别到新的主服务器;移除旧的主服务器)
  • 其他内部检查

这些测试可以证实故障转移是成功的,不仅是 MySQL 级别的,而是在更大的基础设施范围内成功的。VIP 被赋予;特定的服务已经启动;信息到达了应该去的地方。

该脚本进一步继续恢复那个失败的服务器:

  • 从备份恢复它,从而隐含地测试了我们的备份/恢复过程
  • 验证服务器配置是否符合预期(该服务器不再认为其是主服务器)
  • 将其加入到复制集群,期望找到在主服务器上写入的数据

看一下以下可视化的计划的故障转移测试:从运行良好的群集,到在某些副本上发现问题,诊断主服务器(7136)是否死机,选择一个服务器(a79d)来晋升,重构该服务器下的拓扑,晋升它(故障切换成功),恢复失败的(原)主服务器并将其放回群集。

automated master failover

测试失败怎么样?

我们的测试脚本使用了一种“停止世界”的方法。任何故障切换​​组件中的单个故障都将导致整个测试失败,因此在有人解决该问题之前,无法进行任何进一步的自动化测试。我们会得到警报,并检查状态和日志进行处理。

脚本将各种情况下失败,如不可接受的检测或故障转移时间;备份/还原出现问题;失去太多服务器;在故障切换后的意外配置等等。

我们需要确保协调器正确地连接服务器。这是竞争性写入负载有用的地方:如果设置不正确,复制很容易中断。我们会得到 DUPLICATE KEY 或其他错误提示出错。

这是特别重要的,因此我们改进协调器并引入新的行为,以允许我们在安全的环境中测试这些变化。

出现:混乱测试

上面所示的测试程序将捕获(并已经捕获)我们基础设施许多部分的问题。这些够了吗?

在生产环境中总是有其他的东西。有些特定测试方法不适用于我们的生产集群。它们不具有相同的流量和流量方式,也不具有完全相同的服务器集。故障类型可能有所不同。

我们正在为我们的生产集群设计混乱测试。 混乱测试将会在我们的生产中,但是按照预期的时间表和充分控制的方式来逐个破坏我们的部分生产环境。 混乱测试在恢复机制中引入更高层次的信赖,并影响(因此测试)我们的基础设施和应用程序的更大部分。

这是微妙的工作:当我们承认需要混乱测试时,我们也希望可以避免对我们的服务造成不必要的影响。不同的测试将在风险级别和影响方面有所不同,我们将努力确保我们的服务的可用性。

模式迁移

我们使用 gh-ost来运行实时 模式迁移 schema migration 。gh-ost 是稳定的,但也处于活跃开发中,重大新功能正在不断开发和计划中。

gh-ost 通过将数据复制到 ghost 表来迁移,将由二进制日志拦截的进一步更改应用到 ghost 表中,就如其正在写入原始表。然后它将 ghost 表交换代替原始表。迁移完成时,GitHub 继续使用由 gh-ost 生成和填充的表。

在这个时候,几乎所有的 GitHub 的 MySQL 数据都被 gh-ost 重新创建,其中大部分重新创建多次。我们必须高度信赖 gh-ost,让它一遍遍地操弄我们的数据,即使它还处于活跃开发中。下面是我们如何获得这种信赖的。

gh-ost 提供生产环境测试能力。它支持在副本上运行迁移,其方式与在主服务器上运行的方式大致相同: gh-ost 将连接到副本,并将其视为主服务器。它将采用与实际主机迁移相同的方式解析其二进制日志。但是,它将复制行并将二进制日志事件应用于副本,并避免对主服务器进行写入。

我们在生产环境中给 gh-ost 提供专用的副本。这些副本并不为生产环境提供服务。每个这样的副本将检索生产表的当前列表,并以随机顺序对其进行迭代。一个接一个地选择一个表并在该表上执行复制迁移。迁移实际上并不修改表结构,而是运行一个微不足道的 ENGINE=InnoDB 更改。该测试运行迁移时,如果表正在生产环境中使用,会复制实际的生产数据,并接受二进制日志之外的真实的生产环境流量。

这些迁移可以被审计。以下是我们如何从对话中(chatops)检查运行中的测试状态:

ggunson

.migration test-status

Hubot

# Migrating `prod`.`pull_requests`; Ghost table is `prod`.`_pull_requests_gho`
# Migrating ghost-db-mysql-0007:3306; inspecting ghost-db-mysql-0007:3306; executing on ghost-db-mysql-0007
# Migration started at Mon Jan 30 02:13:39 -0800 2017
# chunk-size: 2500; max-lag-millis: 1500ms; max-load: Threads_running=30; critical-load: Threads_running=1000; nice-ratio: 0.000000
# throttle-additional-flag-file: /tmp/gh-ost.throttle
# panic-flag-file: /tmp/ghost-test-panic.flag
# Serving on unix socket: /tmp/gh-ost.test.sock
Copy: 57992500/86684838 66.9%; Applied: 57708; Backlog: 1/100; Time: 3h28m38s(total), 3h28m36s(copy); streamer: mysql-bin.000576:142993938; State: migrating; ETA: 1h43m12s

当测试迁移完成表数据的复制时,它将停止复制并执行切换,使用 ghost 表替换原始表,然后交换回来。我们对实际替换数据并不感兴趣。相反,我们将留下原始的表和 ghost 表,它们应该是相同的。我们通过校验两个表的整个表数据来验证。

测试能以下列方式完成:

  • 成功 :一切顺利,校验和相同。我们期待看到这一结果。
  • 失败 :执行问题。这可能偶尔发生,因为迁移进程被杀死、复制问题等,并且通常与 gh-ost 自身无关。
  • 校验失败 :表数据不一致。对于被测试的分支,这个需要修复。对于正在进行的 master 分支测试,这意味着立即阻止生产迁移。我们不会遇到后者。

测试结果经过审核,发送到机器人聊天室,作为事件发送到我们的度量系统。下图中的每条垂直线代表成功的迁移测试:

automated master failover

这些测试不断运行。如果发生故障,我们会收到通知。当然,我们可以随时访问机器人聊天室(chatops),了解发生了什么。

测试新版本

我们不断改进 gh-ost。我们的开发流程基于 git 分支,然后我们通过拉取请求(PR)来提供合并。

提交的 gh-ost 拉取请求(PR)通过持续集成(CI)进行基本的编译和单元测试。一旦通过,该 PR 在技术上就有资格合并,但更好的是它有资格通过 Heaven 进行部署。作为我们基础架构中的敏感组件,在其进入 master 分支前,我们会小心部署分支进行密集测试。

shlomi-noach

.deploy gh-ost/fix-reappearing-throttled-reasons to prod/ghost-db-mysql-0007

Hubot

@shlomi-noach is deploying gh-ost/fix-reappearing-throttled-reasons (baee4f6) to production (ghost-db-mysql-0007). 
@shlomi-noach's production deployment of gh-ost/fix-reappearing-throttled-reasons (baee4f6) is done! (2s) 
@shlomi-noach, make sure you watch for exceptions in haystack

jonahberquist

.deploy gh-ost/interactive-command-question to prod/ghost-db-mysql-0012

Hubot

@jonahberquist is deploying gh-ost/interactive-command-question (be1ab17) to production (ghost-db-mysql-0012). 
@jonahberquist's production deployment of gh-ost/interactive-command-question (be1ab17) is done! (2s) 
@jonahberquist, make sure you watch for exceptions in haystack

shlomi-noach

.wcid gh-ost

Hubot

shlomi-noach testing fix-reappearing-throttled-reasons 41 seconds ago: ghost-db-mysql-0007 
jonahberquist testing interactive-command-question 7 seconds ago: ghost-db-mysql-0012 

Nobody is in the queue.

一些 PR 很小,不影响数据本身。对状态消息,交互式命令等的更改对 gh-ost 应用程序的影响较小。而其他的 PR 对迁移逻辑和操作会造成重大变化,我们将严格测试这些,通过我们的生产表车队运行这些,直到其满足了这些改变不会造成数据损坏威胁的程度。

总结

在整个测试过程中,我们建立对我们的系统的信赖。通过自动化这些测试,在生产环境中,我们得到了一切都按预期工作的反复确认。随着我们继续发展我们的基础设施,我们还通过调整测试来覆盖最新的变化。

产品总会有令你意想不到的未被测试覆盖的场景。我们对生产环境的测试越多,我们对应用程序的期望越多,基础设施的能力就越强。


via: https://githubengineering.com/mysql-testing-automation-at-github/

作者:tomkrouperShlomi Noach 译者:MonkeyDEcho 校对:wxy

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

测试管理 Test Management 指测试人员所需要的任何的所有东西。测试管理工具用来记录测试执行的结果、计划测试活动以及汇报质量控制活动的情况。在这篇文章中我们会向你介绍如何配置 Zephyr 测试管理工具,它包括了管理测试活动需要的所有东西,不需要单独安装测试活动所需要的应用程序从而降低测试人员不必要的麻烦。一旦你安装完它,你就看可以用它跟踪 bug 和缺陷,和你的团队成员协作项目任务,因为你可以轻松地共享和访问测试过程中多个项目团队的数据。

Zephyr 要求

安装和运行 Zephyr 要求满足以下最低条件。可以根据你的基础设施提高资源。我们会在 64 位 CentOS-7 系统上安装 Zephyr,几乎在所有的 Linux 操作系统中都有可用的 Zephyr 二进制发行版。

注:表格

Zephyr test management tool
Linux OSCentOS Linux 7 (Core), 64-bit
PackagesJDK 7 或更高 , Oracle JDK 6 update没有事先安装的 Tomcat 和 MySQL
RAM4 GB推荐 8 GB
CPU2.0 GHZ 或更高
Hard Disk30 GB , 至少 5GB

安装 Zephyr 要求你有超级用户(root)权限,并确保你已经正确配置了网络的静态 IP ,默认端口必须可用并允许通过防火墙。其中 tomcat 会使用 80/443、 8005、 8009、 8010 端口, Zephyr 内部使用 RTMP 协议的 flex 会使用 443 和 2099 号端口。

安装 Java JDK 7

安装 Zephyr 首先需要安装 Java JDK 7,如果你的系统上还没有安装,可以按照下面的方法安装 Java 并设置 JAVA\_HOME 环境变量。

输入以下的命令安装 Java JDK 7。

[root@centos-007 ~]# yum install java-1.7.0-openjdk-1.7.0.79-2.5.5.2.el7_1

[root@centos-007 ~]# yum install java-1.7.0-openjdk-devel-1.7.0.85-2.6.1.2.el7_1.x86_64

安装完 java 和它的所有依赖后,运行下面的命令设置 JAVA\_HOME 环境变量。

[root@centos-007 ~]# export JAVA_HOME=/usr/java/default
[root@centos-007 ~]# export PATH=/usr/java/default/bin:$PATH

用下面的命令检查 java 版本以验证安装。

[root@centos-007 ~]# java –version

java version "1.7.0_79"
OpenJDK Runtime Environment (rhel-2.5.5.2.el7_1-x86_64 u79-b14)
OpenJDK 64-Bit Server VM (build 24.79-b02, mixed mode)

输出显示我们已经正确安装了 1.7.0\_79 版本的 OpenJDK Java。

安装 MySQL 5.6.x

如果的机器上有其它的 MySQL,建议你先卸载它们并安装这个版本,或者升级它们的 模式 schemas 到指定的版本。因为 Zephyr 前提要求这个指定的 5.6.x 版本的 MySQL ,要有 root 用户名。

可以按照下面的步骤在 CentOS-7.1 上安装 MySQL 5.6 :

下载 rpm 软件包,它会为安装 MySQL 服务器创建一个 yum 库文件。

[root@centos-007 ~]# yum install wget
[root@centos-007 ~]# wget http://repo.mysql.com/mysql-community-release-el7-5.noarch.rpm

然后用 rpm 命令安装下载下来的 rpm 软件包。

[root@centos-007 ~]# rpm -ivh mysql-community-release-el7-5.noarch.rpm

安装完这个软件包后你会有两个和 MySQL 相关的新的 yum 库。然后使用 yum 命令安装 MySQL Server 5.6,它会自动安装所有需要的依赖。

[root@centos-007 ~]# yum install mysql-server

安装过程完成之后,运行下面的命令启动 mysqld 服务并检查它的状态是否激活。

[root@centos-007 ~]# service mysqld start
[root@centos-007 ~]# service mysqld status

对于全新安装的 MySQL 服务器,MySQL root 用户的密码为空。为了安全起见,我们应该重置 MySQL root 用户的密码。用自动生成的空密码连接到 MySQL 并更改 root 用户密码。

[root@centos-007 ~]# mysql
mysql> SET PASSWORD FOR 'root'@'localhost' = PASSWORD('your_password');
mysql> flush privileges;
mysql> quit;

现在我们需要在 MySQL 默认的配置文件中配置所需的数据库参数。打开 "/etc/" 目录中的文件并按照下面那样更新。

[root@centos-007 ~]# vi /etc/my.cnf

[mysqld]
datadir=/var/lib/mysql
socket=/var/lib/mysql/mysql.sock
symbolic-links=0

sql_mode=NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES
max_allowed_packet=150M
max_connections=600
default-storage-engine=INNODB
character-set-server=utf8
collation-server=utf8_unicode_ci

[mysqld_safe]
log-error=/var/log/mysqld.log
pid-file=/var/run/mysqld/mysqld.pid
default-storage-engine=INNODB
character-set-server=utf8
collation-server=utf8_unicode_ci

[mysql]
max_allowed_packet = 150M
[mysqldump]
quick

保存配置文件中的更新并重启 mysql 服务。

[root@centos-007 ~]# service mysqld restart

下载 Zephyr 安装包

我们已经安装完了安装 Zephyr 所需要的软件包。现在我们需要获取 Zephyr 二进制发布包和它的许可证密钥。到 Zephyr 官方下载链接 http://download.yourzephyr.com/linux/download.php 输入你的电子邮件 ID 并点击下载。

下载 Zephyr

然后确认你的电子邮件地址,你会获得 Zephyr 下载链接和它的许可证密钥链接。点击提供的链接从服务器中选择和你操作系统合适的版本下载二进制安装包以及许可证文件。

我们把它下载到 home 目录并更改它的权限为可执行。

Zephyr 二进制包

开始安装和配置 Zephyr

现在我们通过执行它的二进制安装脚本开始安装 Zephyr。

[root@centos-007 ~]# ./zephyr_4_7_9213_linux_setup.sh –c

一旦你运行了上面的命令,它会检查是否正确配置了 Java 环境变量。如果配置不正确,你可能会看到类似下面的错误。

testing JVM in /usr ...
Starting Installer ...
Error : Either JDK is not found at expected locations or JDK version is mismatched.
Zephyr requires Oracle Java Development Kit (JDK) version 1.7 or higher.

如果你正确配置了 Java,它会开始安装 Zephyr 并要求你输入 “o” 继续或者输入 “c” 取消安装。让我们敲击 “o” 并输入回车键开始安装。

安装 zephyr

下一个选项是检查安装 Zephyr 所有的要求,输入回车进入下一个选项。

zephyr 要求

输入 “1” 并回车同意许可证协议。

I accept the terms of this license agreement [1], I do not accept the terms of this license agreement [2, Enter]

我们需要选择安装 Zephyr 合适的目标位置以及默认端口,如果你想用默认端口之外的其它端口,也可以在这里配置。

installation folder

然后自定义 mysql 数据库参数并给出配置文件的正确路径。在这一步你可能看到类似下面的错误。

Please update MySQL configuration. Configuration parameter max_connection should be at least 500 (max_connection = 500) and max_allowed_packet should be at least 50MB (max_allowed_packet = 50M).

要消除这个错误,你要确保在 mysql 配置文件中正确配置了 "max\_connection" 和 "max\_allowed\_packet" 参数。运行所示的命令连接到数据库确认这些设置。

连接 mysql

当你正确配置了 mysql 数据库,它会提取配置文件并完成安装。

配置 mysql

安装过程在你的计算机上成功的安装了 Zephyr 4.7。要启动 Zephyr 桌面,输入 “y” 完成 Zephyr 安装。

启动 zephyr

启动 Zephyr 桌面

打开你的 web 浏览器并用你的本机 IP 地址启动 Zephyr 桌面,你会被导向 Zephyr 桌面。

http://your_server_IP/zephyr/desktop/

Zephyr 桌面

从 Zephyr 仪表盘点击 "Test Manager" 并用默认的用户名和密码 "test.manager" 登录。

Test Manage 登录

你登录进去后你就可以配置你的管理设置了。根据你的环境选择你想要的设置。

Test Manage 管理

完成管理设置后保存设置,资源管理和项目配置也类似,然后开始使用 Zephyr 作为你的测试管理工具吧。如图所示在 Department Dashboard Management 中检查和编辑管理设置状态。

zephyr 仪表盘

总结

好了! 我们已经在 CentOS 7.1 上安装完了 Zephyr。我们希望你能更加深入了解 Zephyr 测试管理工具,它提供简化测试流程、允许快速访问数据分析、协作工具以及多个项目成员之间交流。如果在你的环境中遇到任何问题,欢迎和我们联系。


via: http://linoxide.com/linux-how-to/setup-zephyr-tool-centos-7-x/

作者:Kashif Siddique 译者:ictlyh 校对:wxy

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

FreeBSD 10.0 Beta 1, 一个可以运行在x86, ARM, IA-64, PowerPC, PC-98, and UltraSPARC 等架构上的操作系统, 已经发布并且可供下载测试。

FreeBSD的开发者在以不可思议的速度前进,并且不断地发布一个又一个的新版本. 新的测试版在有五个内测版的情况下完成,但是没有任何问题。

官方公告称,“因为在最后一刻发现10.0-BETA1 freebsd-update(8) 中存在问题,freebsd-update(8) 不支持10.0-BETA1的升级。所以请不要用freebsd-update(8) 来升级 10.0-BETA1。请注意 cvsup和CVS不支持用于的src/tree方式的升级。”

而且, 据开发者说, ports.txz发行版没有被包含在 10.0 Beta 1 发布版中,但是它有望在发行周期中被纳入后续版本的disc1.iso中。

FreeBSD 10.0 Beta 1的亮点:

  • freebsd-version,一个用于审核的工具,已经完成。如果你想确定客户端补丁级别,这是一个很重要的工具,它与'uname -r'的报告是不同的;
  • ZFS lzjb的解压性能有所改进;
  • 支持了两种新的MIPS CPU:mips24k和mips74k;
  • 每个jail配置的"jail*" rc.conf(5) 变量的配置被自动转换到/var/run/jail。.conf在jail(8)之前调用, 因此采用了新的jail.conf(5)语法;
  • 绝大多数的ATF工具和\_atf用户被移除;

发行方鼓励用户们测试发行版并报告任何发现的问题。官方变更目录有完整的修正和修改列表。现在可以在Softpedia立刻下载FreeBSD 10.0 Beta 1。

请注意这是一个开发者版本请不要再任何产品端上安装。它仅被希望用于测试目的。

via: http://news.softpedia.com/news/FreeBSD-10-0-Beta-1-Available-for-Download-and-Testing-391246.shtml

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

译者:crowner 校对:wxy