SSpec

SSpec 是一个 Swift 的行为驱动开发 (BDD) 框架,它提供了一种很棒的替代标准 XCTest 的方案。

如果你之前使用过行为驱动开发 (BDD),那么 SSpec 对你来说应该很熟悉。 对于那些从未使用过任何 BDD 框架的人来说,它也很容易理解和上手。

Build Status

安装

在你的项目的 Package.swift 文件中添加 SSpec 作为包依赖项

.package(url: "https://github.com/dimakura/SSpec", from: "0.2.3")

你还应该将其放在测试目标的依赖项下

.testTarget(
  name: "MyPackageTests",
  dependencies: [
    "SSpec",
    // other dependencies
  ]),

包管理器会在构建期间为你下载 SSpec

swift build

不要忘记导入 SSPec 才能使用它

import SSpec

开始使用

通过查看这个简单的例子,你可以很容易地了解很多关于 SSpec 的信息

SSpec.run {                   // 1
  it("2 + 2 = 4") {           // 2
    expect(2 + 2).to.eq(4)    // 3
  }
}

XCTAssert(SSpec.hasErrors == false)

运行 SSpec 的标准方法是使用 SSpec.run 方法,就像我们在示例的第一行所做的那样。 在此方法的闭包中,我们定义了我们的测试用例。

第一个(也是唯一的)测试用例在第二行定义。 它有一个标题 ("2 + 2 = 4") 和另一个闭包。

在第三行测试用例的闭包中,你可以看到这个例子实际上试图测试什么。 它确保 2 + 2 确实是 4

你可以在 SSpec.run {...} 的主体中放置任意数量的用例。 你通常应该创建一个单独的 SSpec.run {...} 并在其中定义所有测试。 SSpec 运行所有用例并检查它们的正确性。

如果任何用例失败,SSpec 将打印详细的错误报告,以便你可以轻松找到问题的根源。

带有默认 SSpec 报告器的示例输出如下所示

Sample Output

要以编程方式检测失败,你可以使用 SSpec.hasErrors 属性,该属性默认为 false,但一旦你的规范中至少有一个用例失败,它就会变为 true

你可以使用此属性将失败报告回 XCTest

XCTAssert(SSpec.hasErrors == false)

这不是绝对必要的。 但是,当你的 CI 依赖于 XCTest 的失败时,这是一种检测错误的好方法。

分组测试

两个全局函数 describecontext 有助于对相似的测试进行分组。 它们也有助于记录你的代码。

SSpec.run {
  describe("Person") {
    context("with high salary") {
      let person = Person(salary: .High)

      it("is rich") {
        expect(person.isRich).to.beTrue
      }
    }

    context("with low salary") {
      let person = Person(salary: .Low)

      it("is not rich") {
        expect(person.isRich).to.beFalse
      }

      it("is poor") {
        expect(person.isPoor).to.beTrue
      }
    }
  }
}

你可以互换使用 describecontext。 通常,程序员使用 describe 在高级别对示例进行分组,并使用 context 将它们分解为更详细的示例。

钩子 (Hooks)

如果多个测试使用共享变量或需要相同的初始化代码,你可以方便地使用 before 钩子。 Before 钩子在每个测试之前运行。

如果你需要在运行每个测试后进行清理,你可以使用 after 钩子来完成此操作。

describe("Event") {
  var event: Event!

  before {
    // This code runs before each example
    event = Event(priority: .High, dueDate: Date.tomorrow())
    event.save()
  }

  it("is urgent") {
    expect(event.urgent).to.beTrue
  }

  it("is 1 day away") {
    expect(event.daysLeft).to.eq(1)
  }

  after {
    // This code runs after each example
    Database.clean()
  }
}

请注意,如果不同级别存在 before 钩子,则它们都会从顶层开始执行。 对于 after 钩子,执行从最低级别开始。

describe("Level 1") {
  before {
    // this runs first
  }

  describe("Level 2") {
    before {
      // this runs second
    }

    it("Example") {
      // this runs after two before hooks
    }

    after {
      // this runs first after the "Example"
    }
  }

  after {
    // this runs last
  }
}

匹配器 (Matchers)

通用匹配器

所有值都可以使用 beNil 匹配器来测试是否存在 (presence)

it("is nil") {
  expect(value).to.beNil
}

布尔匹配器

要测试布尔值,请使用 beTruebeFalse 匹配器

it("is true") {
  expect(true).to.beTrue
}
it("is false") {
  expect(false).to.beFalse
}

Equatable 匹配器

待办事项 (TODO)

字符串匹配器

待办事项 (TODO)

数组匹配器

待办事项 (TODO)

Change 匹配器

待办事项 (TODO)

自定义匹配器

Swift 是一种强类型语言。 SSpec 使用类型来扩展匹配器。

假设你创建了自定义类 Person

class Person {
  let name: String
  let salary: Double

  init(_ name: String, _ salary: Double) {
    self.name = name
    self.salary = salary
  }

  var isRich: Bool {
    return salary > 50000
  }

  var isPoor: Bool {
    return !isRich
  }
}

并且希望你的规范像这样

describe("Person") {
  let jack = Person("Jack", 100000)
  let jane = Person("Jane", 20000)

  describe(jack.name) {
    it("is rich") {
      expect(jack).to.beRich
    }
  }

  describe(jane.name) {
    it("is poor") {
      expect(jane).to.bePoor
    }
  }
}

你所需要做的,就是扩展 SSExcept<T>

extension SSExpect where T == Person {
  var beRich: Void {
    // Note that `assert` takes into account negation status.
    // If you need to know negation status use `isNegate` variable.
    assert(
      value!.isRich,                                // `value` is of type `Person?`
      error: "Expected \(value!.name) to be rich",  // There's also standard `valueStr` variable and
                                                    // `toString(value:)` function you can use.
      errorNegate: "Expected \(value!.name) to be poor"
    )
  }

  var bePoor: Void {
    assert(
      value!.isPoor,
      error: "Expected \(value!.name) to be poor",
      errorNegate: "Expected \(value!.name) to be rich"
    )
  }
}

配置

报告器 (Reporter)

默认情况下,SSpec 使用点来报告进度。

还有其他选项可用

SSpec.reporter = .Dot        // default reporter
SSpec.reporter = .Spec       // most document-like reporter
SSpec.reporter = .Progress   // progress bar reporter

贡献

如果您有兴趣为 SSpec 做出贡献,请在此处阅读相关信息