关于前端单元测试

对于许多开发人员来说,测试前端代码仍然是一个令人困惑的实践。但是随着前端开发变得越来越复杂,开发人员前所未有地负责稳定性和一致性,前端测试必须作为一个平等的公民在你的代码库中接受。 我们分解你的不同测试选项,并解释什么情况下他们是最好的使用。
前端测试是一个笼统的术语,涵盖了各种自动化测试策略。 其中的一些测试,比如单元测试和集成测试,多年来一直是后端开发社区中公认的最佳实践。

如何运行测试?

测试可以在浏览器中运行,方法是创建一个 HTML 页面,其中包含测试库和以 JS 文件形式包含的测试。

也可以通过导入测试和依赖库Node.js 中执行测试。 Node.js 中通常使用 jsdom 模拟类浏览器的环境。 它提供了window, document, body, location, cookies, selectors以及任何你在浏览器中运行 JS 得到的东西,但是它没有呈现任何真实的东西。 这不同于“headless mode” ,因为对于headless mode,你需要一个真正的浏览器,它甚至可以截屏,不像 jsdom。

我们建议使用第二种方法(Node.js + jsdom) ,因为它比在浏览器中运行测试要快得多。 第一种方法可能更可靠,因为你使用的是同样的软件,它们将在现实生活中呈现你的网站。 但是,使用 jsdom 更轻便,对于大多数用例来说应该足够了。

有哪些测试类型?

一般来说,前端测试主要包含以下三方面

  • 单元测试: 测试一段代码(通常是一个对象或函数) ,与其他部分隔离开来
  • 集成测试: 将多个部分放在一起进行测试
  • 功能测试(也称为e2e测试): 对整个应用程序进行自动测试,这些测试通常忽略整个应用程序的内部结构,而是从外部像黑盒子一样查看它们

这里这里这里有关于不同类型测试的更深入的信息。

单元测试

单元测试是应用广泛的测试类型之一,它处于所有测试类型的最低级别。 其目的是确保您的代码的最小位(称为单元)按预期独立运行。通常是对象或模块中的一个函数

单元测试简单、编写快速、运行快速。 这意味着你可以有许多单元测试,更多的单元测试意味着可以捕获更多的错误。 如果你需要更改代码,它们特别有用: 当你有一组单元测试来验证代码的工作时,你可以安全地更改代码,并确保程序的其他部分不会出错。

单元测试使用断言函数确保它们的输出正如预期的那样,使用代码覆盖报告工具来了解哪些单元被覆盖。

最原始的单元测试如下:

1
2
3
function add(a,b){
return a + b;
}

上面是一个add函数,他的功能是传入两个参数,他会返回两个参数的累加。如何校验这个函数呢?可以下面的代码进行最简单的校验

1
2
3
if(add(1,2) !== 3){
throw Error('add计算错误!');
}

如果是用jasmine来描述我们上面的测试的话

1
2
3
4
5
describe("Calculator Operations", function () {
it("Should add two numbers", function () {
expect(add(1,2)).toBe(3);
});
});

下面我们解释下jasmine的相关语法

describe会创建一个测试套件——一组有关联性的测试单元的集合。上面的测试套件是跟计算相关的,因此我们还可以加入相减、相乘等测试操作。

使用 it 函数,我们用来描述我们正在测试的特性或功能片段。

expect 是测试断言,用来判断我们的代码是否如期运行。

单元测试是尽可能多地使用函数式编程和纯函数的原因之一。
应用程序越纯,就越容易测试。

集成测试

老式的测试主要集中在单元测试,导致应用程序中许多小的部分可以正常运行,但是整个过程不断失败。另一方面,集成测试检测单元被重构并通过测试但是依赖于它的流程失败的情况。

集成测试应该包括重要的跨模块过程。 有时它们扩展到跨多个类的进程,有时扩展到测试不同的系统,如前端-后端交互。

与单元测试相比,你可能需要使用 spies 来确保预期的副作用,而不是仅仅断言输出或 stub 来模拟和修改进程中不在特定测试中的部分,您可能会从中受益。

另外,与单元测试相反,可能需要一个浏览器或类浏览器环境(jsdom)来访问window

Component snapshot tests 组件快照测试也属于这一类。 它们为我们提供了一种方法,可以测试进程如何影响选定的组件,而不需要实际将它们呈现到浏览器或类浏览器环境中。

如下是在vue中的组件快照测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
// HelloWorld.spec.js
import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
})

功能测试(e2e测试)

有时,快速有效的单元和集成测试是不够的。

功能测试控制浏览器并在这些环境中模拟用户行为(单击、输入、滚动等…)并确保这些场景从终端用户的角度切实可行。

