1. 概述

ScalaTest是scala生态系统中最流行和灵活的测试工具,可以测试scala、js、java代码。

2. ScalaTest的特性

a. ScalaTest的核心是套件(suite),即0到多个测试的集合
b. 测试可以是含有一个名称的任意内容,该名称可以用来启动、待处理或取消,也可表示成功或失败等
c. trait Suite声明run和其他"生命周期"的方法,这些方法定义编写和执行测试的默认方式;"生命周期"方法可被重写,以定制测试如何编写和运行
d. ScalaTest提供继承Suite的样式traits,并且重写生命周期方法来支持不同的测试类型。它提供了混合(mixin)特性,重写生命周期方法,以满足特定测试需求
e. 你可以通过组合Suite样式和混合traits来定义测试类;可以通过组合Suite实例来定义测试套件。

3. Maven依赖

Maven项目中增加ScalaTest,只需引入如下依赖即可
<dependency>
<groupId>org.scalatest</groupId>
<artifactId>scalatest_2.11</artifactId>
<version>3.0.8</version>
<scope>test</scope>
</dependency>

4. 选取测试样式的不成文约定

a. 推荐为每个项目选择一组满足团队的测试样式,同时保持项目代码的一致性
b. 推荐为单元测试选择一个主样式,而验收测试选择另一样式
c. 一般情况下,推荐使用FlatSpec样式用于单元测试和集成测试,FeatureSpec用于验收测试。
注意:选择的样式只是表明测试声明的外观,无论选择哪种样式,ScalaTest中的其他内容,均以相同的方式工作

5. 样式Trait

(1) FunSuite和XUnit类似,可以轻松编写描述性测试名称,自然地编写集中测试,并生成类似规范输出,促进相关利益者沟通。
(2) FlatSpec的结构类似于XUnit,但是测试名称必须写成规定样式,如X should Y, A must be等
(3) FunSpec类似于Ruby的RSpec工具,对于偏好BDD的团队来说,FunSpec的嵌套和温和的结构化文本指南(使用describe和it)为编写规范式测试提供了极好的通用选择。
(4) 对于来资源specs或spec2的团队,WordSpec将会感觉很熟悉。WordSpec在如何编写文本方面非常规范,因此非常适合于希望在规范文本上强制执行高度管理的团队。
(5) FreeSpec在如何编写规范文档中方面相对自由
(6) PropSpec适合想要在属性检查方面专门编写测试的团队,当选择不同的样式特征作为主要单元测试样式时,也是编写偶尔测试矩阵的好选择
(7) FeatureSpec主要用于验收测试,包括促进程序员与非程序员一起工作以确定验收要求的过程
(8) RefSpec允许将测试类定义为方法,与将测试表示为函数的样式类相比,每个测试保存一个函数文字。更少的函数文字转换为更快的编译时间和更少的生成的类文件,这可以帮助最小化构建时间。
因此,在构建时间受到关注的大型项目中以及通过静态代码生成器以编程方式生成大量测试时,使用Spec可能是一个不错的选择。

6. 定义基类

(1) 为工程创建你最常使用混合特性的抽象基类,而非重复的复制代码来混合相同的trait,如:
a. 创建抽象基类
import org.scalatest._
abstract class UnitSpec extends FlatSpec with Matchers with OptionValues with Inside with Inspectors
b. 继承抽象类
import org.scalatest._
class MySpec extends UnitSpec{
// 测试类
}

7. 编写第一个测试

(1) 使用ScalaTest时,定义一个类,且继承一个样式类如FlatSpec
(2) 在FlatSpec中的每个测试由句子构成,该句子指定了一些所需行为和一个测试它的代码块。
a. 该句子需要一个主题,如" A Stack"
b. 一个动词,如should, must, can
示例:"A Stack" should "pop values in last-in-first-out order"
c. 如果多个测试的主题相同,可以使用it来指代之前的主题,如:
it should "throw NoSuchElementException if an empty stack is popped"
d. 句子后面需要增加"in",其后紧跟着以{}括起来的测试代码示例:

