前端测试:Part II (单元测试)

简介: 我们在Part1里已经说过,但与那测试就是测试单元的代码,不管这些单元是函数、模块还是类。多数人认为测试应该以单测为主,但我不这么认为,如果你同意也没有问题。我会一遍一遍又一遍地在这一系列文章中强调,你怎测试都行,只要你写了足够多的测试,让你对你的上线有信心就行。

单元测试

我们在Part1里已经说过,但与那测试就是测试单元的代码,不管这些单元是函数、模块还是类。多数人认为测试应该以单测为主,但我不这么认为,如果你同意也没有问题。我会一遍一遍又一遍地在这一系列文章中强调,你怎测试都行,只要你写了足够多的测试,让你对你的上线有信心就行。

不管你写多少单测,单测确实是最好写也最好懂的测试,它们天生具有函数属性。设定一个单元的输入,执行,检查输出(输入可能就是一个函数的参数,输出就是返回值)。

更重要的是你应该提醒自己写代码时要让这些单元彼此隔离,不会相互依赖,这样才能方便单测。

计算器应用中的单元

理论已经够多了,我们看下我们的计算器,源码在这里。这是个React应用,有两个主要的组件,keypaddisplay。显然他们是单元且对其他单元无依赖。但是他们是React层面的单元,以后我会专门讲如何测它们。

如果你已经阅读过代码可能会疑问为啥我不用JSX。原因是我不想使用代码转译。Node和现代的浏览器能够识别ES6,所以为啥不让你写的代码直接运行呢?是的,我的代码不能再IE中运行,但是这只是个demo,所以没问题。在一个真实的项目里,我会添加代码转译的。

译注:最新的代码仓库已经用JSX重构并增加了babel转译,所以这段呵呵了。

总有些代码负责处理当点击了一个数字或者运算符后的运算,哪部分代码完成这部分工作呢?按照现在时髦的做法,我也把我的组件分成了展示型组件(keypaddisplay)和状态型组件(calculator-app)。后一个组件时唯一一个有状态的组件,负责调用计算逻辑,并驱动display展示点击后应该显示啥。

calculator模块

负责计算的逻辑并不在组件中,它是一个单独的模块calculator,并不依赖React。这样的模块是最适合单测的。对UI和IO没有依赖的模块最适合单测。你应该尽力让你更多的业务逻辑以不依赖IO或者UI的形式写成模块。

在前端里,不依赖IO是啥意思呢?不访问文件、数据库… ?不,前端里本来就没有这些。但是还会有Ajax,local storage,DOM 访问,浏览器API访问,对我而言,这些都是IO。

我是如何把计算器的逻辑和组件分开的?在这里,其实很简单,这些逻辑都是算法类的,我把它们放到了calculator模块中。

这个模块非常简单,输入是计算器的当前状态(一个对象)和一个字符(一个数字或者运算符),返回值是计算器的新状态。如果你用过Redux,这个逻辑跟Redux的Reducer差不多。但是如何获取最原始的状态呢?简单,这个模块同样Export了initialState,你可以用它去初始化计算器。计算器的状态并非不透明的,它包含可一个组件display,用来将计算器的内在状态显示出来。

如果你没有耐心去仔细阅读代码,我们这里只看下开头就行,这部分最重要,算法是怎么实现的其实无关紧要。



module.exports.initialState = { display: '0', initial: true }

module.exports.nextState = (calculatorState, character) => {
  if (isDigit(character)) {
    return addDigit(calculatorState, character)
  } else if (isOperator(character)) {
    return addOperator(calculatorState, character)
  } else if (isEqualSign(character)) {
    return compute(calculatorState)
  } else {
    return calculatorState
  }
}

//....

怎么测试呢?我们通常会用一个测试框架。当下最流行的测试框架是Mocha,我们就用它来测试。当然用Jest,Jasmine,Tape或者其他框架都可以。

用Mocha进行单测

所有的测试框架都是类似的——你把测试代码写成函数,测试框架负责执行它们。

npm installMoch后,我们就可以通过npm脚本运行了。当然,命令就是"Mocha"。看下package.json,你会看到:



"scripts": {
...
    "test": "mocha 'test/**/test-*.js' && eslint test lib",
...
},

运行npm test,就会执行以test打头的文件夹里的测试脚本。如果你clone了这个代码仓库,你要先npm install