另外值得注意的是,许多服务为您提供了在其上运行这些测试的设备和浏览器。 这里还有更多类似的服务。

还可以设置可视化回归测试工具 ,通过对屏幕截图进行智能比较,验证应用程序的不同屏幕是否可视化。 这些截图通常作为功能测试的一部分,或者执行单独的浏览器自动化会话。
image

有哪些测试流派?

TDD

TDD是Test Driven Development 的缩写,也就是测试驱动开发。

通常传统软件工程将测试描述为软件生命周期的一个环节,并且是在编码之后。但是TDD要求我们在编写功能代码前先编写测试代码。

Tdd 过程包括以下步骤:

  1. 写一个单元测试去描述程序的一个方面。
  2. 运行它应该会失败,因为程序还缺少这个特性。
  3. 编写使测试通过所需的最小代码量
  4. 运行测试以检查新测试通过情况
  5. 可以选择重构代码
  6. 重复第一个步骤,重新完善测试代码
    具体的实践可以看[译]5步法让TDD变得简单

    BDD

    BDD行为驱动开发,即Behaviour Driven Development,是一种新的敏捷开发方法。

行为驱动开发(BDD)是测试驱动开发(TDD)的一个分支。同样也是先写测试,再完成具体实现。

Bdd 注重于测试行为,而不是实现。一个写得不好的测试将检查实现而不是行为。

如果实现随着应用程序的发展或重构而改变,那么即使行为相同,测试也必须改变。 这抵消了 TDD 节省时间的好处。

举一个计数器的例子:

1
2
3
4
5
6
7
8
9
suite('Counter', function() {
test('tick increases count to 1', function() {
var counter = new Counter();

counter.tick();

assert.equal(counter.count, 1);
});
});

这是一个虚构的计数器对象的单元测试。我们测试在调用 tick 之后,这个值应该是1,这听起来很合理。 但是在测试中有一个问题。测试完全依赖于计数器从0开始这一事实。 所以换句话说,这个测试依赖于两件事情。

  1. 计数器从0开始
  2. tick调用增量1
    计数器从0开始是一个与 tick ()函数的行为无关的实现细节。因此,它不应该对测试有任何影响。我们这样编写测试的唯一原因是我们考虑的是实现,而不是行为。
    Bdd 建议测试行为,所以我们不去考虑代码是如何实现的,而是花一点时间考虑场景是什么。 当tick调用时,它应该增加一个计数。
    这里重要的部分是考虑场景,而不是实现,可以引导你设计一个更好的测试。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    describe('Counter', function() {
    it('should increase count by 1 after calling tick', function() {
    var counter = new Counter();
    var expectedCount = counter.count + 1;

    counter.tick();

    assert.equal(counter.count, expectedCount);
    });
    });
    在这个版本的测试中,使用了 Mocha 的 BDD 风格函数,我们去掉了实现细节。我们不是依赖于从0开始的计数器,而是与 counter.count + 1进行比较,后者在测试行为方面更有意义。

有时候你的需求会改变。让我们想象一下,由于某种原因,计数器必须从其他值开始。在此之前,我们必须更改测试以适应这一点,但是对于BDD编写的测试代码而言,没有必要这样做。

有哪些测试工具类型?

测试工具可以分为以下功能。 有些只为我们提供一种功能,有些则为我们提供一种组合。

为了实现最灵活的集合功能,通常使用几种工具的组合。

  1. 测试启动程序(Test launchers): 使用 CLI 或 UI 在浏览器或 Node.js 中通过用户配置启动测试。 也可以通过手动打开浏览器来实现。(Karma, Jasmine, Jest, TestCafe, Cypress)
  2. 测试结构提供程序(Testing structure)可以帮助您组织测试文件(Mocha, Jasmine, Jest, Cucumber, TestCafe, Cypress)
  3. 断言函数(Assertion functions)检查测试返回的结果是否符合预期。(Chai, Jasmine, Jest, Unexpected, TestCafe, Cypress)
  4. 生成并显示测试进度和结果。 (Mocha, Jasmine, Jest, Karma, TestCafe, Cypress)
  5. Mocks, Spies, 和 stubs来隔离测试的某些部分并捕捉其副作用。(Sinon, Jasmine, enzyme, Jest, testdouble)
  6. 生成并比较组件或数据结构的快照,以确保以前运行的更改是预期的。(Jest, Ava)
  7. 生成代码覆盖率报告,以了解测试覆盖了多少代码。(Istanbul, Jest, Blanket)
  8. 浏览器控制器模拟功能测试的用户操作。 (Nightwatch, Nightmare, Phantom, Puppeteer, TestCafe, Cypress)
  9. 可视化回归工具用于使用图像比较技术将您的网站与其以前的版本进行可视化比较。(ApplitoolsPercyWraithWebdriverCSS)

