功能测试
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实例是可读流。此服务提供的实例由FunctionalTestRunnerCLI 自动传输到 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’);