【问题标题】:How to test for order of execution如何测试执行顺序
【发布时间】:2021-06-22 17:58:43
【问题描述】:

我有一个简单的类来存储和CurrentUserRealm 类型的实例,称为CurrentUserStore。此类中有一条规则,即一个对象始终只能有一条记录,因此对于 register(user:) 函数,在添加新用户之前需要先清除该类型的领域。代码如下:

final class CurrentUserStore: CurrentUserStoreProtocol {
    
    private let realmAdapter: RealmAdapterInterface
    
    init(realmAdapter: RealmAdapterInterface) {        
        self.realmAdapter = realmAdapter
    }

    func initiate() throws { ... }        

    func register(user: User) {        
        if !realmAdapter.retrieve(type: User.Type).isEmpty {
            realmAdapter.clear(type: User.Type)
        }
        realmAdapter.add(user)
    }

    func retrieve() -> User { ... }

    func unregister() { ... }

}

protocol RealmAdapterInterface {
    func initiate() throws
    func add<T: Any>(_ entry: T)
    func retrieve<T: Any>(type: T.Type) -> [T]
    func clear<T: Any>(type: T.Type)
}

现在,为了测试这种行为,我有这个:

class CurrentUserStoreTest: XCTestCase {

    func testRegister_WhenAdapterRetrievesTooManyUsers_ShouldClearAdapterFirst() {

        let realmAdapter = MockRealmAdapter(retrieveReturnValue: [User.random(), User.random()])
        
        let store = CurrentUserStore(realmAdapter: realmAdapter)

        let inputtedUser = User.random()
        store.register(inputtedUser)
        
        XCTAssertTrue(realmAdapter.isClearCalled)
        XCTAssertTrue(realmAdapter.clearArgumentType is User.Type)
        XCTAssertTrue(realmAdapter.isAddCalled)
        XCTAssertEqual(inputtedUser, realmAdapter.addArgumentUser)

    }
}

private final class MockRealmAdapter: RealmAdapterInterface {
    private(set) var isInitiateCalled = false
    private(set) var isAddCalled = false
    private(set) var isRetrieveCalled = false
    private(set) var isClearCalled = false

    private(set) var initiateCallCount = 0
    private(set) var addArgumentUser: Any?
    private(set) var retrieveArgumentType: Any.Type?
    private(set) var clearArgumentType: Any.Type?
    
    private let retrieveReturnValue: [Any]

    init(retrieveReturnValue: [Any] = []) {
        self.retrieveReturnValue = retrieveReturnValue
    }
    
    func initiate() throws {
        isInitiateCalled = true
        initiateCallCount += 1
    }
    
    func add<T>(_ entry: T) throws {
        isAddCalled = true
        addArgumentUser = entry
    }
    
    func retrieve<T>(type: T.Type) throws -> [T] {
        isRetrieveCalled = true
        retrieveArgumentType = type
        return retrieveReturnValue
    }
    
    func clear<T>(ype: T.Type) throws {
        isClearCalled = true
        clearArgumentType = type
    }
}

现在,如果我要将 register(user:) 函数变成这样:

    func register(user: User) {        
        if !realmAdapter.retrieve(type: User.Type).isEmpty {
            realmAdapter.add(user)
            realmAdapter.clear(type: User.Type)
        } else {
            realmAdapter.add(user)
        }
    }

测试仍然是绿色的,因为我们仍然会调用适配器类的add(_:)clear(type:) 函数。虽然因为执行的顺序,结果会很不一样。关于如何测试这个执行顺序的任何想法?

谢谢。

【问题讨论】:

  • 如果调用的顺序很重要,你需要让你的 mock 跟踪调用的顺序。
  • @matt 你知道吗?你说得对。我怎么没想到。谢谢。

标签: ios swift unit-testing testing


【解决方案1】:

所以,在我从@matt 的评论中得到一些启示后,我将我的模拟类更改为:

class CurrentUserStoreTest: XCTestCase {

    func testRegister_WhenAdapterRetrievesTooManyUsers_ShouldClearAdapterFirst() {

        let realmAdapter = MockRealmAdapter(retrieveReturnValue: [User.random(), User.random()])
        
        let store = CurrentUserStore(realmAdapter: realmAdapter)

        let inputtedUser = User.random()
        store.register(inputtedUser)
        
        XCTAssertTrue(realmAdapter.isClearCalled)
        XCTAssertTrue(realmAdapter.clearArgumentType is User.Type)
        XCTAssertTrue(realmAdapter.isAddCalled)
        XCTAssertEqual(inputtedUser, realmAdapter.addArgumentUser)
        XCTAssertEqual([.retrieveCalled, .clearCalled, .addCalled], realmAdapter.functionCallOrder)
    }
}

enum RealmAdapterFunctionCall {
    case initiateCalled
    case addCalled
    case retrieveCalled
    case clearCalled
}

private final class MockRealmAdapter: RealmAdapterInterface {
    private(set) var functionCallOrder: [RealmAdapterFunctionCall] = []
    private(set) var isInitiateCalled = false
    private(set) var isAddCalled = false
    private(set) var isRetrieveCalled = false
    private(set) var isClearCalled = false

    private(set) var initiateCallCount = 0
    private(set) var addArgumentUser: Any?
    private(set) var retrieveArgumentType: Any.Type?
    private(set) var clearArgumentType: Any.Type?
    
    private let retrieveReturnValue: [Any]

    init(retrieveReturnValue: [Any] = []) {
        self.retrieveReturnValue = retrieveReturnValue
    }
    
    func initiate() throws {
        isInitiateCalled = true
        functionCallOrder.append(.initiateCalled)
        initiateCallCount += 1
    }
    
    func add<T>(_ entry: T) throws {
        isAddCalled = true
        functionCallOrder.append(.addCalled)
        addArgumentUser = entry
    }
    
    func retrieve<T>(type: T.Type) throws -> [T] {
        isRetrieveCalled = true
        functionCallOrder.append(.retrieveCalled)
        retrieveArgumentType = type
        return retrieveReturnValue
    }
    
    func clear<T>(ype: T.Type) throws {
        isClearCalled = true
        functionCallOrder.append(.clearCalled)
        clearArgumentType = type
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-03-25
    • 1970-01-01
    • 1970-01-01
    • 2018-04-23
    • 2012-10-14
    • 1970-01-01
    • 2011-02-09
    • 2021-07-08
    相关资源
    最近更新 更多