让我们来解释一下上面提到的一些术语:

测试启动程序将得到一个测试列表,以及运行这些测试所需的各种配置和搭建(运行哪些浏览器、使用哪些 babel 插件、如何格式化输出等)。

1
2
3
4
5
6
7
8
# Install Karma:
npm install karma --save-dev

# Install plugins that your project needs:
npm install karma-jasmine jasmine-core karma-chrome-launcher karma-firefox-launcher --save-dev

# Run on
npx karma start karma.conf.js --log-level debug --single-run
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
module.exports = function(config) {
config.set({
basePath: '',
frameworks: ['jasmine'],
files: [
'src/**/**.js',
'test/**/*.js'
],
exclude: [
],
webpack: {},
preprocessors: {
'src/**/*.js': ['webpack','coverage'],
'test/**/*.js': ['webpack']
},
coverageReporter: {
type : 'html',
dir : 'coverage/'
},
reporters: ['progress','coverage'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
concurrency: Infinity
})
}

测试结构指的是测试的组织。 现在,测试通常被组织在一个支持行为驱动开发BDD 结构中。 它通常看起来像这样:

1
2
3
4
5
6
7
8
9
10
describe('calculator', function() {
// describes a module with nested "describe" functions
describe('add', function() {
// specify the expected behavior
it('should add 2 numbers', function() {
//Use assertion functions to test the expected behavior
...
})
})
})

断言函数用于确保被测变量包含期望值。 它们通常是这样的,前两种风格是最常见的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Chai expect (popular)
expect(foo).to.be.a('string')
expect(foo).to.equal('bar')

// Jasmine expect (popular)
expect(foo).toBeString()
expect(foo).toEqual('bar')

// Chai assert
assert.typeOf(foo, 'string')
assert.equal(foo, 'bar')

// Unexpected expect
expect(foo, 'to be a', 'string')
expect(foo, 'to be', 'bar')

提示: 这里有一篇关于 Jasmine 和 Jest 断言的好文章。

Spies为我们提供函数相关的信息。 例如,他们被调用了多少次,在哪些情况下,被谁调用?

Spies用于集成测试,以确保流程的副作用是预期的。 例如,一个计算函数在某个过程中被调用了多少次?

注意我们是如何运行 father.execute() 的,并计算在这个过程中 father.child 运行了多少次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Child {
...
execute() { ... }
...
}

class Father {
constructor() {
this.child = new Child()
}
...
execute() {
...
this.child.execute()
...
this.child.execute()
...
}
...
}

it('should call child execute twice when father executes', () => {
const father = new Father()

// create a sinon spy to spy on object.method
const childSpy = sinon.spy(father.child, 'execute')

// call the method with the argument "3"
father.execute()

// make sure child.execute was called twice
assert(childSpy.calledTwice)
})

Stubbing 或者 dubbing可以用用户提供的功能替换现有模块的选定方法,以确保测试期间的预期行为。

为了确保 user.isValid() 在测试期间总是返回 true,您可以使用:

1
2
3
4
5
6
7
8
// Sinon
sinon.stub(user, 'isValid').returns(true)

// Jasmine stubs are actually spies with stubbing functionallity
spyOn(user, 'isValid').andReturns(true)

// Testing someFn with user where user.isValid() returns true
assert(someFn(user))

Mocks or Fakes用于伪造某些模块或行为,以测试进程的不同部分。

例如,Sinon可以伪造一个服务器,以确保在测试某个流时能够得到离线、快速和预期的响应。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
it('returns an object containing all users', done => {

// create and configure the fake server to replace the native network call
const server = sinon.createFakeServer()
server.respondWith('GET', '/users', [
200,
{ 'Content-Type': 'application/json' },
'[{ "id": 1, "name": "Gwen" }, { "id": 2, "name": "John" }]'
])

// call a process that includes the network request that we mocked
Users.all()
.done(collection => {
const expectedCollection = [
{ id: 1, name: 'Gwen' },
{ id: 2, name: 'John' }
]
expect(collection.toJSON()).to.eql(expectedCollection)
done()
})

// respond to the request
server.respond()

// remove the fake server
server.restore()
})

Snapshot Testing(快照测试)是将数据结构与预期结构进行比较时进行的测试。

下面的示例来自官方的 Jest 文档,显示了某个 Link 组件的快照测试。

1
2
3
4
5
6
7
8
9
10
11
12
13
it('renders correctly', () => {

// create an instance of the Link component with page and child text
const linkInstance = (
<Link page="http://www.facebook.com">Facebook</Link>
)

// create a data snapshot of the component
const tree = renderer.create(linkInstance).toJSON()

// compare the sata to the last snapshot
expect(tree).toMatchSnapshot()
})

它实际上并没有呈现和拍摄组件的图片,但是它将其内部数据保存在一个单独的文件中,如下所示:

1
2
3
4
5
6
7
8
9
10
exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;

当测试运行时,新快照与上一个快照不同时,系统会提示开发人员确认更改的目的是什么。
image
浏览器可以由安装在其上面的驱动程序控制,并使用不同的方法控制浏览器。 这就是 selenium 的作用机理:

Node.js <=> WebDriver <=> FF/Chrome/IE/Safari drivers <=> browser

或者通过脚本注入可以访问整个应用程序环境的 JS 代码: DOM、network、 cookie 等等… 可以引发模拟用户行为的事件。 例如:

document.getElementByID(‘someButton’).Dispatchevent(clickEvent)

原理:

Node.js <=> FF/Chrome/IE/Safari 脚本注入 <=> 模拟事件

有哪些常见的测试工具?

市面上有很多很棒的工具。 我不能在这里罗列所有的工具,但是我试图在下面的列表中包括最知名、最活跃和最常用的工具:

jsdom

Jsdom 是 WHATWG DOM 和 HTML 标准的 JavaScript 实现。 换句话说,jsdom 模拟了浏览器的环境,只运行原生的 JS。

如前所述,在这个模拟的浏览器环境中,测试可以非常快地运行。 Jsdom 的缺点是,不是所有的东西都可以在真正的浏览器之外模拟(例如,你不能截图) ,所以使用它会限制测试的范围。

值得一提的是,JS 社区迅速改善了 jsdom,目前的版本非常接近真正的浏览器。

Electron

Electron 框架允许您使用 JavaScript、 HTML 和 CSS 编写跨平台的桌面应用程序。 它还有一个headless mode。

它有一个庞大的社区,许多重要的应用程序都是建立在它之上的,所以它应该是最前沿的: AtomSlackSkypeGitHub桌面等等。

Cypress.io 这样的测试工具使用 Electron 来启动测试,最大限度地控制浏览器。

Istanbul

Istanbul 会告诉你有多少代码被单元测试覆盖。 它将以百分比的形式报告语句、行、函数和分支覆盖率,以便您更好地理解剩下要覆盖的内容。

Karma

Karma 是一个高效的测试环境,它支持所有流行的测试描述框架(Jasmine、 Mocha、 QUnit)。 Karma 启动一个带有特定网页的测试服务器,用于在页面的环境中运行测试。 此页面可以在许多浏览器和类浏览器环境(包括 jsdom)中运行。

选择 Karma 的主要因素在于它支持集成 CI/CD 引擎和以下特性。

  • 可用于在浏览器、PhantomJS 等headless环境以及设备上运行测试
  • 支持在大多数流行框架中编写的测试
  • 支持使用 Chrome 和 Webstorm 进行测试用例调试

ChaiJS

Chai 是最流行的断言库,它有许多插件和扩展。

Unexpected

Unexpected断言库,其语法与 Chai 略有不同。 它也是可扩展的,因此基于它的库可以使断言更加先进,就像 unexpected-react 一样,您可以在这里阅读更深入的内容。

Sinon

Sinon 主要用来伪造真实的数据。有非常强大的spies,stubs 和 mocks。可以用于任何单元测试框架。

testdouble.js

testdouble 是一个不那么受欢迎的库,它做了Sinon所做的事情,并声称做得更好,在设计、理念和特性方面有一些差异,可以使它在许多情况下变得有用。 你可以在这里这里这里阅读。

Wallaby

Wallaby是另一个值得一提的工具。 它不是免费的,但许多用户推荐购买。 它在 IDE 上运行(它支持所有主要的测试) ,并且运行与代码更改相关的测试,并且在代码旁边实时显示是否有任何失败。
image

Cucumber

Cucumber通过使用 Gherkin 语法在验收标准文件和相应的测试之间进行划分,Cucumber 可以帮助在 BDD 中编写测试。

测试可以用框架支持的多种语言编写,包括 JS,我们关注的是:

1
2
3
4
5
6
7
8
Feature: A reader can share an article to social networks
As a reader
I want to share articles
So that I can notify my friends about an article I liked
Scenario: An article was opened
Given I'm inside an article
When I share the article
Then the article should change to a "shared" state
1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = function() {
this.Given(/^I'm inside an article$/, function(callback) {
// functional testing tool code
})

this.When(/^I share the article$/, function(callback) {
// functional testing tool code
})

this.Then(/^the article should change to a "shared" state$/, function(callback) {
// functional testing tool code
})
}

