Swift Unit Testing Compare Arrays and Dictionaries

Whilst writing unit tests in Swift I have found it necessary to compare large arrays, the contents of which could be arrays, dictionaries, strings, integers or floats.

XCTAssertEquals() does not support the comparison of arrays or dictionaries, so I wrote this set of methods to iterate through the large data structures and compare the leaves. 

    func compareArrays(arrayA : [Any], arrayB : [Any]) {
        for i in 0..<arrayA.count {
            if let subArrayA = arrayA[i] as? [Any],
                let subArrayB = arrayB[i] as? [Any] {
                compareArrays(arrayA: subArrayA, arrayB: subArrayB)
            } else if let dictionaryA = arrayA[i] as? [String : Any],
                let dictionaryB = arrayB[i] as? [String : Any] {
                compareDictionarys(dictionaryA: dictionaryA, dictionaryB: dictionaryB)
            } else {
                compareObjects(objectA: arrayA[i], objectB: arrayB[i])
            }
        }
    }
    
    func compareDictionarys(dictionaryA : [String : Any], dictionaryB : [String : Any]) {
        for key in dictionaryA.keys {
            if let subArrayA = dictionaryA[key] as? [Any],
                let subArrayB = dictionaryB[key] as? [Any] {
                compareArrays(arrayA: subArrayA, arrayB: subArrayB)
            } else if let dictionaryA = dictionaryA[key] as? [String : Any],
                let dictionaryB = dictionaryB[key] as? [String : Any] {
                compareDictionarys(dictionaryA: dictionaryA, dictionaryB: dictionaryB)
            } else if let objectA = dictionaryA[key],
                let objectB = dictionaryB[key] {
                compareObjects(objectA: objectA, objectB: objectB)
            } else {
                let typeStrA = String(describing:type(of:dictionaryA[key]))
                let typeStrB = String(describing:type(of:dictionaryB[key]))
                XCTFail("Cannot unwrap optionals, which appear to be \(typeStrA) and \(typeStrB)")
            }
        }
    }
    
    func compareObjects(objectA : Any, objectB : Any) {
        if let stringA = objectA as? String,
            let stringB = objectB as? String {
            XCTAssertEqual(stringA, stringB)
        } else if let intA = objectA as? Int,
            let intB = objectB as? Int {
            XCTAssertEqual(intA, intB)
        } else if let floatA = objectA as? Float,
            let floatB = objectB as? Float {
            XCTAssertEqual(floatA, floatB)
        } else {
            let typeStrA = String(describing:type(of:objectA))
            let typeStrB = String(describing:type(of:objectB))
            XCTFail("Cannot ascertain type of \(objectA) or \(objectB), which appear to be \(typeStrA) and \(typeStrB)")
        }
    }
Last Updated on Friday, 08 December 2017 18:19

XCUIElement+GentleSwipe.swift

Recently I found that XCUIElement.swipeUp() was far too violent, swiping way past the element in the UI I wanted my test to tap, so I wrote an extension to XCUIElement that swiped gently using XCUIElement.press(forDuration:thenDragTo:)

//
//  XCUIElement+GentleSwipe.swift
//
//  Created by Robin Spinks on 11/10/2017.
//

import Foundation
import XCTest

extension XCUIElement
{
    enum direction : Int {
        case Up, Down, Left, Right
    }
    
    func gentleSwipe(_ direction : direction) {
        let half : CGFloat = 0.5
        let adjustment : CGFloat = 0.25
        let pressDuration : TimeInterval = 0.05
        
        let lessThanHalf = half - adjustment
        let moreThanHalf = half + adjustment
        
        let centre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: half))
        let aboveCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: lessThanHalf))
        let belowCentre = self.coordinate(withNormalizedOffset: CGVector(dx: half, dy: moreThanHalf))
        let leftOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: lessThanHalf, dy: half))
        let rightOfCentre = self.coordinate(withNormalizedOffset: CGVector(dx: moreThanHalf, dy: half))
        
        switch direction {
        case .Up:
            centre.press(forDuration: pressDuration, thenDragTo: aboveCentre)
            break
        case .Down:
            centre.press(forDuration: pressDuration, thenDragTo: belowCentre)
            break
        case .Left:
            centre.press(forDuration: pressDuration, thenDragTo: leftOfCentre)
            break
        case .Right:
            centre.press(forDuration: pressDuration, thenDragTo: rightOfCentre)
            break
        }
    }
}
Last Updated on Thursday, 12 October 2017 11:23

Reasons Not to Use .xib Files

Change Tracking

There are dates and codes in .xib files, which are changed every time they are viewed them in xcode, which can obscure actual changes in git or similar change control systems.

Merging changes is also very difficult with .xib files - especially storyboards, which are basically massive xml wrappers containing multiple .xib files.

Readability

.xib files are huge xml files, which can be worked out, but are generally very difficult to read for those not extremely familiar with them.

This makes identification of changes difficult because it is difficult to visualise the layout or see what the changes actually are (if any), without loading the .xib into interface builder; for example, when browsing the git repo using a browser.

To see all the settings in a .xib file you have to load it into xcode and check all the little settings for every object.

All non-default settings are clearly laid out when objects are defined in code.

Flexibility

Object sizing and placement defined in code can be very flexible, such as using percentages of the view, or other objects in the view, for sizing or placement.

Debugging

It's difficult to find little inconsistencies in .xib files that may be causing a problem as you have to select every little piece in Interface Builder and carefully inspect all it's little options, whereas any non-default settings are easily identifiable in code.

Autolayout

Autolayout is possible in code, so we aren't losing anything by not using storyboards

Visualisation

It could be argued that storyboards are essential for visualising the layout of views, but there are numerous advantages to designing your UI layout in a graphic design application, such as photoshop, which will provide you with all the placement metrics you need, including autolayout behaviour for different screen sizes and rotations.

Last Updated on Wednesday, 11 October 2017 16:05