代码之家  ›  专栏  ›  技术社区  ›  emenegro

在测试中使用枚举时避免耦合

  •  1
  • emenegro  · 技术社区  · 9 年前

    假设我们有这个枚举

    enum Action: String {
        case doThing
        case doOtherThing
    }
    

    func run(action: Action, block: () -> Void)
    

    现在,我对 run 方法,所以我需要通过一个 Action 这种方式:

    func testActionRun() {
    
        let expect = expectation(description: #function)
        let sut = ActionRunner()
    
        sut.run(action: .doThing) {
            expect.fulfill()
            // Assert something
        }
    
        waitForExpectations(timeout: 0.1, handler: nil)
    }
    

    因为我需要测试其他情况 ActionRunner .doThing 遍布整个测试套件。

    问题是:如果我更改了生产代码并 case doThing case doThatThing 现在我所有的测试套件都失败了,因为没有 案例点处理

    完美的做法是声明一个假人 case 在测试代码中允许

    sut.run(action: .dummyAction) {
    }
    

    但是 enum 不允许这样做,因为它不允许继承或扩展来添加 案例

    我想到的第一个选择是皈依 但这种更改在生产中是不必要的,其唯一目的是在测试代码中完成一些事情。

    2 回复  |  直到 9 年前
        1
  •  2
  •   mokagio codeWhisperer    9 年前

    使用枚举时如何避免耦合是一个棘手的问题。我自己也遇到过几次这样的问题,但都没有确切的答案:/

    您提出的一点是使用协议,这在生产中感觉没有必要。我有点同意这一点,但大多数时候这是必要的邪恶。

    在您展示的示例中,我认为可能对设计进行调整可以解决部分问题。

    尤其是在查看此代码时

    func run(action: Action, block: () -> Void) {
        // ...
    }
    
    func testActionRun() {
    
        let expect = expectation(description: #function)
        let sut = ActionRunner()
    
        sut.run(action: .doThing) {
            expect.fulfill()
            // Assert something
        }
    
        waitForExpectations(timeout: 0.1, handler: nil)
    }
    

    我想到的是你的 Action 指定特定行为。这是您测试 run 方法传递 .doThing 你期望的行为与超车时不同 .doOtherThing

    如果是这样,您有什么理由需要传递动作枚举实例吗 一个动作块 作用

    您可以将定义行为的代码与执行实际操作的代码分离开来,甚至比您已经做的还要多。例如:

    protocol Actionable {
        var action: () -> () { get }
    }
    
    enum Action: Actionable {
        case doThing
        case doOtherThing
    
        var action {
          switch self {
          case .doThing: return ...
          case .doOtherThing: return ...
        }
    }
    
    class ActionRunner {
        func run(actionable: Actionable) {
            actionable.action()
        }
    }
    
    func testActionRun() {
        let expect = expectation(description: #function)
        let sut = ActionRunner()
    
        sut.run(actionable: FakeActionable()) {
            expectation.fulfill()
        }
    
        waitForExpectations(timeout: 0.1, handler: nil)
    }
    
    class FakeActionable: Actionable {
        let action = { }
    }   
    
    func testDoThing() {
        let sut = Action.doThing
    
        sut.action()
    
        // XCTAssert for the expected effect of the action
    }
    

    注:

    ActionRunner 唯一的目的是正确运行给定的 Actionable ,以及 枚举的唯一目的是描述不同的操作应该做什么。

    这个示例代码的功能相当有限,只能运行 () -> () 但你可以在此基础上实现更高级的行为。

        2
  •  1
  •   Adolfo    9 年前

    Action 您的设置函数中的变量 XCTestCase

    import XCTest
    
    class SharingKitTests: XCTestCase {
        var theAction: Action!
    
        override func setUp() {
            super.setUp()
    
            self.theAction = .doThing
    
        }
    }
    

    那么你就可以用这个了 var在所有测试方法中,如果需要更改值,只需在一个地方更改。