package com.ws.spark.study.scalatest

import org.scalatest.FlatSpec

import scala.collection.mutable

class StackSpec extends FlatSpec{

  "A Stack" should "pop values in last-in-first-out order" in {
val stack = new mutable.Stack[Int]
stack.push(1)
stack.push(2)
assert(stack.pop() == 2)
assert(stack.pop() == 1)
} // it 表示之前的主题 "A Stack", 适用于主题相同的情况
it should "throw NoSuchElementException if an empty stack is popped" in {
val emptyStack = new mutable.Stack[Int]
assertThrows[NoSuchElementException]{
emptyStack.pop()
}
}
}

8. 使用断言

(0) 参考:

package com.ws.spark.study.scalatest

import org.scalatest.FlatSpec

import scala.collection.mutable

class AssertionTest extends FlatSpec{

  /********************************* assert macro *************************************/
/**************************************************/
val left = 2
val right = 1
// 将会打印" 2 did not equal 1 "错误
// assert(left == right)
/**************************************************/
val a = 1
val b = 2
val c = 3
val d = 4
val xs = List(a, b, c)
val num = 1.0 // 打印"1 did not equal 2, and 3 was not greater than or equal to 4"
// assert(a == b || c >=d )
// List(1, 2, 3) did not contain 4
// assert(xs.contains(4))
// "help" started with "h", but "goodbye" did not end with "y"
// assert("help".startsWith("h") && "goodbye".endsWith("y"))
// 1.0 was not instance of scala.Int
// assert(num.isInstanceOf[Int])
// Some(2) was not empty
// assert(Some(2).isEmpty)
/**************************************************/
// 对于不认识的表达式,assert打印string类型信息,并且增加"was false"
// scala.None.isDefined was false
// assert(None.isDefined) // AssertsionTest.this.xs.exists(((i: Int) => i.>(10))) was false
// assert(xs.exists(i => i > 10))
/**************************************************/
val attempted = 2
// assert(attempted == 1, "Execution was attempted " + left + " times instead of 1 time") /********************************* Expected results *************************************/
val x = 5
val y = 2
// Expected 2, but got 3
// assertResult(2) {
// x - y
// } /********************************* Forcing failures *************************************/
// fail()
// fail("I've got a bad feeling about this") /********************************* Achieving success *************************************/
succeed /********************************* Expected exceptions *************************************/
val s = "hi"
// 当charAt抛出索引越界异常,assertThrows将返回Succeed
// 当charAt正常结束或返回另一个异常时,assertThrows将会立即结束,且抛出TestFailedException
assertThrows[IndexOutOfBoundsException] {
s.charAt(-1) // Result type: Assertion
} val caught = intercept[IndexOutOfBoundsException] {
s.charAt(-1) // Result Type: IndexOutOfBoundsException
}
// caught.getMessage: String index out of range: -1
assert(caught.getMessage.indexOf("-1") != -1) /********************************* Checking that a snippet of code does or does not compile *************************************/
assertDoesNotCompile("val a: String = 1")
assertTypeError("val s: String = 1")
assertCompiles("val a: Int = 1") /********************************* Assumptions *************************************/
val m = 10
val n = 10
assume(m === n, ", m must equal n") /********************************* Forcing cancelations *************************************/
// cancel()
// cancel("Can't run the test because no internet connection was found") /********************************* Forcing cancelations *************************************/
withClue("this is a clue"){
assertThrows[IndexOutOfBoundsException] {
"hi".charAt(-1)
} assertThrows[NoSuchElementException]{
val stack = new mutable.Stack[Int]
stack.pop()
}
}
}

