`tearDown` 调用是必要的吗?
Is `tearDown` calling necessary?
有件事考虑我很久了。假设我们已经编写了测试 class:
final class BearerTokenManagerTests: XCTestCase {
private var bearerTokenManager: BearerTokenManager!
private var bearerTokenProvider: BearerTokenProvider!
private var stubKeyValueStore: KeyValueStoreDummyStub!
private var scheduler: TestScheduler!
private var disposeBag: DisposeBag!
override func setUp() {
super.setUp()
stubKeyValueStore = KeyValueStoreDummyStub()
bearerTokenProvider = BearerTokenProvider(keyValueStore: stubKeyValueStore)
bearerTokenManager = BearerTokenManager(bearerTokenProvider: bearerTokenProvider)
scheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
}
override func tearDown() {
stubKeyValueStore = nil
bearerTokenProvider = nil
bearerTokenManager = nil
scheduler = nil
disposeBag = nil
super.tearDown()
}
func test_bearerToken_observeChanges() {
let bearerToken = scheduler.createObserver(BearerTokenManagerType.BearerToken.self)
bearerTokenManager.bearerToken
.bind(to: bearerToken)
.disposed(by: disposeBag)
scheduler.start()
// every update should be saved in key value store
bearerTokenManager.update(bearerToken: "123")
XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "123")
bearerTokenManager.update(bearerToken: "456")
XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "456")
bearerTokenManager.update(bearerToken: "789")
XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "789")
// every udpate should be emited
XCTAssertEqual(bearerToken.events, [
.next(0, nil), // by default (on start) token equal to nil
.next(0, "123"),
.next(0, "456"),
.next(0, "789"),
])
}
}
tearDown
打扫卫生有必要吗?
为什么我认为没有必要:
- 在每个下一个测试用例之前
setUp
重置所有内容。
- 当
BearerTokenManagerTests
中的测试结束时,一切都应该解除分配
为什么我不确定
- 假设“当
BearerTokenManagerTests
中的测试结束时,一切都应该解除分配”可能是错误的
- 我担心
RxScheduler
副作用
- 我还不知道
有人可以分享他们的经验吗?你清理 tearDown
中的东西吗?重置 setUp
中的属性是否足够?
快速回答
根据这篇文章:https://qualitycoding.org/xctestcase-teardown/
XCTest creates a new XCTestCase
instance for every individual test invocation but doesn't deinit
any of them after completion.
演示
我在 Xcode 11.7 中创建了演示应用程序,行为仍然相同。
正在测试的系统
import UIKit
var counter = 0
class ViewController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
counter += 1;
print("Created ViewController, currently living: \(counter)")
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
deinit {
counter -= 1;
print("Destroyed ViewController, currently living: \(counter)")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
带有 tearDown()
的测试用例
class ViewControllerTests: XCTestCase {
var vc: ViewController!
override func setUp() {
super.setUp()
vc = ViewController()
}
override func tearDown() {
vc = nil
super.tearDown()
}
func test_a() {
XCTAssert(true == true)
}
func test_b() {
XCTAssert(true == true)
}
func test_c() {
XCTAssert(true == true)
}
}
输出:
Test Suite 'ViewControllerTests' started at 2020-09-13 14:44:15.889
Test Case '-[DemoTests.ViewControllerTests test_a]' started.
Created ViewController, currently living: 1
Destroyed ViewController, currently living: 0
Test Case '-[DemoTests.ViewControllerTests test_a]' passed (0.001 seconds).
Test Case '-[DemoTests.ViewControllerTests test_b]' started.
Created ViewController, currently living: 1
Destroyed ViewController, currently living: 0
Test Case '-[DemoTests.ViewControllerTests test_b]' passed (0.000 seconds).
Test Case '-[DemoTests.ViewControllerTests test_c]' started.
Created ViewController, currently living: 1
Destroyed ViewController, currently living: 0
Test Case '-[DemoTests.ViewControllerTests test_c]' passed (0.000 seconds).
Test Suite 'ViewControllerTests' passed at 2020-09-13 14:44:15.891.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'sdadasTests.xctest' passed at 2020-09-13 14:44:15.892.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'Selected tests' passed at 2020-09-13 14:44:15.892.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.004) seconds
没有tearDown()
的测试用例
class ViewController2Tests: XCTestCase {
var vc: ViewController!
override func setUp() {
vc = ViewController()
}
func test_a() {
XCTAssert(true == true)
}
func test_b() {
XCTAssert(true == true)
}
func test_c() {
XCTAssert(true == true)
}
}
输出:
Test Suite 'ViewController2Tests' started at 2020-09-13 14:47:43.067
Test Case '-[sdadasTests.ViewController2Tests test_a]' started.
Created ViewController, currently living: 1
Test Case '-[DemoTests.ViewController2Tests test_a]' passed (0.001 seconds).
Test Case '-[DemoTests.ViewController2Tests test_b]' started.
Created ViewController, currently living: 2
Test Case '-[DemoTests.ViewController2Tests test_b]' passed (0.000 seconds).
Test Case '-[DemoTests.ViewController2Tests test_c]' started.
Created ViewController, currently living: 3
Test Case '-[DemoTests.ViewController2Tests test_c]' passed (0.000 seconds).
Test Suite 'ViewController2Tests' passed at 2020-09-13 14:47:43.070.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'sdadasTests.xctest' passed at 2020-09-13 14:47:43.070.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'Selected tests' passed at 2020-09-13 14:47:43.071.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.004) seconds
长答案
就像您在没有 tearDown()
初始化的示例中看到的那样 setUp()
不会将新对象分配给相同的 属性。每个测试都会创建单独的实例,并且在完成后不会 deinit
它。只有整个 XCTestCase
个实例的末尾将被释放。
在小项目中,它可能无关紧要。
但是如果你在一个 XCTestCase
中有很多测试并且你在 setUp()
中创建大量数据(例如占用大量内存的存根)你应该考虑使用 tearDown()
因为每个测试都会保留自己的 setUp()
数据副本,直到整个 XCTestCase
完成,您最终可能会遇到内存限制问题。
有件事考虑我很久了。假设我们已经编写了测试 class:
final class BearerTokenManagerTests: XCTestCase {
private var bearerTokenManager: BearerTokenManager!
private var bearerTokenProvider: BearerTokenProvider!
private var stubKeyValueStore: KeyValueStoreDummyStub!
private var scheduler: TestScheduler!
private var disposeBag: DisposeBag!
override func setUp() {
super.setUp()
stubKeyValueStore = KeyValueStoreDummyStub()
bearerTokenProvider = BearerTokenProvider(keyValueStore: stubKeyValueStore)
bearerTokenManager = BearerTokenManager(bearerTokenProvider: bearerTokenProvider)
scheduler = TestScheduler(initialClock: 0)
disposeBag = DisposeBag()
}
override func tearDown() {
stubKeyValueStore = nil
bearerTokenProvider = nil
bearerTokenManager = nil
scheduler = nil
disposeBag = nil
super.tearDown()
}
func test_bearerToken_observeChanges() {
let bearerToken = scheduler.createObserver(BearerTokenManagerType.BearerToken.self)
bearerTokenManager.bearerToken
.bind(to: bearerToken)
.disposed(by: disposeBag)
scheduler.start()
// every update should be saved in key value store
bearerTokenManager.update(bearerToken: "123")
XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "123")
bearerTokenManager.update(bearerToken: "456")
XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "456")
bearerTokenManager.update(bearerToken: "789")
XCTAssertEqual(stubKeyValueStore.string(forKey: "BearerToken"), "789")
// every udpate should be emited
XCTAssertEqual(bearerToken.events, [
.next(0, nil), // by default (on start) token equal to nil
.next(0, "123"),
.next(0, "456"),
.next(0, "789"),
])
}
}
tearDown
打扫卫生有必要吗?
为什么我认为没有必要:
- 在每个下一个测试用例之前
setUp
重置所有内容。 - 当
BearerTokenManagerTests
中的测试结束时,一切都应该解除分配
为什么我不确定
- 假设“当
BearerTokenManagerTests
中的测试结束时,一切都应该解除分配”可能是错误的 - 我担心
RxScheduler
副作用 - 我还不知道
有人可以分享他们的经验吗?你清理 tearDown
中的东西吗?重置 setUp
中的属性是否足够?
快速回答
根据这篇文章:https://qualitycoding.org/xctestcase-teardown/
XCTest creates a new
XCTestCase
instance for every individual test invocation but doesn'tdeinit
any of them after completion.
演示
我在 Xcode 11.7 中创建了演示应用程序,行为仍然相同。
正在测试的系统
import UIKit
var counter = 0
class ViewController: UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
counter += 1;
print("Created ViewController, currently living: \(counter)")
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
deinit {
counter -= 1;
print("Destroyed ViewController, currently living: \(counter)")
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
带有 tearDown()
的测试用例
class ViewControllerTests: XCTestCase {
var vc: ViewController!
override func setUp() {
super.setUp()
vc = ViewController()
}
override func tearDown() {
vc = nil
super.tearDown()
}
func test_a() {
XCTAssert(true == true)
}
func test_b() {
XCTAssert(true == true)
}
func test_c() {
XCTAssert(true == true)
}
}
输出:
Test Suite 'ViewControllerTests' started at 2020-09-13 14:44:15.889
Test Case '-[DemoTests.ViewControllerTests test_a]' started.
Created ViewController, currently living: 1
Destroyed ViewController, currently living: 0
Test Case '-[DemoTests.ViewControllerTests test_a]' passed (0.001 seconds).
Test Case '-[DemoTests.ViewControllerTests test_b]' started.
Created ViewController, currently living: 1
Destroyed ViewController, currently living: 0
Test Case '-[DemoTests.ViewControllerTests test_b]' passed (0.000 seconds).
Test Case '-[DemoTests.ViewControllerTests test_c]' started.
Created ViewController, currently living: 1
Destroyed ViewController, currently living: 0
Test Case '-[DemoTests.ViewControllerTests test_c]' passed (0.000 seconds).
Test Suite 'ViewControllerTests' passed at 2020-09-13 14:44:15.891.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'sdadasTests.xctest' passed at 2020-09-13 14:44:15.892.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'Selected tests' passed at 2020-09-13 14:44:15.892.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.004) seconds
没有tearDown()
的测试用例
class ViewController2Tests: XCTestCase {
var vc: ViewController!
override func setUp() {
vc = ViewController()
}
func test_a() {
XCTAssert(true == true)
}
func test_b() {
XCTAssert(true == true)
}
func test_c() {
XCTAssert(true == true)
}
}
输出:
Test Suite 'ViewController2Tests' started at 2020-09-13 14:47:43.067
Test Case '-[sdadasTests.ViewController2Tests test_a]' started.
Created ViewController, currently living: 1
Test Case '-[DemoTests.ViewController2Tests test_a]' passed (0.001 seconds).
Test Case '-[DemoTests.ViewController2Tests test_b]' started.
Created ViewController, currently living: 2
Test Case '-[DemoTests.ViewController2Tests test_b]' passed (0.000 seconds).
Test Case '-[DemoTests.ViewController2Tests test_c]' started.
Created ViewController, currently living: 3
Test Case '-[DemoTests.ViewController2Tests test_c]' passed (0.000 seconds).
Test Suite 'ViewController2Tests' passed at 2020-09-13 14:47:43.070.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'sdadasTests.xctest' passed at 2020-09-13 14:47:43.070.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.003) seconds
Test Suite 'Selected tests' passed at 2020-09-13 14:47:43.071.
Executed 3 tests, with 0 failures (0 unexpected) in 0.002 (0.004) seconds
长答案
就像您在没有 tearDown()
初始化的示例中看到的那样 setUp()
不会将新对象分配给相同的 属性。每个测试都会创建单独的实例,并且在完成后不会 deinit
它。只有整个 XCTestCase
个实例的末尾将被释放。
在小项目中,它可能无关紧要。
但是如果你在一个 XCTestCase
中有很多测试并且你在 setUp()
中创建大量数据(例如占用大量内存的存根)你应该考虑使用 tearDown()
因为每个测试都会保留自己的 setUp()
数据副本,直到整个 XCTestCase
完成,您最终可能会遇到内存限制问题。