web-dev-qa-db-fra.com

Swift - Test unitaires des variables et méthodes privées

J'essaie de tester une classe, mais je ne sais pas trop quoi tester. Voici la classe que je veux tester unitaire:

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

Pour le code ci-dessus, quels seraient de bons tests.

Vaut-il la peine de tester chaque opération (+, -, *, /, etc.) dans le dictionnaire operations?

Vaut-il la peine de tester les méthodes privées?

36
breaktop

Vous ne pouvez pas tester des méthodes privées dans Swift using @testable. Vous ne pouvez tester que les méthodes marquées internal ou public. Comme le disent les docs:

Remarque: @testable fournit un accès uniquement aux fonctions "internes"; Les déclarations "privées" ne sont pas visibles en dehors de leur fichier même lorsque vous utilisez @testable.

En savoir plus ici

29
Daniel Galasko

Les tests unitaires par définition sont des tests de boîte noire, ce qui signifie que vous ne vous souciez pas des composants internes de l'unité que vous testez. Vous êtes principalement intéressé à voir quelle est la sortie unitaire en fonction des entrées que vous lui donnez dans le test unitaire.

Maintenant, par les sorties, nous pouvons affirmer plusieurs choses:

  • le résultat d'une méthode
  • l'état de l'objet après avoir agi dessus,
  • l'interaction avec les dépendances de l'objet

Dans tous les cas, nous nous intéressons uniquement à l'interface publique, puisque c'est celle qui communique avec le reste du monde.

Les éléments privés n'ont pas besoin de tests unitaires simplement parce que tout élément privé est indirectement utilisé par un élément public. L'astuce consiste à écrire suffisamment de tests pour exercer les membres du public afin que les tests privés soient entièrement couverts.

En outre, une chose importante à garder à l'esprit est que les tests unitaires doivent valider les spécifications de l'unité, et non sa mise en œuvre. La validation des détails de l'implémentation ajoute un couplage étroit entre le code de test unitaire et le code testé, ce qui présente un gros inconvénient: si le détail de l'implémentation testée change, il est probable que le test unitaire devra également être modifié, ce qui diminue l'avantage d'avoir test unitaire pour ce morceau de code.

24
Cristik

Bien que je sois d'accord pour ne pas tester les choses private, et que je préfère tester uniquement l'interface publique, j'ai parfois eu besoin de tester quelque chose dans une classe qui était caché (comme une machine d'état complexe). Pour ces cas, vous pouvez:

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

Ensuite, vous pouvez le faire:

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

Je comprends parfaitement qu'il s'agit d'un hack. Mais connaître cette astuce peut sauver votre bacon à l'avenir ou non. N'abusez pas de cette astuce.

11
Diego Freniche

J'ai trouvé ce lien qui dit quelque chose de similaire avec Cristik.

Fondamentalement, vous posez la mauvaise question, vous ne devriez pas chercher à tester la classe/les fonctions marquées "privé".

0
Joey Zhou