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
Я нашел эту ссылку, которая говорит что-то подобное с Кристиком.
В принципе, вы задаете неправильный вопрос, вы не должны пытаться проверить класс/функции, помеченные как "частные".