原文:Simpletest Testing tutorial (Drupal 7)
本指南中的代码在 Examples 模块中维护。这意味着:
-
你可以获得一份拷贝,进行修改和试验。
-
如果发现了问题,可以提出 issue,并获得修正。当然,尤其欢迎给出补丁或增强功能的帮助。
这个指南能够帮你了解 Drupal 测试的基础知识,随后你应该有能力编写自己的第一个测试。在本例中,我们会创建一个叫做 simpletest_example 的测试模块。这个模块提供一种名为 simpletest_example 的内容类型。这个内容类型跟基础的 Drupal Node 是一致的。接下来会解释如何通过测试来证明 simpletest_example 这一内容类型能够正常工作。
准备工作
首先,我们要确认已经安装了 Simpletest 模块。Drupal 7 中,Simpletest 随核心发布,命名为 Testing。请确认该模块是否被激活。
Simpletest 的测试信息在 Drupal 7 中是个缺省设置,不过因为的确有用,所以还是要确认一下这个选项是打开的,这个选项打开后,会在测试的每个点生成一个截图,来展示当时的状态。可以在 “admin/config/development/testing/settings” 检查这个配置。
在 Drupal 7 中,Simpletest 2.x 版本也作为外部模块提供,如果你下载和启用了这个模块,他会覆盖核心的Simpletest。另外,模块的.info 文件需要 testing_api = 2.x 才能识别,所以核心测试对 Simpletest 2.x 是无效的。
Drupal Simpletest 的运作方式
Drupal基本上是一个提供 Web 相关功能的系统,所以对这些功能的检测就是很必要的。Simpletest 创建一个完整的 Drupal 实例,以及一个虚拟的浏览器,然后使用虚拟浏览器去测试这个 Drupal 实例,跟手动工作的情况类似。必须要注意的是,每一个测试都是在一个新的 Drupal 实例下运行的。换句话说,当前站点的用户和配置都是不存在的,所以在 Drupal 核心之外的模块也没有被启用。所以如果你的测试序列需要一个有特别权限的用户,就必须创建一个出来(这跟从头新建一个站点的情况类似)。如果对模块有依赖,也需要启用。如果对配置参数有需求,也必须用 Simpletest 来进行配置,这是因为你的当前站点的所有配置都不会被复制到这个测试实例之中,files 目录中的内容也同样不会被拷贝过去,用户同样也都不存在。然而这些任务都可以在 Simpletest 的命令中来完成,下面我们会介绍其中的一小部分。
关于 Simpletest Example 模块
Simpletest Example 模块提供了一种自定义的 Node 类型。这个类型具有一个标题和内容字段。这给我们用来演示创建内容的方法。例子中实现了 *hook_node_info()*,来让 Drupal 知道这个节点类型,还实现了 hook_form() 为 Node 提供一个 Form。这个模块还提供了相关的权限,来完成创建和编辑这一 Node 类型的许可。当然,我们还有一个 simpletest_example.info。
注意我们的模块有个bug:权限的处理不太对头。虽然存在编辑 simpletest 示例的权限,可惜并没有正确的被 simple_test_access() 处理,所以我们即使有这个权限,还是无法进行编辑。当然,我们如果手工测试的话,很可能因为我们用的是 1 号用户而导致无法复现这个情况。
(又开始插播广告了——译者注)这些代码由 Example 模块提供和维护。在你作出一些重要变更之前,利用这一模块中的代码进行测试修改以及实验是非常有帮助的,欢迎大家下载安装并使用这些模块。
我们做什么
如果你安装了 simpletest_example,你可以跟随这些步骤来看看需要测试的内容:
浏览 *Content > Add new content > Simpletest Example Node Type*,会看到如下页面:
查看一下界面,确定我们要测试的内容:
手动试一下这些功能,熟悉一下即将测试的界面,并保证在 simpletest 执行之前,基本功能是可用的。如果连怎么运行都不知道,是无法编写一个可用的测试用例的。这里只要简单的在标题和内容中写几个字,然后点击保存按钮,应该会看到类似这样的结果。
为 simpletest_example 建立测试。
现在是时候创建我们的测试了,我们将在 simpletest_example.test文件中完成这一任务。
如果你要向 Drupal 7 模块中新增一个 .test 文件,你应该把它加入到 info 文件的 files[] 中。在我们的例子中,我们在 simpletest_example.info 中加入了这样的内容:
files[] = simpletest_example.test
如果你在使用前面提到的 Simpletest 2.x,你可能还需要在 .info 文件中加入这一行:
testing_api = 2.x
如果你要往一个现存的模块中添加 .test 文件,那么可能需要重建 Drupal 的缓存,来通知 Drupal 有新文件加入。
建立一个测试需要有四个基础步骤:
-
创建一个结构:继承 DrupalWebTestCase。
-
在测试用例的初始化过程中,创建配置信息,用户等,作为准备工作。
-
在测试用例中创建实际的测试。
-
当然,如果出了问题,查查为什么测试无法正常工作,并对测试或被测试模块进行排错。
首先,我们继承一个 DrupalWebTestCase 作为基本的模板。
接下来,是很重要的 setUP() 过程。这里我们必须做点事情让这个 Drupal 实例能够按照我们期望的方式来运行。我们必须要清楚:如果我们新建一个 Drupal 站点,我们要做什么才能满足我们测试需要的条件?本例中,我们知道我们需要:
-
启用 Simpletet Example 模块
-
建立一个有权创建 simpletest_example 内容的用户
-
用这个新建用户登录
下面的 setUp() 代码用来完成这些任务:
privileged_user = $this->drupalCreateUser(array(
'create simpletest_example content',
'extra special edit any simpletest_example',
));
$this->drupalLogin($this->privileged_user);
}
?>
注意:在 Drupal 6 中,我们必须显式的启用所有被依赖模块。Drupal 7 会自动启用这些需要的模块。
编写指定测试:创建 Node
现在我们要练习实现指定的测试内容。我们只要为前面的 test 类添加成员方法就可以了,每个成员方法会实现一个练习。所有的成员函数名都应该以小写的 ‘test’ 开始。所有这类函数都会自动被 Simpletest 识别并按需运行(注意,虽然可以把每个断言放到一个不同的函数中,但是还是推荐一次运行多个不同的断言)。
我们的第一个测试是利用 node/add/simpletest-example 中的 form 创建一个新的simpletest_example 类型的 Node:
randomName(8);
$edit["body[und][0][value]"] = $this->randomName(16);
$this->drupalPost('node/add/simpletest-example', $edit, t('Save'));
$this->assertText(t('Simpletest Example Node Type @title has been created.', array('@title' => $edit['title'])));
}
?>
drupalPost, drupalGet 和 Assertions
上面的代码是一组非常简单的 Form 提交,提交的目标是 node/add/simpletest-example
页面。他做了一个字段的数组( $edit
数组,为标题和内容分配随机内容),然后把这个 form 提交,并检测我们是否后能在页面上获取到相应的内容。
大多数测试是这样的内容:
-
用drupalGet()前往指定页面,或用 drupalPost() 提交一个 form。
-
检测指定页面是否出现了符合我们期待的内容。
$this->drupalGet($path)
很简单:浏览指定页面。
$this->drupalPost($path, $edit_fields, $submit_button_name)
稍微有点复杂。
然后就有很多可能的检测,最简单的情况是 $this->assertText($text_to_find_on_page)
,如果这篇教程无法满足你的需求,可以在这里获得更多帮助。
测试中的用户和核心环境
测试是在一个沙箱环境中运行的。利用 $this->drupalLogin($account)
登录。drupalGet 和 drupalPost 这些测试方法也是运行在沙箱中的。如果你想调用核心API功能(例如 user_access
),强烈推荐将测试用户指派到核心环境之中。
drupalCreateUser(array('access content'));
$this->drupalLogin($account);
global $user;
$user = user_load($account->uid);
$this->assertFalse(user_access('access content'));
?>
Simpletest 在测试期间,把用户切换到 uid 1,并协助恢复用户,所以我们可以在没有安全问题的情况下改变用户。
Simpletest 的 Web 界面
接下来我们需要运行测试。这里我们要从web界面启动测试。
进入 Configuration > Development > Testing
页面,找到刚创建的模块。在这个页面,你会看到两个 Tab:“Settings” 和 “List”。在 List 页上,你会看到可用测试的列表。选择刚创建的组名为 “Examples” 测试,点击 Run Tests
按钮。在查看这个列表之前,最好晴空一下缓存。
测试运行之后,就可以获得结果,这个测试的通过情况。
也可以在命令行下运行测试,更多信息可以参考从命令行运行测试。
测试失败演示
只是运行一个成功的例子意义很有限,还是看看失败的情况吧。
我们会用同样的 class 来进行编辑 Node 的测试。我们的模块在这个功能上面有缺陷 —— 没有正确的处理 “edit own simpletest_example” 权限。所以有这个权限的用户也无法进行编辑。在这个测试中,我们会创建一个 Node,并尝试去编辑。
运行下面的测试,来查看结果。
'simpletest_example',
'title' => $this->randomName(32),
'body' => array(LANGUAGE_NONE => array(array($this->randomName(64)))),
);
$node = $this->drupalCreateNode($settings);
// For debugging, we might output the node structure with $this->verbose()
$this->verbose('Node created: ' . var_export($node, TRUE));
// It would only be output if the testing settings had 'verbose' set.
// We'll run this test normally, but not on the testbot, as it would
// indicate that the examples module was failing tests.
if (!$this->runningOnTestbot()) {
// The debug() statement will output information into the test results.
// It can also be used in Drupal 7 anywhere in code and will come out
// as a drupal_set_message().
debug('We are not running on the PIFR testing server, so will go ahead and catch the failure.');
$this->drupalGet("node/{$node->nid}/edit");
// Make sure we don't get a 401 unauthorized response:
$this->assertResponse(200, 'User is allowed to edit the content.');
// Looking for title text in the page to determine whether we were
// successful opening edit form. Note that the output message
// "Found title in edit form" is not translated with t().
$this->assertText(t("@title", array('@title' => $settings['title'])), "Found title in edit form");
}
}
?>
单元测试
Simpletest也提供了 DrupalUnitTestCase
作为一个 DrupalWebTestCase
的补充。
单元测试不会创建数据表和文件目录。这使得单元测试比功能测试的运行更加迅速,同时也意味着他无法访问数据库和文件系统。调用任何访问数据库的 Drupal 函数都会引发异常,这其中就包含了watchdog()
, module_implements()
, module_invoke_all()
等。
关于 Drupal 的单元测试的更多信息,可移步到 Unit Testing with Simpletest。
Simpletest中使用 t() 的时机
跟Drupal中的字符串不同,simpletest中的字符串一般不需要使用 t(),这里有两个原因:
-
多数字符串只是用来测试,不会出现在网站上,所以无需翻译。
-
参与测试的各个不同的组件应该尽量解耦,有利于提供高性能的,可靠的覆盖测试。t() 对于其他子系统有依赖,所以尽量避免。
什么时候使用 t()
在下列情况下需要使用 t():
-
进行翻译或本地化相关的测试。
-
测试一个翻译好的内容展现。例如,要检查 403 页面 “Access denied” 消息,使用下面的代码:
assertText(t('Access denied'), 'Access denied message found on forbidden foo page.'); ?>
注意第一个字符串 “Access denied” ,产生于 drupal_deliver_html_page()
,接下来用 t() 进行翻译。所以我们也用 t() 来检查这个消息的展现。
什么时候不用 t()
下面的情况不要用 t():
-
getInfo() 中的字符串。
-
测试消息,重复使用上面的例子:
assertText(t('Access denied'), 'Access denied message found on forbidden foo page.'); ?>
第二个字符串,’Access denied message found on forbidden foo page.‘,是仅为测试这服务的,所以不应进行翻译。
- 测试模块或者主题的输出,除非是明确要测试翻译功能。
在上面所有的例子中,如果需要格式化输出变量,可以使用 format_string()。
Simpletests 除错
在 test 设置中(*admin/config/development/testing/settings*)有一个选项叫 “Provide verbose information when running tests”,如果打开了这个选项,每个drupalGet() 和 drupalPost() 都会捕捉一个HTML文件,可以通过这些文件查看测试结果。这是个非常重要的除错手段。
也可以使用 $this->verbose("some message")
,参数中的消息会被输出。
在 Boombatower’s blog about debugging in Drupal 7 更多的 debug()和$this->verbose() 的信息。
接下来做什么?
注意,rfay 维护了一篇配合本文的Google Docs presentation to go with this material。
文章来源于互联网:Drupal 7 测试指南(simpletest)