(1) ScalaTest在任意类型的trait中默认均有三种断言:
a. assert: 通用断言
b. assertResult: 区分预期值与实际值
c. assertThrows: 确保代码块抛出期望异常
(2) ScalaTest中的断言在trait Assertions中定义,也提供了如下方法:
a. assume: 有条件的取消测试
b. fail: 无条件测试失败
c. cancel: 无条件取消测试
d. succeed: 无条件测试成功
e. intercept: 确保代码块抛出期望异常,然后对异常进行断言
f. assertDoesNotCompile: 确保代码块不编译
g. assertCompiles: 确保代码编译
h. assertTypeError: 确保代码由于类型错误而不被编译
i. withClue: 当失败时增加更多信息
(3) assert宏(macro)
a. assert宏的工作原理是识别传递给assert的表达式AST中的模式。对于有限的公共表达式集,给出一条错误信息,等效于ScalaTest的matcher表达式提供的信息。
b. 对于无法识别的表达式,则会打印string信息,并且在其后增加"was false"
c. 也可以增加错误额外信息,通过在assert方法的第二个参数增加
(4) 期望值
a. 通过assertResult实现期望值的断言,使用方法为assertResult的参数位置为期望值,其后跟着花括号包裹的代码,代码执行结果为指定的期望值
(5) 强制失败
a. 如果想测试失败,可以直接写fail(),也可以将错误信息增加到fail参数中
(6) 成功实现
a. succeed可以用于异步测试中没有以Future[Assertion]或Assertion结尾的类型错误
(7) 期望异常
a. ScalaTest提供了两个方法来检测方法抛出的指定异常:assertThrows和intercept。intercept与assertThrows类似,不同之处在于intercept不返回Succeed类型,而是返回捕获的异常
(8) 检测代码块是否编译
a. 当创建库时期望某些潜在错误不被编译,降低库的错误性,可以使用assertDoesNotCompile
b. 当且仅当类型错误时,为了确保代码块不被编译,可以使用assertTypeError。而语法错误时,仍将抛出TestFailedException
c. 为了确保代码块必须编译,可以使用assertCompiles
(9) 假设
a. 在测试前使用assume方法,当不满足条件时,测试将会退出
b. 当不满足条件时,assume抛出TestCanceledException, 而assert抛出
c. assume方法也自带参数,可以在异常时增加额外信息
(10) 强制取消
a. cancel方法和fail方法类似,不同在于cancel抛出TestCanceledException,而fail抛出TestFailedException
(11) 获取线索
a. assert和assertResult自带线索参数, 而intercept没有
b. 如若在调用assertThrows抛出的失败异常的详细信息中,获取相同的线索,需要使用withClue
c. withClue方法仅在混合ModifiableMessages trait的异常类型详情中附加线索信息。

9. "标记"测试

(1) ScalaTest允许定义任意测试类别,将测试"标记"为属于这些类别,并基于该标记过滤要运行的测试(如有的测试执行时间较长)
(2) ScalaTest默认支持ignore标记,可以"短暂"使一个测试不执行。在FlatSpec样式中,可以将it或in替代为ignore
(3) ScalaTest支持自定义"标记"测试,在FlatSpec中,可以在in之前将抽象类org.scalatest.tag扩展到taggedas的对象传递给它。类tag接收一个string参数,作为名称

10. 测试装置