许多团队会发现这种语法比 TDD 更方便。

选择你的单元和集成测试框架

您可能应该做的第一个选择是希望使用哪个框架。 建议使用您的框架提供的工具,直到需要独特的工具为止。

简而言之,如果正考虑如何开始测试或者正在为大型项目寻找一个快速的框架,那么使用 Jest 是不会出错的。

如果你想要一个非常灵活和可扩展的配置,选择 Mocha

如果你想要简单,去用 Ava 吧。

如果你想做得很低级,那就用tape

以下是一些最突出的工具及其特点:

JEST

Jest 是 Facebook 定期维护的最流行的框架之一。这是基于Jasmine 的。它是基于 React 的应用程序的首选框架,因为它是零配置的。然而,它不仅限于与 React 一起使用,vue-cli 的配置选项也有它。

在阅读了大量的文章和博客帖子之后,人们对 Jest 的速度和便捷印象深刻,真是难以置信。

  • 性能——首先,Jest 通过实现一种聪明的并行测试机制,被认为对于拥有许多测试文件的大型项目来说速度更快(这在我们的经验中是正确的,在这些博客文章中也有讨论: 这里这里这里这里这里)
  • UI——清晰方便
  • 零配置,快手上手——随时可以使用的断言、spies和mocks等同于类似于 Sinon 这样的功能。 如果您需要一些独特的特性,库仍然可以很容易地被使用
  • Globals——像Jasmine一样,它默认创建测试全局,所以你不需要引入他们。 这可能被认为是不好的,因为它使你的测试更不灵活,更不可控,但在大多数情况下,它只是使你的编写更容易
    1
    2
    3
    4
    5
    6
    7
    8
    // "describe" is in the global scope already
    // so no these require lines are **not required**:
    // import { describe } from 'jest'
    // import { describe } from 'jasmine'

    describe('calculator', function() {
    ...
    })
  • 快照测试——jest-Snapshot 是由 Facebook 开发和维护的,尽管它可以作为框架集成工具的一部分或者通过使用正确的插件在几乎任何其他框架中使用
  • 改进的 mocking 模块——Jest 为您提供了一种简单的方法来模拟沉重的模块以提高测试速度。 例如,可以通过调用服务来解析promise,而不是发出网络请求
  • 代码覆盖测试——包括一个基于 Istanbul 的强大且快速的内置代码覆盖工具
  • 可靠性——尽管这是一个相对年轻的库,但在整个2017年和2018年,Jest 稳定下来,现在被认为是可靠的。 它目前得到所有主要 ide 和工具的支持
  • 开发——只更新更新的文件,因此测试在观察模式下运行得非常快

    Jasmine

    Jasmine 是一个测试 JavaScript 代码的(BDD)行为驱动开发框架。Jasmine 是 Jest 所基于的测试框架。它已经存在了很长时间,为什么你更喜欢Jasmine而不是Jest?它有大量的文章,工具,以及在各种论坛上回答的问题,这些都是社区多年来创建的。

