功能测试
edit功能测试
edit我们通过功能测试确保 Kibana UI 按照预期方式运行。该功能测试通过自动化用户交互,取代了长达数小时的人工测试。Kibana 使用的工具叫做 FunctionalTestRunner
,能够更好的控制功能测试环境,更便于插件作者使用。
运行功能测试
editFunctionalTestRunner
结构非常简单,大部分功能主要源自位于 test/functional/config.js 的配置文件。如果您正在编写一个插件,就会拥有自己的配置文件。有关更多信息,请参见 插件功能测试 。
使用 node.js 执行 FunctionalTestRunner
脚本运行测试,使用 Kibana 的默认配置:
node scripts/functional_test_runner
在没有任何参数的情况下运行时, FunctionalTestRunner
会自动加载位于标准位置的配置,但是您可以用 --config
标记来覆盖这个行为。 --bail
和 --grep
也有命令行标记,功能与 mocha 类似。 日志也可以通过使用 --quiet
、 --debug
或 --verbose
标记进行自定义设置。
使用 --help
标记获得更多选项。
编写功能测试
edit环境
edit我们使用 chromedriver、 leadfoot 和 digdug 在 Chrome 上做自动化测试。 FunctionalTestRunner
启动后,digdug 会打开一个启动 chromedriver 的 Tunnel
和一个 Chrome 的精简用例。digdug 还会创建一个 Leadfoot’s Command
类的用例,该用例可以通过 remote
服务器获取。 remote
通过 digdug Tunnel
与 Chrome 进行通讯。 remote
涉及的所有命令详见 leadfoot/Command API。
FunctionalTestRunner
使用 babel 语言自动编译功能测试,因此测试可以使用 Kibana 源代码使用的 ECMAScript 特性。详见 style_guides/js_style_guide.md。
术语
editProvider(提供者):
FunctionalTestRunner
运行的代码会被打包进一个函数中,这样它就可以通过配置文件传递,并且能被参数化。这些提供者函数中的任何一个都有可能是异步的,并需要返回或重新获取他们想要的 值 。提供者函数总通过单一参数:API Provider(参见 Provider API 章节)来调用。
提供者配置示例:
// config and test files use `export default` export default function (/* { providerAPI } */) { return { // ... } }
- Services(服务)
- 服务根据服务提供者产生的单一值命名。测试和其他服务能够通过请求服务的名称来检索服务实例。除 mocha API 以外的所有功能都是通过服务公开的。
- Page objects(页对象)
- 页对象是一种将通常行为封装进特定页面或插件的特殊服务。当您编写自己的插件时,您可能想要添加一个(或多个)用于描述测试所需执行的常见交互的页面对象。
- Test Files(测试文件)
-
FunctionalTestRunner
的主要目的是执行测试文件。这些文件导出一个提供者 API 调用的测试提供者(Test Provider),但并不会返回数值。相反,测试提供者用 mocha’s BDD interface 定义一个程序组。 - Test Suite(测试套件)
-
测试套件是调用
describe()
的测试集,然后通过调用it()
、before()
、beforeEach()
等填充测试和 setup/teardown hooks。每个测试文件都必须定义唯一一个顶级测试套件,但测试套件可以拥有任意多个嵌套测试套件。
测试文件剖析
edit下列带注释的示例文件展示了每个测试套件所使用的基本结构。它首先导入 expect.js
,然后定义其默认输出:一个匿名的测试提供者。该测试提供者为 getService()
和 getPageObjects()
函数拆解提供者 API。它使用这些函数来收集本套件的依赖。对于 mocha.js 用户,其他测试文件看起来就很普通了。
import expect from 'expect.js'; // test files must `export default` a function that defines a test suite export default function ({ getService, getPageObject }) { // most test files will start off by loading some services const retry = getService('retry'); const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); // for historical reasons, PageObjects are loaded in a single API call // and returned on an object with a key/value for each requested PageObject const PageObjects = getPageObjects(['common', 'visualize']); // every file must define a top-level suite before defining hooks/tests describe('My Test Suite', () => { // most suites start with a before hook that navigates to a specific // app/page and restores some archives into elasticsearch with esArchiver before(async () => { await Promise.all([ // start with an empty .kibana index esArchiver.load('empty_kibana'), // load some basic log data only if the index doesn't exist esArchiver.loadIfNeeded('makelogs') ]); // go to the page described by `apps.visualize` in the config await PageObjects.common.navigateTo('visualize'); }); // right after the before() hook definition, add the teardown steps // that will tidy up elasticsearch for other test suites after(async () => { // we unload the empty_kibana archive but not the makelogs // archive because we don't make any changes to it, and subsequent // suites could use it if they call `.loadIfNeeded()`. await esArchiver.unload('empty_kibana'); }); // This series of tests illustrate how tests generally verify // one step of a larger process and then move on to the next in // a new test, each step building on top of the previous it('Vis Listing Page is empty'); it('Create a new vis'); it('Shows new vis in listing page'); it('Opens the saved vis'); it('Respects time filter changes'); it(... }); }
提供者 API
edit提供者 API 对象(Provider API Object)是所有提供者的第一个也是唯一一个参数。这个对象可以用于加载服务、页面对象和配置、测试文件。
在配置文件中,API具有以下属性
|
|
|
返回一个解析为配置实例的承诺,提供 |
在服务和 PageObject 提供者中,API 是:
|
根据名称,加载并返回服务的一个单例实例 |
|
加载 |
测试提供者中的 API 与服务提供者 API 相同,但是具有附加方法:
|
加载路径上的测试文件。使用此方法将其他文件中的套件嵌套到更高级的套件中。 |
服务指标
edit内置服务
editFunctionalTestRunner
自带三种内置 service:
- config:
-
- 源码: src/functional_test_runner/lib/config/config.js
- 概要: src/functional_test_runner/lib/config/schema.js
-
使用
config.get(path)
查看配置文件中的任意值
- log:
-
- 源码: src/utils/tooling_log/tooling_log.js
-
ToolingLog
实例是可读流。此服务提供的实例由FunctionalTestRunner
CLI 自动传输到 stdout -
log.verbose()
、log.debug()
、log.info()
、log.warning()
像 console.log 那样工作,只不过产生结构化更好的输出
- lifecycle:
-
- 源码: src/functional_test_runner/lib/lifecycle.js
- 设计主要用于 service 中
- 公开生命周期事件以进行基本协调。处理程序可以返回承诺并异步地解析、失败
-
包括
beforeLoadTests
、beforeTests
、beforeEachTest
、cleanup
、phaseStart
、phaseEnd
阶段
Kibana 服务
editKibana 功能测试定义了绝大部分测试会使用的实际功能。
- retry:
-
- 源码: test/functional/services/retry.js
- 重试操作辅助器
-
常用方法:
-
retry.try(fn)
- 在 loop 中执行fn
直至成功或超过默认重试时间 -
retry.tryForTime(ms, fn)
在 loop 中执行,直至成功或超过ms
毫秒
-
- testSubjects:
-
- 源码: test/functional/services/test_subjects.js
- 测试主题是从测试中选出的被专门标记过的要素
-
可能的情况下,在 CSS 选择器中使用
testSubjects
-
使用:
-
用
data-test-subj
属性标记您的测试对象:<div id="container”> <button id="clickMe” data-test-subj=”containerButton” /> </div>
-
使用
testSubjects
帮助器点击这个按钮await testSubjects.click(‘containerButton’);
-
-
常用方法:
-
testSubjects.find(testSubjectSelector)
- 在页面中寻找一个测试对象;如果过一段时间没有找到,抛出异常 -
testSubjects.click(testSubjectSelector)
- 在页面中点击一个测试主题;如果过一段时间没有找到,抛出异常
-
- find:
-
- 源码: test/functional/services/find.js
-
remote.findBy
方法帮助器,用于记录日志和管理超时 -
常用方法:
-
find.byCssSelector()
-
find.allByCssSelector()
-
- kibanaServer:
-
- 源码: test/functional/services/kibana_server/kibana_server.js
- 与 Kibana 服务器交互的帮助器
-
常用方法:
-
kibanaServer.uiSettings.update()
-
kibanaServer.version.get()
-
kibanaServer.status.getOverallState()
-
- esArchiver:
-
- 源码: test/functional/services/es_archiver.js
-
用
esArchiver
创建的加载、卸载文件 -
常用方法:
-
esArchiver.load(name)
-
esArchiver.loadIfNeeded(name)
-
esArchiver.unload(name)
-
- docTable:
-
- 源码: test/functional/services/doc_table.js
- 与 doc 表格交互的帮助器
- pointSeriesVis:
-
- 源码: test/functional/services/point_series_vis.js
- 与点序列可视化交互的帮助器
- Low-level utilities:
-
-
es
- 源码: test/functional/services/es.js
- Elasticsearch 客户端
-
高级选项:
kibanaServer.uiSettings
或esArchiver
-
remote
- 源码: test/functional/services/remote/remote.js
-
Leadfoot’s
Command
类实例 - 负责与浏览器的所有通信
-
高级选项:
testSubjects
、find
和PageObjects.common
- 完整 API 参见 leadfoot/Command API
-
自定义服务
edit服务是有意通用的。它们可以是任何东西(甚至什么都不是)。有些服务有助于与特定类型的 UI 元素(如 PooSosieServices )交互,而其他服务则更为基础,如日志或配置。每当您想在可重用包中提供一些功能时,请考虑制作自定义服务。
为了创建一个自定义的 somethingUseful
service:
-
创建一个如下的
test/functional/services/something_useful.js
文件:// Services are defined by Provider functions that receive the ServiceProviderAPI export function SomethingUsefulProvider({ getService }) { const log = getService('log'); class SomethingUseful { doSomething() { } } return new SomethingUseful(); }
-
从
services/index.js
重新导出您的 provider -
将它导入到
src/functional/config.js
并添加到服务配置中:import { SomethingUsefulProvider } from './services'; export default function () { return { // … truncated ... services: { somethingUseful: SomethingUsefulProvider } } }
PageObjects
editPageObject 的目的只是自我解释。可视化的 PageObject 提供与可视化 app 交互的助手,相当于仪表板对于仪表板 app。
"common" PageObject 是一个例外。作为一个延缓的实验性的实现,common PageObject 是有用的跨页面的帮助器集合。现在我们有了共享服务,并且这些服务可以与其他的 FunctionalTestRunner
共享,我们会继续将功能从 common PageObject 转移到服务中。
请在已有或新服务中添加新的方法,而不是进一步扩展 CommonPage 类。
Gotchas
edit记住您不能运行文件( it
块)中一个单独的测试,因为整个 describe
需要按顺序执行。在一个文件中应该只有一个顶级的 describe
。
功能测试计时
edit另一个重要的 gotcha 是通过注意时间来编写稳定的测试。所有 remote
方法异步运行。最好在进入下一步之前,在 UI 上添加等待变化的交互。
例如,与其简单的编写点击按钮的交互,不如在头脑中编写更高级目的的交互:
不好的例子: PageObjects.app.clickButton()
class AppPage { // what can people who call this method expect from the // UI after the promise resolves? Since the reaction to most // clicks is asynchronous the behavior is dependant on timing // and likely to cause test that fail unexpectedly async clickButton () { await testSubjects.click(‘menuButton’); } }
好的例子: PageObjects.app.openMenu()
class AppPage { // unlike `clickButton()`, callers of `openMenu()` know // the state that the UI will be in before they move on to // the next step async openMenu () { await testSubjects.click(‘menuButton’); await testSubjects.exists(‘menu’); } }
这样写将确保您的测试时间不是片状的,或者基于交互后UI更新的假设。
调试
edit在命令行运行:
node --debug-brk --inspect scripts/functional_test_runner
该命令会输出一个URL,通过在 Chrome 浏览器中访问该URL,您可以调试您的功能测试用例。
您也可以在运行 FunctionalTestRunner
时增加 --debug
或 --verbose
参数,从而在命令行看额外的日志信息。您可以像下面这样,在您的测试用例中增加日志:
// load the log service const log = getService(‘log’); // log.debug only writes when using the `--debug` or `--verbose` flag. log.debug(‘done clicking menu’);