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
        }
    }
}

Для кода выше, что было бы хорошим тестом.

Стоит ли тестировать каждую операцию (+, -, *,/и т.д.) В operations словаря?

Стоит ли тестировать частные методы?

Ответ 1

Вы не можете тестировать частные методы в Swift с помощью @testable. Вы можете только тестировать методы, отмеченные как internal и public. Как говорят документы:

Примечание. @Testable обеспечивает доступ только для "внутренних" функций; "частные" объявления не видны за пределами их файла даже при использовании @testable.

Подробнее здесь

Ответ 2

Единичное тестирование по определению - проверка черного ящика, что означает, что вам не нужны внутренние устройства тестируемого устройства. Вы в основном заинтересованы в том, чтобы узнать, какой вывод устройства основан на входах, которые вы им даете в модульном тесте.

Теперь по выходу мы можем утверждать несколько вещей:

  • результат метода
  • состояние объекта после действия на него,
  • взаимодействие с зависимостями, которые объект имеет

Во всех случаях нас интересует только публичный интерфейс, так как тот, который общается с остальным миром.

Частным вещам не нужно иметь модульные тесты просто потому, что любой частный элемент косвенно используется публичным. Трюк состоит в том, чтобы написать достаточно тестов, которые используют публичных членов, чтобы частные были полностью охвачены.

Кроме того, важно помнить, что модульное тестирование должно проверять спецификации устройства, а не его реализацию. Проверка деталей реализации добавляет плотную связь между кодом модульного тестирования и тестируемым кодом, который имеет большой недостаток: если тестируемая деталь реализации изменяется, тогда, вероятно, потребуется также изменить unit тест, и это уменьшит преимущество, единичный тест для этой части кода.

Ответ 3

Хотя я согласен не тестировать 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

Тогда вы можете сделать это:

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())
    }
}

Я прекрасно понимаю, что это взломать. Но знание этого трюка может спасти ваш бекон в будущем или нет. Не злоупотребляйте этим трюком.

Ответ 4

Я нашел эту ссылку, которая говорит что-то подобное с Кристиком.

В принципе, вы задаете неправильный вопрос, вы не должны пытаться проверить класс/функции, помеченные как "частные".