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

bre*_*top 36 unit-testing swift

我正在尝试测试一个课程,但我对测试什么感到困惑.这是我要进行单元测试的类:

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
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

是否值得测试私有方法?

Dan*_*sko 29

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

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

在这里阅读更多


Cri*_*tik 23

根据定义,单元测试是黑盒测试,这意味着您不关心您测试的单元的内部.您主要想知道基于您在单元测试中给出的输入的单位输出是什么.

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

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

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

私有东西不需要单独测试,因为任何私有项目都是由公共项目间接使用的.诀窍是编写足够的测试来锻炼公众成员,以便完全覆盖私人成员.

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

  • 单元测试不一定是黑盒测试.http://stackoverflow.com/a/24627489/1202880 http://stackoverflow.com/a/9893055/1202880 (3认同)

Die*_*che 11

虽然我同意不测试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
    }
}

// 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
Run Code Online (Sandbox Code Playgroud)

然后你可以这样做:

import XCTest
@testable import TestTests

class TestTestsTests: XCTestCase {

    func testExample() {
        let sut = Test()

        XCTAssertEqual(1, sut.testInternal())
    }

    func testPrivateExample() {
        let sut = Test()

        XCTAssertEqual(3, sut.exposePrivate())
    }
}
Run Code Online (Sandbox Code Playgroud)

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

  • 这很好地暴露了我的单例的私有初始化程序,因此我可以为每个测试创建一个新实例。单例并不是测试的最佳选择,但是鉴于已经编写了代码,现在最好使单例可测试。我将不同意答案中的代码注释,因为Swift 4要求扩展必须在同一文件中才能从扩展内部访问私有功能。 (2认同)

mat*_*att 7

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

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

现在可以说#if TESTING,有别于单纯DEBUG

例如,我使用它来声明只有测试才能看到的初始值设定项。