(1) 当多个测试共用相同的装置(如文件, 套接字, 数据库连接等),需要避免测试中的重复装置代码。
(2) ScalaTest推荐了3种减少重复代码的方法:
1) 当不同的测试需要不同的装置时,使用Scala重构
a. get-fixture-methods: 重构extract方法可以为你在每个所需测试中提供可变装置对象的新实例,但结束时不会清理
参考:com.ws.spark.study.scalatest.fixtures.GetFixtureTest
b. fixture-context-objects: 将装置的方法和属性放置到trait中,可以将trait混合,进而为每个测试提供所需的新创建的trait。适用于不同测试中需要可变装置对象的不同组合,且结束时不需要清理
参考:com.ws.spark.study.scalatest.fixtures.FxiturContextTest
c. loan-fixture-methods: 当不同测试需要不同装置,且必须结束时进行清理,可通过loan模式重构重复代码
2) 当大部分测试需要相同装置时,重载withFixture【推荐使用】
a. withFixture(NoArgTest):
该方法允许在大部分测试的开头与结尾执行边缘检测、转换测试结果、重试测试、基于测试名称,标记,或测试数据来做决策。
该方法不适于:
* 不同测试需要不同的装置 => 可使用scala重构
* 装置代码中的异常应终止suite,而非测试失败 => 可使用before-and-after
* 有需要传递到测试中的对象 => 可重载withFixture(OneArgTest)
b. withFixture(OneArgTest): 使用于当需要将相同装置对象作为参数传入大部分测试的场景
3). 当代码失败时,想要中止suite,而非测试失败,可以混合before-and-after trait
a. BeforeAndAfter: 当想要在测试之前和之后执行相同边缘检测,而非在测试开始和结束时,使用此模板
b. BeforeAndAfterEach:在测试之前和之后堆叠trait
注意:
继承BeforeAndAfterEach的stacking traits与实现withFixture的traits的不同之处在于:
a. BeforeAndAfterEach中,初始化与清理代码发生在测试的前后。beforeEach或afterEach异常终止时,将被当做一个SuiteAborted事件
b. withFixture中,初始化和清理代码发生在测试的开始与结束。withFixture异常终止时,将被当做测试失败。

11. 共享测试

(1) 有时需要在不同的fixture对象执行相同的测试,在FlatSpec中,首先将需共享的测试放在行为函数中,这些行为函数将在任何FlatSpec
的构建阶段调用,因此它们所包含的测试将被注册为FlatSpec中的测试。
(2) 注意:当使用共享测试时,suite中的每个测试必须有不同的名字。如果在相同的suite中注册了同名的测试,运行时将会报多个测试注册到同名的测试异常。
在FlatSpec中,较好的解决方法是确保行为函数的每个调用都有一个不同的主题。
例如: "A Stack (when empty)" should "be empty" in { assert(emptyStack.empty) }
如果“should be empty”测试被分解为行为函数,那么只要行为函数的每次调用都在不同主题的上下文中,就可以重复调用它。

参考:

package com.ws.spark.study.scalatest.sharetests

import org.scalatest.FlatSpec

import scala.collection.mutable.ListBuffer

class Stack[T] {