此外,Angular 仍然建议使用它而不是Jest,虽然 Jest 也非常适合进行 Angular 测试,而且很多人都这样做。

  • 快速上手——随时可以开始测试所需的一切
  • Globals——带有测试功能全局属性变量
  • 社区——它自2009年上市以来,收集了大量的文章、建议和基于它的工具
  • Angular——Angular有对它所有版本的支持,它被推荐使用在Angular的官方文档

    Mocha

    Mocha是最常用的库。 与 Jasmine 不同,它通常被跟第三方断言、mocking和spying工具一起使用(通常是 SinonChai)。

这意味着 Mocha 有点难以建立和划分到更多的库中,但是它更加灵活,对扩展更加开放。

例如,如果您希望使用特殊的断言逻辑,那么您可以使用自己的断言库来。 这也可以在Jasmine中做,但在Mocha这种变化将更加明显。

  • 社区——有许多插件和扩展来测试独特的场景
  • 可扩展性——非常可扩展,以至于插件、扩展和库的设计只能在其上运行
  • Globals——默认情况下创建测试结构全局,但显然不是像Jasmine一样有断言,spies和mocks。 有些人对Globals变化的不一致感到惊讶

    Ava

    Ava 是一个简约的轻量级测试框架,利用了 Javascript 的异步特性。 Ava 可以并发地执行测试。它允许你几乎完全控制你所做的事情。 它用于基于 node.js 的代码测试。
    优点:
  • 轻量、快速上手
  • Globals——不创建任何全局变量,因此您可以更好地控制您的测试
  • 简单——简单的结构和断言,没有复杂的 API,同时支持许多高级特性
  • 开发——Ava 只更新更新的文件,所以测试将在监听模式快速运行
  • 速度——比大多数其他测试框架都要快,作为独立的nodejs进程并行测试
  • 支持快照

    Tape

    Tape是最简单的测试框架之一,在架构上与 AVA 非常相似。
  • Globals——不支持全局变量,提供纯粹的代码,让开发人员可以完全自由地编写测试用例
  • 简单——没有复杂 API 的简单结构和断言,甚至比 Ava 更简单
  • 测试之间没有共享状态——Tape不鼓励使用“beforeEach”之类的函数,以确保测试模块化和最大限度地控制测试周期
  • 不需要 CLI——Tape将简单地运行在任何 JS 可以运行的地方

    功能测试工具(e2e测试)

    首先,如上所述,你可以在这里这里找到关于服务提供者的好文章,这些服务提供者将托管运行测试的机器,并帮助你在不同的设备和浏览器上运行这些测试。

