Agile Swift
Godfrey Nolan
RIIS LLC
Agenda
- Unit Testing intro
- Up and Running
- Swift Flavors
- Unit testing 101
- Tools of the trade
- Mocking
- User Interface testing
Unit Testing Intro
- Hello World
- Benefits
- Testing Pyramid
- UI Testing
Unit Testing Intro
import XCTest
@testable import HelloWorld
class HelloWorldTests: XCTestCase {
func testExample() {
XCTAssertEqual(HelloWorld().text, "Hello, World!")
}
}
struct HelloWorld {
var text = "Hello, World!"
}
Unit Testing Benefits
- Catch more mistakes
- Confidently make more changes
- Built in regression testing
- Extend the life of your codebase
Swift 3.x Flavors
- MacOS
- Xcode, command line and repl
- Linux
- Ubuntu 14.04 and 16.04
- Command line and repl
- No GUI libraries
- Swift Playground on iPad
- More flavors to come?
Interactive Swift
- Good language learning environment
- Options
- Playgrounds
- Repl
Command Line Swift
- Headless Swift
- First class cousin of Xcode Swift
- Package Manager
- Demo swift package init
- Primary option on Linux
- Docker
- AWS Free tier
- Also works on MacOS
Package Manager
Package.swift
import PackageDescription
let package = Package(
name: "HelloWorld"
)
import PackageDescription
let package = Package(
name: "DeckOfPlayingCards",
dependencies: [
.Package(url: "https://github.com/apple/example-package-fisheryates.git", majorVersion: 2),
.Package(url: "https://github.com/apple/example-package-playingcard.git", majorVersion: 3),
]
)
LinuxMain.swift
import XCTest
@testable import HelloWorldTests
XCTMain([
testCase(HelloWorldTests.allTests),
])
HelloWorldTests.swift
import XCTest
@testable import HelloWorld
class HelloWorldTests: XCTestCase {
static var allTests : [(String, (HelloWorldTests) -> () throws -> Void)] {
return [
("testExample", testExample),
]
}
func testExample() {
XCTAssertEqual(HelloWorld().text, "Hello, World!")
}
}
struct HelloWorld {
var text = "Hello, World!"
}
Swift 3.x in Xcode
- More options
- Playgrounds
- Xcode 8.x IDE
Unit Testing 101
- Setup and Teardown
- Performance
- Assertions
- Code Coverage
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Unit Testing 101
Tools of the Trade
- Nimble matchers
- Cuckoo
- Slather
- SwiftFormat
- Jenkins
- SonarQube
import XCTest
import Nimble
@testable import Calculator
class CalculatorTests: XCTestCase {
let resCalc = CalculatorModel()
func testAdd() {
expect(self.resCalc.add(1,1)) == 2
}
func testAddRange() {
expect(self.resCalc.mul(4, 3)).to(satisfyAnyOf(beGreaterThan(10),beLessThan(20)))
}
}
Tools of the Trade
$ sudo gem install cocoapods
$ pod init
$ vi podfile
platform :ios, '9.0'
source 'https://github.com/CocoaPods/Specs.git'
target 'CalculatorTests' do
use_frameworks!
pod 'Quick'
pod 'Nimble', '~> 5.0.0'
end
$ pod install
Tools of the Trade
Tools of the Trade
1. Create your project with model class and test class.
2. Run pod init
3. Edit the generated podfile and add pod “Cuckoo” as a test target.
4. Run pod install.
5. Close the project and reopen the workspace.
6. Click on the project folder then choose Test Target ➤ Build Phases.
7. Click + and choose New Run Script Phase.
8. Add Listing to the Run Script section, making sure to modify the input files that you want to mock.
9. Build the project.
10. Run the tests.
11. Drag and drop GeneratedMocks.swift into the test section.
12. Run the mocked tests.
platform :ios, '9.0'
use_frameworks!
target 'DateWithCuckooTests' do
pod 'Cuckoo',
:git => 'https://github.com/SwiftKit/Cuckoo.git',
:branch => 'swift-3.0'
end
Tools of the Trade
Tools of the Trade
# Define output file; change "${PROJECT_NAME}Tests" to your test's
root source folder, if it's not the default name
OUTPUT_FILE="./${PROJECT_NAME}Tests/GeneratedMocks.swift"
echo "Generated Mocks File = ${OUTPUT_FILE}"
# Define input directory; change "${PROJECT_NAME}" to your project's root
source folder, if it's not the default name
INPUT_DIR="./${PROJECT_NAME}"
echo "Mocks Input Directory = ${INPUT_DIR}"
# Generate mock files; include as many input files as you'd like to create mocks for
${PODS_ROOT}/Cuckoo/run generate --testable "${PROJECT_NAME}" \
--output "${OUTPUT_FILE}" \
"${INPUT_DIR}/FileName1.swift" \
"${INPUT_DIR}/FileName2.swift" \
"${INPUT_DIR}/FileName3.swift"
# ... and so forth
Tools of the Trade
Tools of the Trade
Tools of the Trade
# Mocking takes the form when(methodIsCalled).thenReturn(aValue)
stub(mock) {
(mock) in when(mock.getDate.get).thenReturn(dateAndTime.from(year: 2014, month: 05, day: 20) as Date)
}
XCTAssertEqual(mock.getDate, dateAndTime.from(year: 2014, month: 05, day: 20) as Date)
XCTAssertNotNil(verify(mock).getDate)
Tools of the Trade
Tools of the Trade
$ gem install slather
$ slather coverage --html --scheme XcodeSchemeName path/to/project.xcodeproj
$ slather coverage --html --scheme Calculator Calculator/Calculator.xcodeproj
Tools of the Trade
# SwiftLint
$ brew install swiftlint
$ cd /path/to/XCode directory
$ swiftlint autocorrect
# SwiftFormat
$ brew install swiftformat
$ swiftformat --indent 4
Tools of the Trade
Tools of the Trade
XCUI
- User Interface testing
- Two options
- Recorded tests
- Roll your own
XCUI
XCUI
XCUI
XCUI
func testExample() {
let app = XCUIApplication()
app.buttons["3"].tap()
app.buttons["*"].tap()
app.buttons["4"].tap()
app.buttons["="].tap()
let resultTextField = app.textFields["resultsFld"]
XCTAssert(resultTextField.value! as! String == "12")
}
XCUI
XCUI - Roll your own
- XCUI has 3 component parts
- XCUIApplication
- XCUIElement
- XCUIElementQuery
XCUI - Roll your own
- XCUIApplication
- Launch the app
- XCUIElementQuery
- Find the XCUIElement to test
- XCUIElement
- Perform test
class CalculatorUITests: XCTestCase {
override func setUp() {
super.setUp()
// stop if any test fails
continueAfterFailure = false
}
func testExample() {
// Arrange
let app = XCUIApplication()
// Act
app.buttons["3"].tap()
app.buttons["*"].tap()
app.buttons["4"].tap()
app.buttons["="].tap()
// Assert
XCTAssert(app.textFields["resultsFld"].value! as! String == "12")
}
}
Books
Contact Details
@riisllc, @godfreynolan
godfrey@riis.com
http://riis.com/blog
AgileSwift
By godfreynolan
AgileSwift
- 1,323