Using Swift’s Unit Test Framework

In an earlier blog post I wrote about my solution to one of the homework assignments for Stanford University’s iOS programming course. My conclusion was that, while the code worked, it seemed a little bit too C-like and not really embracing some of the elegance of the Swift programming language.

My intention is to rewrite the code and see if I can get it looking somewhat more Swifty. But of course in doing that I don’t want to break it – there’s a lot of different cases the CalculatorBrain has to handle. This is a classic scenario that can be solved using automated unit testing.

When you create a new application project in XCode, as well as the main build target you get given a test target with boilerplate code for using XCode’s unit testing framework. It is simple to use – just add new functions to the test class, and use XCTAsserts to ensure the right results.

XCode lets you run tests either individually or as a group. One way of doing this is to click on the diamonds that the IDE marks in the source file. This screenshot shows an example.

XCode Unit Test Screenshot

The diamonds turn green when the test has been successful, red when failed, and grey when they have not yet been executed.

The unit tests for most of the Calculator assignment are in the code snippet below. The tests are simple in structure. In the test class I create a single instance of CalculatorBrain, and in the setUp() code that gets called before each separate test the CalculatorBrain.clear() method is called to reset its state. One thing I haven’t got is a test case to verify the clear() method – for completeness I really ought to.

class SWCalculatorTests: XCTestCase {
    
    var brain = CalculatorBrain ()
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
        brain.clear()
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    func testOperations() {
        brain.pushOperand(3)
        brain.pushOperand(5)
        brain.performOperation("+")
        let result = brain.evaluate()
        
        XCTAssert(result != nil)
        XCTAssert(result == 8)
    }

    func testMinus() {
        brain.pushOperand(3)
        brain.pushOperand(5)
        brain.performOperation("-")
        let result = brain.evaluate()
        XCTAssert(result == -2)
    }
    
    func testSqrt() {
        brain.pushOperand(9)
        let result = brain.performOperation("√")
        XCTAssert(result == 3)
    }
    
    func testDescriptionInfix() {
        brain.pushOperand(3)
        brain.pushOperand(5)
        brain.performOperation("-")
        XCTAssert(brain.description=="3.0 - 5.0")
    }
    
    func testDescriptionStackCombos() {
        brain.pushOperand(10)
        brain.performOperation("√")
        brain.pushOperand(3)
        brain.performOperation("+")
        XCTAssert(brain.description == "√(10.0) + 3.0")

        brain.clear()
        brain.pushOperand(3)
        brain.pushOperand(5)
        brain.performOperation("+")
        brain.performOperation("√")
        XCTAssert(brain.description=="√(3.0 + 5.0)")

        brain.clear()
        brain.pushOperand(3)
        brain.pushOperand(5)
        brain.pushOperand(4)
        brain.performOperation("+")
        brain.performOperation("+")
        XCTAssert(brain.description == "3.0 + 5.0 + 4.0")

        brain.clear()
        brain.pushOperand(3)
        brain.pushOperand(5)
        brain.performOperation("√")
        brain.performOperation("+")
        brain.performOperation("√")
        brain.pushOperand(6)
        brain.performOperation("÷")
        XCTAssert(brain.description=="√(3.0 + √(5.0)) ÷ 6.0")
    }
    
    func testDescriptionUnaryFunc() {
        brain.pushOperand(10)
        brain.performOperation("cos")
        XCTAssert(brain.description == "cos(10.0)")
    }
    
    func testDescriptionStackContents() {
        brain.pushOperand(23.5)
        XCTAssert(brain.description == "23.5")
        brain.clear()
        
        brain.pushConstant("π")
        XCTAssert(brain.description == "π")
    }
    
    func testDescriptionMultipleExpressions() {
        brain.pushOperand(3)
        brain.pushOperand(5)
        brain.performOperation("+")
        brain.performOperation("√")
        brain.pushConstant("π")
        brain.performOperation("cos")
        XCTAssert(brain.description == "√(3.0 + 5.0),cos(π)")
    }


So that’s it – pretty simple really. I have focussed the testing on the description() method, rather than the correct results from the calculator. That can be a job for if ever the calculation engine needs modification for new requirements.

My next job now is to refactor the CalculatorBrain code, ensuring I re-run the unit test framework each time I make a change.

Leave a Reply