用于功能测试的工具在实现、原理和 API 方面彼此差异很大,因此强烈建议投入时间理解不同的解决方案并在您的产品上测试它们。

简而言之,如果你一开始想使用一个简单的跨浏览器一体化工具,那就选择 TestCafe

对于一个方便的用户界面,清晰的文档,很酷的工具和整体有趣的一体化工具的功能测试经验去 Cypress.io

如果你更喜欢更老更经过时间检验的工具,你可以一开始使用 Nightwatch.js

如果您更喜欢更老的、更经过时间检验的、具有最大社区支持和灵活性的工具,WebdriverIO 是一个不错的选择。

如果你想要最可靠的和对Angular友好的解决方案,使用Protractor

selenium

Selenium 和依赖它的工具多年来统治了功能测试的市场。 它不是专门为测试而编写的,可以通过公开使用外接程序和浏览器扩展控制浏览器的驱动程序来控制浏览器。

Node.js <=> WebDriver <=> FF/Chrome/IE/Safari drivers <=> browser

可以通过许多不同的方式和使用各种编程语言来访问 Selenium WebDriver,甚至可以使用一些工具来访问,即使没有任何实际的编程。

可以将 WebDriver 导入到测试框架中,作为其中的一部分进行编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
describe('login form', () => {

before(() => {
return driver.navigate().to('http://path.to.test.app/')
})

it('autocompletes the name field', () => {
driver
.findElement(By.css('.autocomplete'))
.sendKeys('John')

driver.wait(until.elementLocated(By.css('.suggestion')))

driver.findElement(By.css('.suggestion')).click()

return driver
.findElement(By.css('.autocomplete'))
.getAttribute('value')
.then(inputValue => {
expect(inputValue).to.equal('John Doe')
})
})

after(() => {
return driver.quit()
})
})

WebDriver本身可能就足够了,确实有些人建议按原样使用它,但是还有各种库在它的基础上进行扩并进行包装。

实际上,包装 WebDriver 可能会增加冗余代码,使调试更加困难,可能会使它偏离正在进行的非常活跃的 WebDriver 开发。

尽管如此,一些人还是不喜欢直接使用它。 让我们看看一些关于 selenium 的库:

Protractor

Protractor 是一个包装 Selenium 的库,它为我们提供了改进的语法和特殊的 Angular 内置钩子。

  • Angular——具有特殊的挂钩,虽然它也可以成功地与其他 JS 框架一起使用。Angular官方文档建议使用这个工具
  • 错误报告——良好的机制
  • 支持——支持TypeScript,库是由庞大的 Angular 团队操作和维护的

    WebdriverIO

    Webdriverio 有自己的 selenium WebDriver 实现。
  • 语法——非常容易和可读
  • 灵活——一个非常简单、灵活和可扩展的库
  • 社区——它拥有良好的支持和热情的开发者社区

    Nightwatch

    Nightwatch有自己的selenium WebDriver 实现。 并提供了自己的测试框架,包括测试服务器、断言和工具。
  • 框架——也可以与其他框架一起使用,但是如果您希望运行功能测试而不是作为另一个框架的一部分,那么它就特别有用
  • 语法——看起来最简单和最易读
  • 支持——不支持typescript ,一般来说,这个库似乎比其他库支持稍微少一些

    Appium

    Appium 提供了一个类似于 Selenium 的 API,用以下工具在移动设备上测试网站:
  • iOS 9.3+: Apple的 XCUITest
  • Before iOS 9.3: Apple的 UIAutomation
  • Android 4.2+: Google的 UiAutomator/UiAutomator2
  • Android 2.3+: Google 的 Instrumentation(通过绑定一个单独的项目(Selendroid)来提供检测支持)
  • Windows Phone: 微软的 WinAppDriver
    因此,如果你使用基于 Selenium 或 Selenium 的工具,你也可以使用 Apium 在移动设备上进行测试。

    TestCafe

    Testcafe 是基于 selenium 工具的一个很好的替代品。 它在2016年底被重写并开源。
    Testcafe 还有一个付费版本,提供非编程测试工具。 它已经被弃用,并将被新的 TestCafe Studio 所取代。目前 TestCafe Studio 是免费的,但在几个月后正式发布时,它将成为一个商业产品。