  val MAX = 10
private val buf = new ListBuffer[T] def full: Boolean = buf.size == MAX
def empty: Boolean = buf.isEmpty
def size: Int = buf.size def push(o: T): Unit ={
if(!full) buf.prepend(o)
else throw new IllegalStateException("can't push onto a full stack")
} def pop(): T ={
if(!empty) buf.remove(0)
else throw new IllegalStateException("can't pop an empty stack")
} def peek: T = {
if(!empty) buf.head
else throw new IllegalStateException("can't pop an empty stack")
} override def toString: String = buf.mkString("Stack(", ", ", ")")
} /**
* 1. 有了共享变量,你可以将不同的测试分解为一个行为函数,行为函数中传入stack fixture,进而可以在测试运行中使用。因此在
* 如下有关Stack的FlatSpec中,将调用行为函数多次
*
* 2. 你可以定义一个行为函数,将这些共享测试封装在使用它们的FlatSpec中。但如果想要在不同的FlatSpec之间共享,也可以在混合
* 到使用它们的每个FlatSpec中的单独trait定义它们
*/ trait StackBehaviors{ this: FlatSpec => def nonEmptyStack(newStack: => Stack[Int], lastItemAdded: Int): Unit ={
it should "be non-empty" in {
assert(!newStack.empty)
} it should "return the top item on peek" in {
assert(newStack.peek === lastItemAdded)
} it should "not remove the top item on peek" in {
val stack = newStack
val size = stack.size
assert(stack.peek === lastItemAdded)
assert(stack.size === size)
} it should "remove the top item on pop" in {
val stack = newStack
val size = stack.size
assert(stack.pop === lastItemAdded)
assert(size === stack.size + 1)
}
} def nonFullStack(newStack: => Stack[Int]): Unit ={ it should "not be full" in {
assert(!newStack.full)
} it should "add to the top on push" in {
val stack = newStack
val size = stack.size
stack.push(7)
assert(stack.size === size + 1)
assert(stack.peek === 7)
}
}
} /**
* 1. 给定了如上行为函数,你可以直接调用它们,FlatSpec提供一个DSL,类似于:
* it should behave like nonEmptyStack(stackWithOneItem, lastValuePushed)
* it should behave like nonFullStack(stackWithOneItem)
*
* 2. 【不推荐】
* 如果倾向于使用命令行样式更改fixtures,例如混合BeforeAndAfterEach,并在beforeEach重新设置stack变量,则你可以在变量var
* 的上下文中编写行为函数。此时,不需要传入stack fixture,因为stack fixture已经在行为函数中的范围。代码如下:
* it should behave like nonEmptyStack // assuming lastValuePushed is also in scope inside nonEmptyStack
* it should behave like nonFullStack
*/ class SharedTestSpec extends FlatSpec with StackBehaviors{ // stack fixture创建方法
def emptyStack = new Stack[Int] def fullStack: Stack[Int] = {
val stack = new Stack[Int]
for(i <- 0 until stack.MAX){
stack.push(i)
}
stack
} def stackWithOneItem: Stack[Int] = {
val stack = new Stack[Int]
stack.push(9)
stack
} def stackWithOneItemLessThanCapacity: Stack[Int] = {
val stack = new Stack[Int]
for(i <- 1 to 9){
stack.push(i)
}
stack
} val lastValuePushed = 9 "A Stack (when empty)" should "be empty" in {
assert(emptyStack.empty)
} it should "complain on peek" in {
intercept[IllegalStateException] {
emptyStack.peek
}
} it should "complain on pop" in {
intercept[IllegalStateException]{
emptyStack.pop()
}
} "A Stack (with one item)" should behave like nonEmptyStack(stackWithOneItem, lastValuePushed)
it should behave like nonFullStack(stackWithOneItem) "A Stack (with one item less than capacity)" should behave like nonEmptyStack(stackWithOneItemLessThanCapacity, lastValuePushed)
it should behave like nonFullStack(stackWithOneItemLessThanCapacity) "A Stack (full)" should "be full" in {
assert(fullStack.full)
}
it should behave like nonEmptyStack(fullStack, lastValuePushed)
it should "complain on a push" in {
intercept[IllegalStateException] {
fullStack.push(10)
}
}
}

