Swift - 单元测试私有变量和方法

时间:2016-05-24 18:29:14

标签: swift unit-testing

我试图测试课程,但我对测试什么感到困惑。这是我想要进行单元测试的课程:

class CalculatorBrain {

    private var accumulator = 0.0

    func setOperand(operand: Double) {
        accumulator = operand
    }

    var result: Double {
        return accumulator
    }

    private var operations: Dictionary<String, Operation> = [
        "=" : .Equals,

        "π" : .Constant(M_PI),
        "e" : .Constant(M_E),

        "±" : .UnaryOperation({ (op1: Double) -> Double in return -op1 }),
        "√" : .UnaryOperation(sqrt ),
        "cos": .UnaryOperation(cos),

        "+" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 + op2 }),
        "−" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 - op2 }),
        "×" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 * op2 }),
        "÷" : .BinaryOperation({ (op1: Double, op2: Double) -> Double in return op1 / op2 })
    ]

    private enum Operation {
        case Constant(Double)
        case UnaryOperation((Double) -> Double)
        case BinaryOperation((Double, Double) -> Double)
        case Equals
    }

    func performOperation(symbol: String) {
        if let operation = operations[symbol] {
            switch operation {
            case .Constant(let value):
                accumulator = value
            case .UnaryOperation(let function):
                accumulator = function(accumulator)
            case .BinaryOperation(let function):
                executePendingBinaryOperation()
                pendingBinaryOperation = PendingBinaryOperationInfo(binaryOperation: function, firstOperand: accumulator)
            case .Equals:
                executePendingBinaryOperation()
            }
        }
    }

    private var pendingBinaryOperation: PendingBinaryOperationInfo?

    private struct PendingBinaryOperationInfo {
        var binaryOperation: (Double, Double) -> Double
        var firstOperand: Double
    }

    private func executePendingBinaryOperation() {
        if let pending = pendingBinaryOperation {
            accumulator = pending.binaryOperation(pending.firstOperand, accumulator)
            pendingBinaryOperation = nil
        }
    }
}

对于上面的代码,什么是好的测试。

是否值得测试字典operations中的每一项操作(+, - ,*,/等)?

是否值得测试私有方法?

7 个答案:

答案 0 :(得分:28)

您无法使用@testable在Swift中测试私有方法。您只能测试标记为internalpublic的方法。正如文档所说:

  

注意:@testable仅提供“内部”功能的访问权限;   即使在“文件”之外,“私有”声明也不会在其文件之外显示   使用@testable。

了解更多here

答案 1 :(得分:23)

按定义进行单元测试是黑盒测试,这意味着您不必关心您测试的设备的内部。您最感兴趣的是根据您在单元测试中给出的输入来查看单位输出的内容。

现在,通过输出,我们可以断言几件事:

  • 方法的结果
  • 作用于对象后的对象状态,
  • 与对象具有依赖关系的交互

在所有情况下,我们只对公共界面感兴趣,因为它是与世界其他地方进行通信的界面。

私人物品不需要进行单元测试,因为任何私人物品都是由公共物品间接使用的。诀窍是编写足够的测试来锻炼公众成员,以便完全覆盖私人成员。

另外,要记住的一件重要事情是单元测试应该验证单元规格,而不是它的实现。验证实现细节会增加单元测试代码与测试代码之间的紧密耦合,这有一个很大的缺点:如果测试的实现细节发生变化,那么单元测试也可能需要更改,这样减少了对该段代码进行单元测试的好处。

答案 2 :(得分:10)

虽然我同意不测试EOF的东西,我宁愿只测试公共接口,但有时我需要在隐藏的类中测试一些东西(比如复杂的状态机)。对于这些情况,您可以做的是:

private

然后你可以这样做:

import Foundation

public class Test {

    internal func testInternal() -> Int {
        return 1
    }

    public func testPublic() -> Int {
        return 2
    }

    // we can't test this!        
    private func testPrivate() -> Int {
        return 3
    }
}

// this extension should be in its own file
// won't ship with production code thanks to #if DEBUG
// add a good comment with "WHY this is needed "
#if DEBUG
extension Test {
    public func exposePrivate() -> Int {
        return self.testPrivate()
    }
}
#endif

我完全理解这是一个黑客攻击。但知道这个技巧可以在未来保存你的培根。不要滥用这个技巧。

答案 3 :(得分:1)

迭戈的答案很聪明,但有可能走得更远。

  1. 进入您的项目编辑器,并通过复制调试配置来定义新的测试配置。
  2. 编辑方案的“测试”操作,以使构建配置为“测试”。
  3. 现在编辑测试目标的构建设置,以为“测试”配置"TESTING"定义其他“活动编译条件”值。

现在您可以说#if TESTING,与单纯的DEBUG不同。

例如,我用它来声明只有测试才能看到的初始化程序。

答案 4 :(得分:1)

我认为实际上不需要测试私人成员。 但是,如果您想使用private的{​​{1}}成员(属性和方法),则可以使用UnitTest

Protocol

并尝试在同一文件(目标类文件)中扩展协议。

Protocol PrivateTestable {
  associatedtype PrivateTestCase  
  var privateTestCase: PrivateTestCase {get}
}

然后您可以在extension CalculatorBrain: PrivateTestable { struct PrivateTestCase { private let target: CalculatorBrain var pendingBinaryOperation: PendingBinaryOperationInfo? { return target.pendingBinaryOperation } init(target: CalculatorBrain) { self.target = target } } var privateTestable: PrivateTestCase { return PrivateTestCase(target: self) } } 中使用pendingBinaryOperation

UnitTest

答案 5 :(得分:0)

我发现this link与Cristik有类似之处。

基本上,你提出错误的问题,你不应该试图测试标有&#34; private&#34;的类/功能。

答案 6 :(得分:0)

简短的回答是你不能。私密部分无法测试。

然而,我认为“你不应该”是一个有效的答案。我曾经是这样想的,但现实生活中的场景比我们想象的要复杂得多。在某些时候,我需要编写一个 FileScanner 类作为框架的一部分,它符合只有 Scanner 函数的 scan(filename: String) 协议。当然FileScanner.scan(filename: String)必须是public,但是支持scan的函数呢?

正如我在上面的评论中提到的,我想:

  1. 尽可能保持界面干净,并且
  2. 尽可能将访问级别限制为私密

这意味着我不想公开其他类不使用的其他函数。我真的希望在函数级别有一个 @testable 修饰符(像 @discardable 等一样工作)但由于它在 Swift 中并不存在,不幸的是我们只有 2 个选项:

  1. 仅为 scan 编写单元测试,这是大多数人的建议。这需要单元测试包中的大量输入文件(不一定是 Target,因为我只在没有 Xcode 的情况下使用 SPM,而且它只是一个 Tests 目录),并且很难创建特定案例用于个人功能。取决于 scan 的复杂程度,这不是一个很好的方法。
  2. 公开其他私有函数。我最终采用了这种方法,并制定了一个约定,如果一个函数没有任何修饰符,我们假设它是 internal 并且可以被同一包(目标)中的其他文件使用,而不是 {{ 1}}。但是,如果我们专门将其标记为 public 等,则意味着我们只想使其成为 internal func,并且永远不应被同一包中的其他类使用。

所以,我的结论是,即使你还不能在 Swift 中测试私有方法和属性,我认为这是 Swift 的一个限制,而不是一个无效的用例。