Testcafe以 JavaScript 脚本的形式注入到网站中,而不是像 Selenium 那样控制浏览器本身。 这使得它可以在任何浏览器上运行,包括在移动设备上,并且完全控制 JavaScript 的执行循环。

  • 快速启动——通过Npm 安装和运行您的第一次测试在任何浏览器
  • 跨浏览器和设备——支持多种浏览器和设备,可以与 SauceLabsBrowserStack 一起使用,它们为您的测试提供设备和浏览器。 这包括在 Headless ChromeHeadless Firefox 中运行测试
  • 方便的错误报告
  • 独立的生态系统——TestCafe 使用自己的测试结构。 它可能非常方便,尤其是因为 UI 测试通常与其他测试分开运行,但有些人不喜欢它
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import { Selector } from 'testcafe';

    fixture `Getting Started`
    .page `https://devexpress.github.io/testcafe/example`

    // Own testing structure
    test('My first test', async t => {
    await t
    .typeText('#developer-name', 'John Smith')
    .click('#submit-button')
    .expect(Selector('#article-header').innerText)
    .eql('Thank you, John Smith!')
    })

    Cypress.io

    Cypress 是 TestCafe 的直接竞争对手。 他们做的东西相对来说是一样的,就是把测试注入到一个网站中,但是他们试图用一种更前沿、更灵活和更方便的方式来做。

它们之间的区别在于 Cypress.io 在浏览器中运行,并在那里控制你的测试,而 TestCafe 则在 Node.js 中运行,并通过与浏览器中的插入脚本进行序列化通信来控制测试。

这个库相对来说是新的(在2017年10月从封闭测试版转移到公开测试版) ,但是他们已经有了许多采用者和爱好者。

Cypress 是一个令人兴奋的测试框架。它以网页的形式在浏览器上提供了一个交互式用户界面。

  • 平行测试是在3.10版本中引入的
  • 文档——清晰可靠
  • 对所有应用程序变量的原生访问(另一方面,TestCafe 将对象转换为 JSON,将它们以文本形式发送给 Node.js,然后将它们解析回对象)
  • 非常方便的运行和调试工具——易于调试和测试过程的日志
  • 不支持跨浏览器——现在只支持 chrome 浏览器,不支持headless。 以headless模式运行在Electron。 他们正在撰写这篇文章
  • 一些用例丢失了,但是在不断的开发中,比如缺少 HTML5的拖放
  • 使用 Mocha 作为测试结构提供程序使其使用非常标准,并允许在与其他测试结构相同的结构中构建功能测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    describe('My First Cypress Test', function() {
    it("Gets, types and asserts", function() {
    cy.visit('https://example.cypress.io')

    cy.contains('type').click()

    // Should be on a new URL which includes '/commands/actions'
    cy.url().should('include', '/commands/actions')

    // Get an input, type into it and verify that the value has been updated
    cy.get('.action-email')
    .type('fake@email.com')
    .should('have.value', 'fake@email.com')
    })
    })

    Puppeteer

    Puppeteer 是一个由 Google 开发的 Node.js 库。它提供了一个方便的 Node.js API 来控制 Chrome 或 Headless Chrome

Headless Chrome 只是一个普通的 Chrome v59+ ,带有 –headless 标志。 当 Chrome 在 headless 模式下运行时,它会暴露一个 API 来控制它,就像之前说的,Puppeteer 是 Google 提供的 JavaScript 工具来控制它。

值得一提的是,Firefox 在2017年底也发布了Headless Mode

注意,不同的测试工具也可以使用 Headless Chrome 和 Firefox。 例如: TestCafe,Karma,Cypress。

  • Puppeteer 相对较新,但它有一个使用和开发围绕着它的工具和包装器的大型社区
  • 因为它是原生的,并且使用了最新的 Chrome 引擎,所以速度非常快
  • 一个主要的缺点也是Headless Chrome的缺点是它不支持扩展, 和 Flash 一样,可能在不久的将来也不会

    PhantomJS

    Phantom 实现了chromium 引擎来创建一个可控的类似 chrome 的 Headless 浏览器。 这是一个很好的工具,使用Headless模式,直到谷歌宣布了“Puppeteer”

它的主要维护者 vitaliyslobodin 不再维护它它的开发被暂停,它的存储库也存档了

Nightmare

Nightmare 是一个功能测试库,它提供了非常简单的测试语法。

它使用 Electron 来控制浏览器的行为。

