如何在 struct/class 中存根 Swift "Trait/Mixin" 方法进行测试
How to stub Swift "Trait/Mixin" method in struct/class for testing
我最近了解到如何通过创建协议并使用默认实现扩展该协议来将 "Traits/Mixins" 添加到 Swift 中的 struct/class。这很棒,因为它允许我添加功能来查看控制器,而不必向所述视图控制器添加一堆帮助对象。我的问题是,如何存根这些默认实现提供的调用?
这是一个简单的例子:
protocol CodeCop {
func shouldAllowExecution() -> Bool
}
extension CodeCop {
func shouldAllowExecution() -> Bool {
return arc4random_uniform(2) == 0
}
}
struct Worker : CodeCop {
func doSomeStuff() -> String {
if shouldAllowExecution() {
return "Cop allowed it"
} else {
return "Cop said no"
}
}
}
如果我想写两个测试,一个验证 String "Cop allowed it" 在 CodeCop 不允许执行时由 doStuff() 返回,另一个测试验证 String "Cop said no"当 CodeCop 不允许执行时由 doStuff() 返回。
我不确定这是否是您正在寻找的,但是您可以 测试此行为而不更新您的代码 的一种方法是通过以下方式更新您的项目结构:
将 CodeCop
协议保存在一个文件中(比方说 CodeCop.swift
)并将扩展代码添加到另一个文件中(CodeCop+shouldAllowExecution.swift)
虽然 CodeCop.swift
链接到您的主要目标和测试目标,但 CodeCop+shouldAllowExecution.swift
仅在主要目标中。
创建一个测试文件 CodeCopTest.swift
,仅在测试目标中可用,其中包含 shouldAllowExecution
的另一个默认实现,这将帮助您 运行 您的测试。
这是一个潜在的 CodeCopTest.swift
文件
import XCTest
fileprivate var shouldCopAllowExecution: Bool = false
fileprivate extension CodeCop {
func shouldAllowExecution() -> Bool {
return shouldCopAllowExecution
}
}
class PeopleListDataProviderTests: XCTestCase {
var codeCop: CodeCop!
override func setUp() {
super.setUp()
codeCop = CodeCop()
}
override func tearDown() {
codeCop = nil
super.tearDown()
}
func testWhenCopAllows() {
shouldCopAllowExecution = true
XCTAssertEqual(codeCop.doSomeStuff(), "Cop allowed it", "Cop should say 'Cop allowed it' when he allows execution")
}
func testWhenCopDenies() {
shouldCopAllowExecution = false
XCTAssertEqual(codeCop.doSomeStuff(), "Cop said no", "Cop should say 'Cop said no' when he does not allow execution")
}
}
这很简单,只需在您的测试目标中编写一个名为 CodeCopStub
的附加协议即可,该协议继承自 CodeCop
:
protocol CodeCopStub: CodeCop {
// CodeCopStub declares a static value on the implementing type
// that you can use to control what is returned by
// `shouldAllowExecution()`.
//
// Note that this has to be static, because you can't add stored instance
// variables in extensions.
static var allowed: Bool { get }
}
然后扩展 CodeCopStub
的 shouldAllowExecution()
方法(继承自 CodeCop
)到 return 一个取决于新静态变量 allowed
的值。这将覆盖任何实现 CodeCopStub
.
的类型的原始 CodeCop
实现
extension CodeCopStub {
func shouldAllowExecution() -> Bool {
// We use `Self` here to refer to the implementing type (`Worker` in
// this case).
return Self.allowed
}
}
此时你剩下要做的就是让 Worker
符合 CodeCopStub
:
extension Worker: CodeCopStub {
// It doesn't matter what the initial value of this variable is, because
// you're going to set it in every test, but it has to have one because
// it's static.
static var allowed: Bool = false
}
您的测试将如下所示:
func testAllowed() {
// Create the worker.
let worker = Worker()
// Because `Worker` has been extended to conform to `CodeCopStub`, it will
// have this static property. Set it to true to cause
// `shouldAllowExecution()` to return `true`.
Worker.allowed = true
// Call the method and get the result.
let actualResult = worker.doSomeStuff()
// Make sure the result was correct.
let expectedResult = "Cop allowed it"
XCTAssertEqual(expectedResult, actualResult)
}
func testNotAllowed() {
// Same stuff as last time...
let worker = Worker()
// ...but you tell it not to allow it.
Worker.allowed = false
let actualResult = worker.doSomeStuff()
// This time, the expected result is different.
let expectedResult = "Cop said no"
XCTAssertEqual(expectedResult, actualResult)
}
请记住,所有这些代码都应该放在您的测试目标中,而不是您的主要目标。通过把它放在你的测试目标中,它的 none 会影响你的原始代码,并且不需要对原始代码进行修改。
我最近了解到如何通过创建协议并使用默认实现扩展该协议来将 "Traits/Mixins" 添加到 Swift 中的 struct/class。这很棒,因为它允许我添加功能来查看控制器,而不必向所述视图控制器添加一堆帮助对象。我的问题是,如何存根这些默认实现提供的调用?
这是一个简单的例子:
protocol CodeCop {
func shouldAllowExecution() -> Bool
}
extension CodeCop {
func shouldAllowExecution() -> Bool {
return arc4random_uniform(2) == 0
}
}
struct Worker : CodeCop {
func doSomeStuff() -> String {
if shouldAllowExecution() {
return "Cop allowed it"
} else {
return "Cop said no"
}
}
}
如果我想写两个测试,一个验证 String "Cop allowed it" 在 CodeCop 不允许执行时由 doStuff() 返回,另一个测试验证 String "Cop said no"当 CodeCop 不允许执行时由 doStuff() 返回。
我不确定这是否是您正在寻找的,但是您可以 测试此行为而不更新您的代码 的一种方法是通过以下方式更新您的项目结构:
将
CodeCop
协议保存在一个文件中(比方说CodeCop.swift
)并将扩展代码添加到另一个文件中(CodeCop+shouldAllowExecution.swift)虽然
CodeCop.swift
链接到您的主要目标和测试目标,但CodeCop+shouldAllowExecution.swift
仅在主要目标中。创建一个测试文件
CodeCopTest.swift
,仅在测试目标中可用,其中包含shouldAllowExecution
的另一个默认实现,这将帮助您 运行 您的测试。
这是一个潜在的 CodeCopTest.swift
文件
import XCTest
fileprivate var shouldCopAllowExecution: Bool = false
fileprivate extension CodeCop {
func shouldAllowExecution() -> Bool {
return shouldCopAllowExecution
}
}
class PeopleListDataProviderTests: XCTestCase {
var codeCop: CodeCop!
override func setUp() {
super.setUp()
codeCop = CodeCop()
}
override func tearDown() {
codeCop = nil
super.tearDown()
}
func testWhenCopAllows() {
shouldCopAllowExecution = true
XCTAssertEqual(codeCop.doSomeStuff(), "Cop allowed it", "Cop should say 'Cop allowed it' when he allows execution")
}
func testWhenCopDenies() {
shouldCopAllowExecution = false
XCTAssertEqual(codeCop.doSomeStuff(), "Cop said no", "Cop should say 'Cop said no' when he does not allow execution")
}
}
这很简单,只需在您的测试目标中编写一个名为 CodeCopStub
的附加协议即可,该协议继承自 CodeCop
:
protocol CodeCopStub: CodeCop {
// CodeCopStub declares a static value on the implementing type
// that you can use to control what is returned by
// `shouldAllowExecution()`.
//
// Note that this has to be static, because you can't add stored instance
// variables in extensions.
static var allowed: Bool { get }
}
然后扩展 CodeCopStub
的 shouldAllowExecution()
方法(继承自 CodeCop
)到 return 一个取决于新静态变量 allowed
的值。这将覆盖任何实现 CodeCopStub
.
CodeCop
实现
extension CodeCopStub {
func shouldAllowExecution() -> Bool {
// We use `Self` here to refer to the implementing type (`Worker` in
// this case).
return Self.allowed
}
}
此时你剩下要做的就是让 Worker
符合 CodeCopStub
:
extension Worker: CodeCopStub {
// It doesn't matter what the initial value of this variable is, because
// you're going to set it in every test, but it has to have one because
// it's static.
static var allowed: Bool = false
}
您的测试将如下所示:
func testAllowed() {
// Create the worker.
let worker = Worker()
// Because `Worker` has been extended to conform to `CodeCopStub`, it will
// have this static property. Set it to true to cause
// `shouldAllowExecution()` to return `true`.
Worker.allowed = true
// Call the method and get the result.
let actualResult = worker.doSomeStuff()
// Make sure the result was correct.
let expectedResult = "Cop allowed it"
XCTAssertEqual(expectedResult, actualResult)
}
func testNotAllowed() {
// Same stuff as last time...
let worker = Worker()
// ...but you tell it not to allow it.
Worker.allowed = false
let actualResult = worker.doSomeStuff()
// This time, the expected result is different.
let expectedResult = "Cop said no"
XCTAssertEqual(expectedResult, actualResult)
}
请记住,所有这些代码都应该放在您的测试目标中,而不是您的主要目标。通过把它放在你的测试目标中,它的 none 会影响你的原始代码,并且不需要对原始代码进行修改。