前端测试方法

最近在学校的《系统分析与设计》一课的大作业上,由于我担任的是测试工程师的角色,因此小小的研究了一些前端和后端的测试到底要怎么做。本文着重于前端测试方法。

1. 什么是测试?

我把测试定义成:是一段检测你的应用代码(也叫“生产代码”)是否按预期执行的代码。有些人称之为 TDD(Test-Driven Development 或者 Test-Driven Design),但是 TDD 是一种特定的测试方法,它先写测试,然后用测试来驱动产品的设计和实现。

2. 为什么测试?

或者说为什么要将测试以编码形式保留下来(并且一般做模块管理),来做自动化测试。因为我觉得测试肯定是需要的,这个不用解释吧。。。不然如何保证程序没bug?不能保证无bug就不能叫出去了吧。。。

我觉得是因为在项目中代码变动太多,由于使用敏捷开发的方式(事实上就算是瀑布那种,我估计也要)原先的代码和功能经常会有变动。我们要保证每次变动后不会产生新的或旧的bug。那么比起每次都重新测试(以某种非自动化方法进行),显然一个很好的办法就是将测试过程编码,那么每次重新测试只要运行测试代码(换言之,自动化)就可以了。

3. 测试的类型

测试包括单元测试、验收测试、集成测试、端到端测试、组件测试和服务测试等多种。

我认为叫什么名字都不重要,因为各种测试的定义都不难理解。所有的测试都分布于“测试光谱”中,光谱的一头是单元测试,另一头是端到端测试。

测试光谱

光谱的一端——单元测试

顾名思义,代码以单元为单位进行测试。那么什么是单元呢?这就不同的编程语言的定义不一样了。它可以是一个函数、一个模块、一个包或者一个类,甚至是一个对象(比如 JavaScript 和 Scala 语言)。在 JavaScript 中,通常是以类或者模块作为一个单元。

以单元进行测试很重要的一点是其测试是独立的。对于一些场景这种测试非常适合,比如算法、某些功能性函数(如字符串中有多少字符)和包含一组验证性功能的类等场景。

这些场景下进行独立的单元测试非常容易,因为他们不依赖于其他单元。但是假设一个单元依赖于其他单元怎么办?可以有两种做法:两个单元一起测试,或者 mock 另一个单元。

什么是 mock?下面我们举例来解释:

假设一个模块是一个单元,模块中包含了 writeSumToFile 这个函数,函数接收两个数字参数,并把他们的和写入文件中。

这里注意,这个模块自己并没有做写文件操作。写文件操作是在另一个单元 fileSumWriter 中做的。

那么为了测试第一个单元,我们既可以传一个真实的 fileSumWriter 进来,也可以模拟(mock)一个写文件操作(并不用真的实现写操作)。

如果我们传递一个 mock 到这个函数,那么这个单元测试,当然可以叫做“单元测试”,因为没有涉及到其他单元。

光谱的另一端——端到端测试

上面介绍了以一个单元为单位的测试。现在介绍端到端测试——测试整个应用。测试过程中,应用的所有配置会设置成和生产环境一样,应用中的一切都会被测试到。

这两个是“测试光谱”的两端。上面提到的其他测试方法,都是分布在这两个极端中间。他们的基本思路是逐步扩大测试范围,被测试的代码越来越多,mock 的代码越来越少。

4. 单元测试

其实对于单元测试来讲,难的并不是单元测试本身,而是分离代码的艺术,把代码尽量分离成单元可测的模块。单元可测的代码一般都是不依赖于其他模块、不依赖于 I/O 的代码。这是比较困难的,大多数人都倾向于把逻辑代码、I/O 代码和 UI 代码写到一起。困难是困难,但不是说做不到,有很多技巧可以使用,比如你的代码中有一些验证字段,那么你就可以把验证代码组织到一起形成函数,再对这个验证函数进行测试。

用 Mocha 进行单元测试

所有的测试框架都类似,写测试代码调用被测函数,通过测试框架运行他们,其中运行它们的代码通常叫做“runner”。使用mocha进行测试,测试成功的结果大概会是这样子:

使用mocha和nodejs进行前端测试虽然听起来有点怪,毕竟我们的前端代码最终是跑在浏览器的。但是nodejs和前端的逻辑代码其实都是javascript写的,这也是我们项目中使用nodejs的一个好处。