最近似乎没有维护。 可能是因为“Puppeteer”的流行,以及它提供了相同的功能。

CodeceptJS

像上面讨论的 CucumberJS 一样,Codecept 提供了另一个不同类库 API 的抽象,使您与测试的交互使用稍微不同的关注用户行为的哲学。

1
2
3
4
5
6
7
8
9
10
Scenario('login with generated password', async (I) => {
I.fillField('email', 'miles@davis.com');
I.click('Generate Password');
const password = await I.grabTextFrom('#password');
I.click('Login');
I.fillField('email', 'miles@davis.com');
I.fillField('password', password);
I.click('Log in!');
I.see('Hello, Miles');
});

下面是可以使用此代码执行的库的列表。 关于上面所有讨论。

WebDriverIO, Protractor, Nightmare, Appium, Puppeteer

如果你认为这种语法更适合你的需要,那就试试吧。

视觉回归测试

可视化回归测试工具大致由以下几部分组成:

  • 自动化浏览器或作为上面讨论的功能测试工具的一部分运行的技术和集成,包括在 CI 的 CLI 中
  • 将智能屏幕截图作为图像和 DOM 快照
  • 图像和 DOM 比较技术,以发现差异,有时甚至使用高级人工智能
  • 用户界面允许人们批准、拒绝和改进比较机制,以便只显示与用户相关的内容
    市场上有很多这种类型的工具,但感觉这个领域还有很长的路要走。

我还注意到付费工具在视觉 / 回归测试类别比免费工具好得多。

Applitools

  • 易于安装
  • 使用人工智能使比较技术和人工输入对于错误肯定的差异和否定非常强大
  • 它可以方便地与上面讨论的许多工具集成
  • 有一个免费和灵活支付的计划,包括为创业公司和非营利组织特别的定价

    Percy

  • 易于安装
  • 使用巧妙的比较技巧
  • 方便的进行用户差异化输入
  • 它可以方便地与上面讨论的许多工具集成
  • 它与好用的工具有很好的集成
  • 有免费和灵活支付的计划

    Happo

    Happo是一个付费的视觉 / 回归测试工具。 它挂钩到 CI 中,以比较更改前后 UI 组件的视觉外观。

屏幕截图可以在不同的浏览器和不同的屏幕尺寸下进行,以确保应用程序的跨浏览器和响应式样的一致性。

为开源项目提供免费计划。

LooksSame

Yandex 创建了这个库和现在已经被弃用的 Gemini 一起,这是一个非常简单易用的视觉回归测试工具。

现在 Yandex 迁移到了hermione上,使用 WebdriverIO v4Mocha.js 运行测试,并使用 LooksSame 进行视觉重组。 它比上面提到的付费工具更加简单和有限,但是对于简单的网站来说,它就足够了。

只要你以自己喜欢的方式生成截图,LooksSame 也可以自己使用

BackstopJS

一个开源的可视化回归工具,运行在 Chrome Headless 上,支持 puppeter 和 CI。

AyeSpy

英国新闻社Times Tooling 团队开发的开源工具。
使用 selenium dockerChrome / Firefox 上创建视觉回归测试。

reg-suit

一个比较图像、生成报告并将其保存在云中的开源库。 如果您想要将可视化回归测试添加到现有的功能测试中,这是非常方便的。 只需添加截屏步骤到您现有的测试流程,并使用它来比较这些截屏。

Differencify

另一个开源的 Chrome Headless 使用 Puppeteer 测试工具与 Jest snapshots 很好的集成。 可以运行在 docker 和生成方便的报告

不需要编码的功能测试工具

在单独的窗口中打开应用程序,并使用浏览器扩展记录与应用程序的手动交互作为测试方案。

使用机器学习帮助您记录和验证测试场景。 跨浏览器并与许多 CI 和协作工具有很好的集成

有免费和支付灵活的计划。

Screener

使用一个 chrome 扩展记录您的测试,有深入的视觉回归报告。 有一些很好的集成例如storybook 不同的 CI 工具和 BrowserStackSauce Labs

Screener不是免费的。

其他类型的工具

如何开始测试流程?

首先选择您喜欢的测试结构和语法(2)断言函数(3)库,然后决定如何运行测试(1)

有些框架,如 JestJasmineTestCafeCypress,提供了所有这些开箱即用的功能。 其中一些只提供了一些功能,应该使用库的组合: (mocha + chai + sinon)

我们还建议创建两个不同的过程。 一个用于运行单元和集成测试,另一个用于功能测试。 这是因为功能测试通常需要更长的时间,特别是在多个不同的浏览器上运行测试套件时。

考虑什么时候适合运行每种类型的测试。例如:

单元 + 集成在代码改动的时候运行,功能测试只有在提交之前进行。

参考文章