(顺便说下,把测试脚本放在根目录下的test文件夹中是测试的惯例,如果你想要让别人认为你写测试很专业,那你也应该这么做)

执行后,输出长这个样子。


89834cbbaec36bebe2522daf9cf37554dc947a2c

如果有一个测试没有通过,你会看到刺眼的红色,然后就可以马上修改了。

看一下我们的测试用例:


// test-calculator.js
const {describe, it} = require('mocha')
const {expect} = require('chai')
const calculator = require('../../lib/calculator')

describe('calculator', function () {
  const stream = (characters, calculatorState = calculator.initialState) =>
    !characters
      ? calculatorState
      : stream(characters.slice(1),
               calculator.nextState(calculatorState, characters[0]))

  it('should show initial display correctly', () => {
    expect(calculator.initialState.display).to.equal('0')
  })
  it('should replace 0 in initialState', () => {
    expect(stream('4').display).to.equal('4')
  })
//...

我们先引入mocha,还有它的断言库expect(稍后我们会将啥事断言库)。引入一些我们需要的函数describeit

然后引入我们要测试的模块——calculator

然后就是使用it函数定义的测试用例了,如下:


it('should show initial display correctly', () => {
    expect(calculator.initialState.display).to.equal('0')
})

it函数接受一个字符串参数,用来描述测试用例,另一个参数是一个函数,就是测试本身了。但是it是不能“裸奔”的,需要被包裹在describe函数定义的测试组中。

在测试逻辑中写啥呢?其实啥都可以。在这里我们就是判断了下初始展示的值是不是等于0. 如果不用expect,我们可以这么写:



if (calculator.initialState.display !== '0')
  throw 'failed'

Mocha中如果一个测试不通过,就会抛出一个异常,就是这么简单。但是使用expect让我们可以使用他的一些特性来方便地检查数组、对象的值。

这就是单元测试的主旨了——执行一个或者一组函数(如果你是面向对象测试,则通常实例化一个对象,然后调用它的方法),检查返回的结果是否等于预期结果。

编写可测试的代码

单测里最复杂的不是测试本身,而是分离代码,从而让它们尽可能地变得可测。**可单测的代码就是,对其他模块和IO没有依赖的代码。**这并不简单,因为我们通常习惯于讲业务逻辑和IO,UI耦合起来。但是这个目标仍然是可以达到的,有很多技术。例如,如果你有一段验证表单的代码,把它们分离出来变成一个一个的验证函数,然后对它们进行测试。

测试代码运行在Node环境下?

注意到很重要的一点——单测是运行在Node环境下的。即使计算器应用的代码是跑在浏览器中,我们仍然使用Node去跑我们的测试,包括要上线的代码。

这怎么能行呢?这是因为我们的代码是同构的。这意味着它们能同时运行在浏览器和Node环境中。如果你的代码中没有任何IO操作,这就意味着它并不是只能在浏览器中运行。尤其是,我们的代码使用了require来组织代码,既能被NodeJS识别,又能被Webpack打包(看下package.json,你就会发现使用了webpack)。


"scripts": {
   "build": "webpack && cp public/* dist",
   ...
}

在浏览器环境下单测

顺便提一下,我们可以使用karma来实现在浏览器中运行Mocha。但是我谨认为如果能在Node中运行就在Node中运行(现在你其实很容易写出在两端都能运行的代码),因为无论是运行还是debug都更方便。不编译代码的话,运行起来会更快。

但是不在浏览器中跑测试,我们就不能确认我们的代码能在浏览器中运行。两个环境中的一些细微差别可能会导致一些问题。

下周

上面说的问题就是E2E测试要管的事了——在真实的浏览器环境中测试我们的代码。下周我们将如何写E2E测试。



原文发布时间为:2018年06月27日
原文作者:妖僧风月
本文来源: 掘金  如需转载请联系原作者
相关文章
|
2月前
|
Java 测试技术 开发者
在软件开发中,测试至关重要,尤以单元测试和集成测试为然
在软件开发中,测试至关重要,尤以单元测试和集成测试为然。单元测试聚焦于Java中的类或方法等最小单元,确保其独立功能正确无误,及早发现问题。集成测试则着眼于模块间的交互,验证整体协作效能。为实现高效测试,需编写可测性强的代码,并选用JUnit等合适框架。同时,合理规划测试场景与利用Spring等工具也必不可少。遵循最佳实践,可提升测试质量,保障Java应用稳健前行。
38 1
|
1月前
|
IDE 测试技术 持续交付
Python自动化测试与单元测试框架:提升代码质量与效率
【9月更文挑战第3天】随着软件行业的迅速发展,代码质量和开发效率变得至关重要。本文探讨了Python在自动化及单元测试中的应用,介绍了Selenium、Appium、pytest等自动化测试框架,以及Python标准库中的unittest单元测试框架。通过详细阐述各框架的特点与使用方法,本文旨在帮助开发者掌握编写高效测试用例的技巧,提升代码质量与开发效率。同时,文章还提出了制定测试计划、持续集成与测试等实践建议,助力项目成功。
54 5
|
2月前
|
JSON 测试技术 数据格式
单元测试问题之使用JCode5插件生成测试类如何解决
单元测试问题之使用JCode5插件生成测试类如何解决
70 3
|
2月前
|
测试技术
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
单元测试问题之使用TestMe时利用JUnit 5的参数化测试特性如何解决
27 2
|
2月前
|
IDE 测试技术 持续交付
Python自动化测试与单元测试框架:提升代码质量与效率
随着软件行业的发展,代码质量和效率变得至关重要。自动化测试与单元测试是保证质量、提升效率的关键。Python凭借其简洁强大及丰富的测试框架(如Selenium、Appium、pytest和unittest等),成为了实施自动化测试的理想选择。本文将深入探讨这些框架的应用,帮助读者掌握编写高质量测试用例的方法,并通过持续集成等策略提升开发流程的效率与质量。
45 4
|
2月前
|
测试技术 C# 开发者
“代码守护者:详解WPF开发中的单元测试策略与实践——从选择测试框架到编写模拟对象,全方位保障你的应用程序质量”
【8月更文挑战第31天】单元测试是确保软件质量的关键实践,尤其在复杂的WPF应用中更为重要。通过为每个小模块编写独立测试用例,可以验证代码的功能正确性并在早期发现错误。本文将介绍如何在WPF项目中引入单元测试,并通过具体示例演示其实施过程。首先选择合适的测试框架如NUnit或xUnit.net,并利用Moq模拟框架隔离外部依赖。接着,通过一个简单的WPF应用程序示例,展示如何模拟`IUserRepository`接口并验证`MainViewModel`加载用户数据的正确性。这有助于确保代码质量和未来的重构与扩展。
30 0
|
2月前
|
测试技术 Java Spring
Spring 框架中的测试之道:揭秘单元测试与集成测试的双重保障,你的应用真的安全了吗?
【8月更文挑战第31天】本文以问答形式深入探讨了Spring框架中的测试策略,包括单元测试与集成测试的有效编写方法,及其对提升代码质量和可靠性的重要性。通过具体示例,展示了如何使用`@MockBean`、`@SpringBootTest`等注解来进行服务和控制器的测试,同时介绍了Spring Boot提供的测试工具,如`@DataJpaTest`,以简化数据库测试流程。合理运用这些测试策略和工具,将助力开发者构建更为稳健的软件系统。
39 0
|
2月前
|
前端开发 JavaScript 测试技术
React 与前端自动化测试也太重要啦!各种测试框架助力确保应用质量,快来开启优质开发之旅!
【8月更文挑战第31天】随着前端技术的发展,React 成为了构建用户界面的热门选择。然而,随着应用复杂性的增加,确保应用质量变得至关重要。本文介绍了前端自动化测试的重要性,并详细综述了常用的测试框架如 Jest、Enzyme 和 Cypress,以及如何通过它们进行高效的 React 组件测试。通过遵循最佳实践,如编写可维护的测试用例、覆盖关键场景、集成 CI/CD 流程和进行性能测试,可以显著提高应用的稳定性和可靠性。
43 0
|
2月前
|
Java 测试技术 API
SpringBoot单元测试快速写法问题之复杂的业务逻辑设计有效的单元测试如何解决
SpringBoot单元测试快速写法问题之复杂的业务逻辑设计有效的单元测试如何解决
|
2月前
|
Java 测试技术 API
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决
SpringBoot单元测试快速写法问题之计算测试用例的分支覆盖率如何解决

热门文章

最新文章

下一篇
无影云桌面