scalaTest的初步使用的更多相关文章

  1. 移动端之Android开发的几种方式的初步体验

    目前越来越多的移动端混合开发方式,下面列举的大多数我都略微的尝试过,就初步的认识写个简单的心得: 开发方式 开发环境 是否需要AndroidSDK 支持跨平台 开发语言&技能 MUI Win+ ...

  2. CSharpGL(29)初步封装Texture和Framebuffer

    +BIT祝威+悄悄在此留下版了个权的信息说: CSharpGL(29)初步封装Texture和Framebuffer +BIT祝威+悄悄在此留下版了个权的信息说: Texture和Framebuffe ...

  3. Android自定义View初步

    经过上一篇的介绍,大家对于自定义View一定有了一定的认识,接下来我们就以实现一个图片下显示文字的自定义View来练习一下.废话不多说,下面进入我们的正题,首先看一下我们的思路,1.我们需要通过在va ...

  4. 初步认识Node 之Node为何物

    很多人即便是在使用了Node之后也不知道它到底是什么,阅读完本文你应该会有一个初步的.具体的概念了.    Node的目标 提供一种简单的构建可伸缩网络程序的方法.那么,什么是可伸缩网络程序呢?可伸缩 ...

  5. [入门级] 基于 visual studio 2010 mvc4 的图书管理系统开发初步 (二)

    [入门级] 基于 visual studio 2010 mvc4 的图书管理系统开发初步 (二) Date  周六 10 一月 2015 By 钟谢伟 Category website develop ...

  6. 基于C/S架构的3D对战网络游戏C++框架 _05搭建系统开发环境与Boost智能指针、内存池初步了解

    本系列博客主要是以对战游戏为背景介绍3D对战网络游戏常用的开发技术以及C++高级编程技巧,有了这些知识,就可以开发出中小型游戏项目或3D工业仿真项目. 笔者将分为以下三个部分向大家介绍(每日更新): ...

  7. Azure底层架构的初步分析

    之所以要写这样的一篇博文的目的是对于大多数搞IT的人来说,一般都会对这个topic很感兴趣,因为底层架构直接关乎到一个公有云平台的performance,其实最主要的原因是我们的客户对此也非常感兴趣, ...

  8. CozyRSS开发记录14-RSS源管理初步完工

    CozyRSS开发记录14-RSS源管理初步完工 1.添加源的响应 DialogHost.Show有几个版本的重载,加一个DialogClosingEventHandler参数.我们让添加源对话框的添 ...

  9. 初步了解CPU

    了解CPU By JackKing_defier 首先说明一下,本文内容主要是简单说明CPU的大致原理,所需要的前提知识我会提出,但是由于篇幅我不会再详细讲解需要的其他基础知识.默认学过工科基础课. ...

随机推荐

  1. 你不知道的JavaScript LHS 和 RHS 查找

    今天在学习JavaScript的相关知识时接触到了 LHS(Left Hand Side)和 RHS(Right Hand Side)两种对变量查找的方法,之所以JavaScript要查找变量,那就先 ...

  2. java- 泛型类到底是类还是接口,<T extends Comparable>的写法中为什么没有用implements

    java-core P533 public static <T extands Comparable> T min(T[] a ) 这里产生了一个疑问,就是 泛型类的英文是generic ...

  3. MySQL安装到最后一步未响应MySQL Server Instance Configuration Wizard

    MySQL安装到最后一步未响应 第一个方法: 打开C盘,并且显示隐藏文件,然后在C盘下就能找到一个文件夹叫  "ProgamData"  ,打开它,删除里面的  "mys ...

  4. Django内置email发送邮件

    ###Django内置email发送邮件 ####1.首先在settings.py文件设置相关参数 ```python STATIC_URL = '/static/' # 设置邮件域名 EMAIL_H ...

  5. Mongo Backup

    #!/bin/sh # This script is run on every mongo node. However, it checks to see if this node is the pr ...

  6. re模块中的非贪婪匹配

    python的re模块中有贪婪匹配和非贪婪匹配之分,当使用*时会匹配零个或多个,使用+时会匹配一个或多个.当使用?在前边特殊符号前时会进行非贪婪匹配,匹配零个或者一个,今天主要讨论非贪婪匹配中存在的坑 ...

  7. JMeter性能测试工具

    1.官网资源 地址:https://jmeter.apache.org/download_jmeter.cgi window下载zip版本 options配置-选择语言-简体(jmeter.prope ...

  8. 老男孩Python==基础数据类型考试题

    转载 # Python基础数据类型考试题 # 考试时间:两个半小时 满分100分(80分以上包含80分及格) # 一,基础题. # 1, 简述变量命名规范(3分) # 1.变量由字母.数字.下划线任意 ...

  9. Codeforces Round #571 (Div. 2)

    A. Vus the Cossack and a Contest 签. #include <bits/stdc++.h> using namespace std; int main() { ...

  10. tarjan缩点——在农场万圣节Trick or Treat on the Farm

    一个房间能到另一个房间,有向图,奶牛从自己编号(1到n)的点出发,如果回到以前到过的点就停止,问每头奶牛可以经过几个点: 情况分两种, 一,奶牛在环上,能走的是环的大小,二,一条链连接一个环,大小是链 ...