如果实在不放心,或者项目中不使用nodejs的话,我们们还可以使用另一个测试框架,Karma 。使用它可以在浏览器中运行 Mocha 代码,但是这里表达一下我的浅见:单元测试能在 Node 下运行就在 Node 下运行,因为很容易执行和 debug(当然现在在浏览器中执行也很方便)。并且如果代码不需要转译的话,执行的也非常快。

但是我们的代码没有在浏览器中测试确实是个问题,因为我们并不真正地知道代码在浏览器中运行会是什么样子。浏览器中的 JS 执行环境和 NodeJS 环境可能会有微妙的差别。

5. 端到端测试

我们将使用端到端测试整个应用。

是否需要像单元测试那样,测试各种组合呢?并不是,我们已经在单元测试中测试过了,端到端测试不是检查某个单元是否 ok,而是把它们放到一起,检查还是否能够正确运行。

这里推荐selenium-webdriver,这个包能够将打开浏览器,访问url,与dom进行操作等自动化。因此可以想象我们的端到端测试不再需要人工的在浏览器上点点点,而是可以通过编码做到自动化!!

代码实例如下:

const {
    prepareDriver, cleanupDriver
} = require('../utils/browser-automation')

//...
describe('calculator app', function () {
    let driver
        ...
    before(async() = > {
        driver = await prepareDriver()
    })
    after(() = > cleanupDriver(driver))

    it('should work', async
    function () {
        await driver.get('http://localhost:8080')
        //...
    })
})

before 中,准备好驱动,在 after 中把它清理掉。准备好驱动后,会自动运行浏览器(Chrome,稍后会看到),清理掉以后会关闭浏览器。

const webdriver = require('selenium-webdriver')
const chromeDriver = require('chromedriver')
const path = require('path')

const chromeDriverPathAddition = `: $ {
    path.dirname(chromeDriver.path)
}`

exports.prepareDriver = async() = > {
    process.on('beforeExit', () = > this.browser && this.browser.quit())
    process.env.PATH += chromeDriverPathAddition

    return await new webdriver.Builder()
        .disableEnvironmentOverrides()
        .forBrowser('chrome')
        .setLoggingPrefs({
        browser: 'ALL',
        driver: 'ALL'
    })
        .build()
}

exports.cleanupDriver = async(driver) = > {
    if (driver) {
        driver.quit()
    }
    process.env.PATH = process.env.PATH.replace(chromeDriverPathAddition, '')
}

前两行引入了 webdriver 和我们使用的浏览器驱动 chromedriver。Selenium Webdriver 的工作原理是通过 API(第一行中引入的 selenium-webdriver)调用浏览器,这依赖于被调浏览器的驱动。本例中被调浏览器驱动是 chromedriver,在第二行引入。

chrome driver 不需要在机器上装了 Chrome,实际上在你运行 npm install 的时候,已经装了它自带的可执行 Chrome 程序。接下来 chromedriver 的目录名需要添加进环境变量中,见代码中的第 9 行,在清理的时候再把它删掉,见代码中第 22 行。

设置了浏览器驱动以后,我们来设置 web driver,见代码的 11 – 15 行。因为 build 函数是异步的,所以它也使用 await。到现在为止,驱动部分就已经设置完毕了。

接下来这段代码非常令人激动,因为它展示了如何将以往的点点点操作自动化。

const digit4Element = await driver.findElement(By.css('.digit-4'))
const digit2Element = await driver.findElement(By.css('.digit-2'))
const operatorMultiply = await driver.findElement(By.css('.operator-multiply'))
const operatorEquals = await driver.findElement(By.css('.operator-equals'))

await digit4Element.click()
await digit2Element.click()
await operatorMultiply.click()
await digit2Element.click()
await operatorEquals.click()

await retry(async() = > {
    const displayElement = await driver.findElement(By.css('.display'))
    const displayText = await displayElement.getText()

    expect(displayText).to.equal('84')
})

6. 集成测试

集成测试与端到端测试有些类似,大部分时候我们会涉及到dom操作,并且这部分操作很难抽离出来。我们当然也可以像端到端测试那样使用webdriver,但是这样测试的速度会慢很多。这里推荐一个非常棒的包:jsdom。用它自己的话说:这是在 NodeJS 中实现的 DOM。使用它的显著的好处是我们可以在 Node 中运行前端测试。

链接放这了,我就不再赘述它的使用方式了。

github of JSDOM

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值