Test or GoFishing

Paul Ardeleanu
@pardel
A guide on how to write better Swift for iOS
TechFest Bucharest, Sept 2018 @pardel
Test Driven Development


TechFest Bucharest, Sept 2018 @pardel
TDD
according to Wikipedia
“Test-driven development (TDD) is a software development process
that relies on the repetition of a very short development cycle:
requirements are turned into very specific test cases, then the
software is improved to pass the new tests, only. This is opposed
to software development that allows software to be added that is not
proven to meet requirements.”
https://en.wikipedia.org/wiki/Test-driven_development
TechFest Bucharest, Sept 2018 @pardel
Why use TDD?

TechFest Bucharest, Sept 2018 @pardel
Behaviour Driven Development


TechFest Bucharest, Sept 2018 @pardel
BDD
according to Wikipedia
"combines the general techniques and principles of TDD with ideas
from domain-driven design and object-oriented analysis and design
to provide software development and management teams with
shared tools and a shared process to collaborate on software
development."
https://en.wikipedia.org/wiki/Behavior-driven_development
TechFest Bucharest, Sept 2018 @pardel
TDD vs BDD

TechFest Bucharest, Sept 2018 @pardel
To Be Determined

TechFest Bucharest, Sept 2018 @pardel
Quick But Gentle
Introduction to TDD


TechFest Bucharest, Sept 2018 @pardel
TDD lifecycle
Feature, Red, Green, Refactor
Pick a feature to implement
making sure it’s a small enough unit.
FEATURE
Change any of the existing code
making sure ALL tests are passing.
REFACTOR
Write a failing test.
Stop as soon as you get a failure.
RED
Write code to pass the test.
Write as little code as possible.
GREENL (
)

TechFest Bucharest, Sept 2018 @pardel
TDD lifecycle
Feature, Red, Green, Refactor
New
feature
create test
enough code to
make it pass
successful
test execution
No
anything to
refactor
Yes
Yes
refactoring
No
Ya Ain’t Gonna Need It

TechFest Bucharest, Sept 2018 @pardel
Testing pyramid
Unit Tests
Integration Tests
UI Tests
TechFest Bucharest, Sept 2018 @pardel
Testing in iOS


TechFest Bucharest, Sept 2018 @pardel
Testing in iOS
XCTest - since Xcode 5

TechFest Bucharest, Sept 2018 @pardel
Testing in iOS
XCTest - since Xcode 5
TechFest Bucharest, Sept 2018 @pardel
Playgrounds

TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
struct Task {
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
_ = Task()
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel
struct Task {
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
_ = Task()
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
_ = Task()
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
_ = Task()
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
init?(name: String) {
if name.lengthOfBytes(using: .utf8) == 0 {
return nil
}
self.name = name
}
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
init?(name: String) {
if name.lengthOfBytes(using: .utf8) == 0 {
return nil
}
self.name = name
}
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
_ = Task(name: UUID().uuidString)
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
init?(name: String) {
if name.lengthOfBytes(using: .utf8) == 0 {
return nil
}
self.name = name
}
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
_ = Task(name: UUID().uuidString)
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
init?(name: String) {
if name.lengthOfBytes(using: .utf8) == 0 {
return nil
}
self.name = name
}
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
_ = Task(name: UUID().uuidString)
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
let name = UUID().uuidString
let task = Task(name: name)
XCTAssertEqual(task?.name, name)
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
init?(name: String) {
if name.lengthOfBytes(using: .utf8) == 0 {
return nil
}
self.name = name
}
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
let name = UUID().uuidString
let task = Task(name: name)
XCTAssertEqual(task?.name, name)
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
init?(name: String) {
// if name.lengthOfBytes(using: .utf8) == 0 {
// return nil
// }
// self.name = name
}
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
let name = UUID().uuidString
let task = Task(name: name)
XCTAssertEqual(task?.name, name)
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel
struct Task {
var name: String
init?(name: String) {
if name.lengthOfBytes(using: .utf8) == 0 {
return nil
}
self.name = name
}
}
class TaskTests: XCTestCase {
func testCanCreateATask() {
let name = UUID().uuidString
let task = Task(name: name)
XCTAssertEqual(task?.name, name)
}
func testTaskMustHaveANonBlankName() {
let task = Task(name: "")
XCTAssertNil(task)
}
}
TaskTests.defaultTestSuite.run()
TechFest Bucharest, Sept 2018 @pardel

Moving to an
Xcode project
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel

Object Factories
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
enum TaskFactory {
static var active: Task {
return Task(name: UUID().uuidString)!
}
static var completed: Task {
var task = Task(name: UUID().uuidString)!
task.complete()
return task
}
}
class TaskFactoryTests: XCTestCase {
func testActiveTask() {
let task = TaskFactory.active
XCTAssertTrue(task.isActive)
}
func testCompletedTask() {
let task = TaskFactory.completed
XCTAssertFalse(task.isActive)
}
}
TechFest Bucharest, Sept 2018 @pardel
func testActiveTasksDontIncludeCompletedTask() {
let startTaskCount = taskManager.activeTasks.count
taskManager.add(task: TaskFactory.completed)
XCTAssertEqual(taskManager.activeTasks.count, startTaskCount)
}
func testCompletedTasksDontIncludeActiveTask() {
let startTaskCount = taskManager.completedTasks.count
taskManager.add(task: TaskFactory.active )
XCTAssertEqual(taskManager.completedTasks.count, startTaskCount)
}
TechFest Bucharest, Sept 2018 @pardel
func testActiveTasksDontIncludeCompletedTask() {
let startTaskCount = taskManager.activeTasks.count
taskManager.add(task: TaskFactory.completed)
XCTAssertEqual(taskManager.activeTasks.count, startTaskCount)
}
func testCompletedTasksDontIncludeActiveTask() {
let startTaskCount = taskManager.completedTasks.count
taskManager.add(task: TaskFactory.active)
XCTAssertEqual(taskManager.completedTasks.count, startTaskCount)
}
TechFest Bucharest, Sept 2018 @pardel

3rd Party Libraries
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
class TaskManager {
private var tasks = [Task]()
public var activeTasks: [Task] {
do {
let predicate = NSPredicate(format: "completedAt == nil")
let items = try Realm().objects(Task.self).filter(predicate)
return Array(items)
} catch {
return [Task]()
}
}
public var completedTasks: [Task] {
do {
let predicate = NSPredicate(format: "completedAt != nil")
let items = try Realm().objects(Task.self).filter(predicate)
return Array(items)
} catch {
return [Task]()
}
}
…
}
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
class TaskManagerTests: XCTestCase {
var taskManager: TaskManager!
override func setUp() {
super.setUp()
taskManager = TaskManager()
}
…
}
TechFest Bucharest, Sept 2018 @pardel
class TaskManagerTests: XCTestCase {
var taskManager: TaskManager!
override func setUp() {
super.setUp()
Realm.Configuration.defaultConfiguration =
Realm.Configuration(inMemoryIdentifier: "Toddledoo")
taskManager = TaskManager()
}
…
}
TechFest Bucharest, Sept 2018 @pardel
class TaskManagerTests: XCTestCase {
var taskManager: TaskManager!
override func setUp() {
super.setUp()
Realm.Configuration.defaultConfiguration =
Realm.Configuration(inMemoryIdentifier: UUID().uuidString)
taskManager = TaskManager()
}
…
}
TechFest Bucharest, Sept 2018 @pardel
class TaskManagerTests: XCTestCase {
var taskManager: TaskManager!
override func setUp() {
super.setUp()
Realm.Configuration.defaultConfiguration =
Realm.Configuration(inMemoryIdentifier: UUID().uuidString)
taskManager = TaskManager()
}
…
}
TechFest Bucharest, Sept 2018 @pardel
UI tests

TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
class TasksViewControllerTests: XCTestCase {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var vc: TasksViewController?
override func setUp() {
super.setUp()
vc = storyboard.instantiateViewController(withIdentifier: "TasksViewController") as? TasksViewController
vc?.loadViewIfNeeded()
}
func testSetup() {
XCTAssertNotNil(vc)
XCTAssertEqual(vc?.isViewLoaded, true)
}
func testIsInitialViewController() {
let nav = storyboard.instantiateInitialViewController() as? UINavigationController
XCTAssertNotNil(nav)
XCTAssertEqual(nav?.viewControllers.count, 1)
XCTAssertNotNil(nav?.viewControllers.first as? TasksViewController)
}
func testHasTitle() {
XCTAssertEqual(vc?.title, "Toddledoo")
}
}
TechFest Bucharest, Sept 2018 @pardel
TechFest Bucharest, Sept 2018 @pardel
class TasksViewControllerTests: XCTestCase {
}
TechFest Bucharest, Sept 2018 @pardel
class TasksViewControllerTests: XCTestCase {
}
let storyboard = UIStoryboard(name: "Main", bundle: nil)
var vc: TasksViewController?
override func setUp() {
super.setUp()
vc = storyboard.instantiateViewController(withIdentifier: "TasksViewController") as? TasksViewController
vc?.loadViewIfNeeded()
}
func testSetup() {
XCTAssertNotNil(vc)
XCTAssertEqual(vc?.isViewLoaded, true)
}
TechFest Bucharest, Sept 2018 @pardel
class TasksViewControllerTests: XCTestCase {
...
}
func testHasTableView() {
XCTAssertNotNil(vc?.tableView)
XCTAssertNotNil(vc?.tableView.superview)
XCTAssertEqual(vc?.tableView.superview, vc?.view)
}
func testTableViewCoversWholeView() {
XCTAssertNotNil(vc?.view)
XCTAssertEqual(vc?.view.bounds, vc?.tableView.frame)
}
func testTableViewCoversWholeView() {
XCTAssertNotNil(vc?.view)
XCTAssertNotNil(vc?.tableView)
let hasTopConstraint = vc?.view.constraints.contains(where: { constraint -> Bool in
return ((constraint.firstItem as? UITableView) == vc?.tableView) &&
(constraint.firstAnchor == vc?.tableView.topAnchor) &&
((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) &&
(constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.topAnchor) &&
(constraint.relation == .equal) &&
constraint.multiplier == 1.0 &&
constraint.constant == 0 &&
constraint.isActive == true
})
XCTAssert(hasTopConstraint ?? false)
}
TechFest Bucharest, Sept 2018 @pardel
class TasksViewControllerTests: XCTestCase {
}
func testTableViewCoversWholeView() {
XCTAssertNotNil(vc?.view)
XCTAssertNotNil(vc?.tableView)
XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in
return ((constraint.firstItem as? UITableView) == vc?.tableView) &&
(constraint.firstAnchor == vc?.tableView.topAnchor) &&
((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) &&
(constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.topAnchor) &&
(constraint.relation == .equal) &&
constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true
}) ?? false)
XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in
return ((constraint.firstItem as? UITableView) == vc?.tableView) &&
(constraint.firstAnchor == vc?.tableView.bottomAnchor) &&
((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) &&
(constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.bottomAnchor) &&
(constraint.relation == .equal) &&
constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true
}) ?? false)
XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in
return ((constraint.firstItem as? UITableView) == vc?.tableView) &&
(constraint.firstAnchor == vc?.tableView.leadingAnchor) &&
((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) &&
(constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.leadingAnchor) &&
(constraint.relation == .equal) &&
constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true
}) ?? false)
XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in
return ((constraint.firstItem as? UITableView) == vc?.tableView) &&
(constraint.firstAnchor == vc?.tableView.trailingAnchor) &&
((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) &&
(constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.trailingAnchor) &&
(constraint.relation == .equal) &&
constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true
}) ?? false)
}
TechFest Bucharest, Sept 2018 @pardel
Continuous Integration


TechFest Bucharest, Sept 2018 @pardel
macOS Server

TechFest Bucharest, Sept 2018 @pardel
macOS Server
TechFest Bucharest, Sept 2018 @pardel
Some tips


TechFest Bucharest, Sept 2018 @pardel
Pick good test names
test_init_takesTitleAndDescription()
test_ValidAccountName_InvalidAccountNumber_ValidSortCode_CurrentAccoun
t_OneOwner_CanNotBeInstantiated()

TechFest Bucharest, Sept 2018 @pardel
Pick good names
SUT
func testLogoutCallsDelegateMethod() {
// Given
guard let sut = sut else {
XCTFail("Could not get system under test")
return
}
// When
delegateStatus = .noneCalled
sut.logout()
// Then
XCTAssertTrue(delegateStatus == .didLogoutCalled, "delegate method was not called")
}

TechFest Bucharest, Sept 2018 @pardel
No magic numbers
UUID().uuidString
// Swift 4.2
Int.random(in: 1..<5)
Float.random(in: 1..<10)
Double.random(in: 1...100)
CGFloat.random(in: 1...1000)
Bool.random()
anArray.shuffle()
anArray.shuffled()
anArray.randomElement()

TechFest Bucharest, Sept 2018 @pardel
No magic numbers
func testActiveTasksDontIncludeCompletedTask() {
let startTaskCount = taskManager.activeTasks.count
XCTAssertTrue(taskManager.add(task: TaskFactory.completed))
XCTAssertEqual(taskManager.activeTasks.count, startTaskCount)
}
func testCompletedTasksDontIncludeActiveTask() {
let startTaskCount = taskManager.completedTasks.count
XCTAssertTrue(taskManager.add(task: TaskFactory.active))
XCTAssertEqual(taskManager.completedTasks.count, startTaskCount)
}

TechFest Bucharest, Sept 2018 @pardel
Input & output in one place
func testCanCreateATask() {
let name = UUID().uuidString
let task = Task(name: name)
XCTAssertNotNil(task)
XCTAssertEqual(task?.name, name)
}

TechFest Bucharest, Sept 2018 @pardel
Third party libraries

TechFest Bucharest, Sept 2018 @pardel
Test EVERYTHING

TechFest Bucharest, Sept 2018 @pardel
Be very specific in your tests
We wanted flying cars

TechFest Bucharest, Sept 2018 @pardel
App Architecture
MVC, MVVM, MVVM-P, Viper, etc.
https://www.objc.io/books/app-architecture/

TechFest Bucharest, Sept 2018 @pardel
BDD
Quick & Nimble
import Quick
import Nimble
class TableOfContentsSpec: QuickSpec {
override func spec() {
describe("the 'Documentation' directory") {
it("has everything you need to get started") {
let sections = Directory("Documentation").sections
expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups"))
expect(sections).to(contain("Installing Quick"))
}
context("if it doesn't have what you're looking for") {
it("needs to be updated") {
let you = You(awesome: true)
expect{you.submittedAnIssue}.toEventually(beTruthy())
}
}
}
}
}

TechFest Bucharest, Sept 2018 @pardel
FitNesse
http://fitnesse.org
package fitnesse.slim.test;
public class ShouldIBuyMilk {
private int dollars;
private int pints;
private boolean creditCard;
public void setCashInWallet(int dollars) {
this.dollars = dollars;
}
public void setPintsOfMilkRemaining(int pints) {
this.pints = pints;
}
public void setCreditCard(String valid) {
creditCard = "yes".equals(valid);
}
public String goToStore() {
return (pints == 0 && (dollars > 2 || creditCard)) ? "yes" : "no";
}
// The following functions are optional. If they aren't declared they'll be ignored.
public void execute() {
}
public void reset() {
}
public void table(List<List<String>> table) {
}
public void beginTable() {
}
public void endTable() {
}
}
TechFest Bucharest, Sept 2018 @pardel
https://m.pardel.net
TechFest Bucharest, Sept 2018 @pardel
h24.io /techfest
TechFest Bucharest, Sept 2018 @pardel
Thank you!
paul@codica.eu
@pardel
codica.eu

Test or Go Fishing - a guide on how to write better Swift for iOS

  • 1.
    Test or GoFishing  PaulArdeleanu @pardel A guide on how to write better Swift for iOS
  • 2.
    TechFest Bucharest, Sept2018 @pardel Test Driven Development 
  • 3.
     TechFest Bucharest, Sept2018 @pardel TDD according to Wikipedia “Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: requirements are turned into very specific test cases, then the software is improved to pass the new tests, only. This is opposed to software development that allows software to be added that is not proven to meet requirements.” https://en.wikipedia.org/wiki/Test-driven_development
  • 4.
    TechFest Bucharest, Sept2018 @pardel Why use TDD? 
  • 5.
    TechFest Bucharest, Sept2018 @pardel Behaviour Driven Development 
  • 6.
     TechFest Bucharest, Sept2018 @pardel BDD according to Wikipedia "combines the general techniques and principles of TDD with ideas from domain-driven design and object-oriented analysis and design to provide software development and management teams with shared tools and a shared process to collaborate on software development." https://en.wikipedia.org/wiki/Behavior-driven_development
  • 7.
    TechFest Bucharest, Sept2018 @pardel TDD vs BDD 
  • 8.
    TechFest Bucharest, Sept2018 @pardel To Be Determined 
  • 9.
    TechFest Bucharest, Sept2018 @pardel Quick But Gentle Introduction to TDD 
  • 10.
     TechFest Bucharest, Sept2018 @pardel TDD lifecycle Feature, Red, Green, Refactor Pick a feature to implement making sure it’s a small enough unit. FEATURE Change any of the existing code making sure ALL tests are passing. REFACTOR Write a failing test. Stop as soon as you get a failure. RED Write code to pass the test. Write as little code as possible. GREENL ( )
  • 11.
     TechFest Bucharest, Sept2018 @pardel TDD lifecycle Feature, Red, Green, Refactor New feature create test enough code to make it pass successful test execution No anything to refactor Yes Yes refactoring No Ya Ain’t Gonna Need It
  • 12.
     TechFest Bucharest, Sept2018 @pardel Testing pyramid Unit Tests Integration Tests UI Tests
  • 13.
    TechFest Bucharest, Sept2018 @pardel Testing in iOS 
  • 14.
     TechFest Bucharest, Sept2018 @pardel Testing in iOS XCTest - since Xcode 5
  • 15.
     TechFest Bucharest, Sept2018 @pardel Testing in iOS XCTest - since Xcode 5
  • 16.
    TechFest Bucharest, Sept2018 @pardel Playgrounds 
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
    TechFest Bucharest, Sept2018 @pardel struct Task { } class TaskTests: XCTestCase { func testCanCreateATask() { _ = Task() } } TaskTests.defaultTestSuite.run()
  • 27.
    TechFest Bucharest, Sept2018 @pardel struct Task { } class TaskTests: XCTestCase { func testCanCreateATask() { _ = Task() } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run()
  • 28.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String } class TaskTests: XCTestCase { func testCanCreateATask() { _ = Task() } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run()
  • 29.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String } class TaskTests: XCTestCase { func testCanCreateATask() { _ = Task() } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run() init?(name: String) { if name.lengthOfBytes(using: .utf8) == 0 { return nil } self.name = name }
  • 30.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String init?(name: String) { if name.lengthOfBytes(using: .utf8) == 0 { return nil } self.name = name } } class TaskTests: XCTestCase { func testCanCreateATask() { _ = Task(name: UUID().uuidString) } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run()
  • 31.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String init?(name: String) { if name.lengthOfBytes(using: .utf8) == 0 { return nil } self.name = name } } class TaskTests: XCTestCase { func testCanCreateATask() { _ = Task(name: UUID().uuidString) } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run()
  • 32.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String init?(name: String) { if name.lengthOfBytes(using: .utf8) == 0 { return nil } self.name = name } } class TaskTests: XCTestCase { func testCanCreateATask() { _ = Task(name: UUID().uuidString) } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run() let name = UUID().uuidString let task = Task(name: name) XCTAssertEqual(task?.name, name)
  • 33.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String init?(name: String) { if name.lengthOfBytes(using: .utf8) == 0 { return nil } self.name = name } } class TaskTests: XCTestCase { func testCanCreateATask() { let name = UUID().uuidString let task = Task(name: name) XCTAssertEqual(task?.name, name) } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run()
  • 34.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String init?(name: String) { // if name.lengthOfBytes(using: .utf8) == 0 { // return nil // } // self.name = name } } class TaskTests: XCTestCase { func testCanCreateATask() { let name = UUID().uuidString let task = Task(name: name) XCTAssertEqual(task?.name, name) } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run()
  • 35.
    TechFest Bucharest, Sept2018 @pardel struct Task { var name: String init?(name: String) { if name.lengthOfBytes(using: .utf8) == 0 { return nil } self.name = name } } class TaskTests: XCTestCase { func testCanCreateATask() { let name = UUID().uuidString let task = Task(name: name) XCTAssertEqual(task?.name, name) } func testTaskMustHaveANonBlankName() { let task = Task(name: "") XCTAssertNil(task) } } TaskTests.defaultTestSuite.run()
  • 39.
    TechFest Bucharest, Sept2018 @pardel  Moving to an Xcode project
  • 40.
  • 41.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
    TechFest Bucharest, Sept2018 @pardel  Object Factories
  • 48.
  • 49.
    TechFest Bucharest, Sept2018 @pardel enum TaskFactory { static var active: Task { return Task(name: UUID().uuidString)! } static var completed: Task { var task = Task(name: UUID().uuidString)! task.complete() return task } } class TaskFactoryTests: XCTestCase { func testActiveTask() { let task = TaskFactory.active XCTAssertTrue(task.isActive) } func testCompletedTask() { let task = TaskFactory.completed XCTAssertFalse(task.isActive) } }
  • 50.
    TechFest Bucharest, Sept2018 @pardel func testActiveTasksDontIncludeCompletedTask() { let startTaskCount = taskManager.activeTasks.count taskManager.add(task: TaskFactory.completed) XCTAssertEqual(taskManager.activeTasks.count, startTaskCount) } func testCompletedTasksDontIncludeActiveTask() { let startTaskCount = taskManager.completedTasks.count taskManager.add(task: TaskFactory.active ) XCTAssertEqual(taskManager.completedTasks.count, startTaskCount) }
  • 51.
    TechFest Bucharest, Sept2018 @pardel func testActiveTasksDontIncludeCompletedTask() { let startTaskCount = taskManager.activeTasks.count taskManager.add(task: TaskFactory.completed) XCTAssertEqual(taskManager.activeTasks.count, startTaskCount) } func testCompletedTasksDontIncludeActiveTask() { let startTaskCount = taskManager.completedTasks.count taskManager.add(task: TaskFactory.active) XCTAssertEqual(taskManager.completedTasks.count, startTaskCount) }
  • 52.
    TechFest Bucharest, Sept2018 @pardel  3rd Party Libraries
  • 53.
  • 54.
  • 55.
    TechFest Bucharest, Sept2018 @pardel class TaskManager { private var tasks = [Task]() public var activeTasks: [Task] { do { let predicate = NSPredicate(format: "completedAt == nil") let items = try Realm().objects(Task.self).filter(predicate) return Array(items) } catch { return [Task]() } } public var completedTasks: [Task] { do { let predicate = NSPredicate(format: "completedAt != nil") let items = try Realm().objects(Task.self).filter(predicate) return Array(items) } catch { return [Task]() } } … }
  • 56.
  • 57.
    TechFest Bucharest, Sept2018 @pardel class TaskManagerTests: XCTestCase { var taskManager: TaskManager! override func setUp() { super.setUp() taskManager = TaskManager() } … }
  • 58.
    TechFest Bucharest, Sept2018 @pardel class TaskManagerTests: XCTestCase { var taskManager: TaskManager! override func setUp() { super.setUp() Realm.Configuration.defaultConfiguration = Realm.Configuration(inMemoryIdentifier: "Toddledoo") taskManager = TaskManager() } … }
  • 59.
    TechFest Bucharest, Sept2018 @pardel class TaskManagerTests: XCTestCase { var taskManager: TaskManager! override func setUp() { super.setUp() Realm.Configuration.defaultConfiguration = Realm.Configuration(inMemoryIdentifier: UUID().uuidString) taskManager = TaskManager() } … }
  • 60.
    TechFest Bucharest, Sept2018 @pardel class TaskManagerTests: XCTestCase { var taskManager: TaskManager! override func setUp() { super.setUp() Realm.Configuration.defaultConfiguration = Realm.Configuration(inMemoryIdentifier: UUID().uuidString) taskManager = TaskManager() } … }
  • 61.
    TechFest Bucharest, Sept2018 @pardel UI tests 
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
    TechFest Bucharest, Sept2018 @pardel class TasksViewControllerTests: XCTestCase { let storyboard = UIStoryboard(name: "Main", bundle: nil) var vc: TasksViewController? override func setUp() { super.setUp() vc = storyboard.instantiateViewController(withIdentifier: "TasksViewController") as? TasksViewController vc?.loadViewIfNeeded() } func testSetup() { XCTAssertNotNil(vc) XCTAssertEqual(vc?.isViewLoaded, true) } func testIsInitialViewController() { let nav = storyboard.instantiateInitialViewController() as? UINavigationController XCTAssertNotNil(nav) XCTAssertEqual(nav?.viewControllers.count, 1) XCTAssertNotNil(nav?.viewControllers.first as? TasksViewController) } func testHasTitle() { XCTAssertEqual(vc?.title, "Toddledoo") } }
  • 69.
  • 70.
    TechFest Bucharest, Sept2018 @pardel class TasksViewControllerTests: XCTestCase { }
  • 71.
    TechFest Bucharest, Sept2018 @pardel class TasksViewControllerTests: XCTestCase { } let storyboard = UIStoryboard(name: "Main", bundle: nil) var vc: TasksViewController? override func setUp() { super.setUp() vc = storyboard.instantiateViewController(withIdentifier: "TasksViewController") as? TasksViewController vc?.loadViewIfNeeded() } func testSetup() { XCTAssertNotNil(vc) XCTAssertEqual(vc?.isViewLoaded, true) }
  • 72.
    TechFest Bucharest, Sept2018 @pardel class TasksViewControllerTests: XCTestCase { ... } func testHasTableView() { XCTAssertNotNil(vc?.tableView) XCTAssertNotNil(vc?.tableView.superview) XCTAssertEqual(vc?.tableView.superview, vc?.view) } func testTableViewCoversWholeView() { XCTAssertNotNil(vc?.view) XCTAssertEqual(vc?.view.bounds, vc?.tableView.frame) } func testTableViewCoversWholeView() { XCTAssertNotNil(vc?.view) XCTAssertNotNil(vc?.tableView) let hasTopConstraint = vc?.view.constraints.contains(where: { constraint -> Bool in return ((constraint.firstItem as? UITableView) == vc?.tableView) && (constraint.firstAnchor == vc?.tableView.topAnchor) && ((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) && (constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.topAnchor) && (constraint.relation == .equal) && constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true }) XCTAssert(hasTopConstraint ?? false) }
  • 73.
    TechFest Bucharest, Sept2018 @pardel class TasksViewControllerTests: XCTestCase { } func testTableViewCoversWholeView() { XCTAssertNotNil(vc?.view) XCTAssertNotNil(vc?.tableView) XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in return ((constraint.firstItem as? UITableView) == vc?.tableView) && (constraint.firstAnchor == vc?.tableView.topAnchor) && ((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) && (constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.topAnchor) && (constraint.relation == .equal) && constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true }) ?? false) XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in return ((constraint.firstItem as? UITableView) == vc?.tableView) && (constraint.firstAnchor == vc?.tableView.bottomAnchor) && ((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) && (constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.bottomAnchor) && (constraint.relation == .equal) && constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true }) ?? false) XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in return ((constraint.firstItem as? UITableView) == vc?.tableView) && (constraint.firstAnchor == vc?.tableView.leadingAnchor) && ((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) && (constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.leadingAnchor) && (constraint.relation == .equal) && constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true }) ?? false) XCTAssert(vc?.view.constraints.contains(where: { constraint -> Bool in return ((constraint.firstItem as? UITableView) == vc?.tableView) && (constraint.firstAnchor == vc?.tableView.trailingAnchor) && ((constraint.secondItem as? UILayoutGuide) == vc?.view.safeAreaLayoutGuide) && (constraint.secondAnchor == vc?.view.safeAreaLayoutGuide.trailingAnchor) && (constraint.relation == .equal) && constraint.multiplier == 1.0 && constraint.constant == 0 && constraint.isActive == true }) ?? false) }
  • 74.
    TechFest Bucharest, Sept2018 @pardel Continuous Integration 
  • 75.
     TechFest Bucharest, Sept2018 @pardel macOS Server
  • 76.
     TechFest Bucharest, Sept2018 @pardel macOS Server
  • 77.
    TechFest Bucharest, Sept2018 @pardel Some tips 
  • 78.
     TechFest Bucharest, Sept2018 @pardel Pick good test names test_init_takesTitleAndDescription() test_ValidAccountName_InvalidAccountNumber_ValidSortCode_CurrentAccoun t_OneOwner_CanNotBeInstantiated()
  • 79.
     TechFest Bucharest, Sept2018 @pardel Pick good names SUT func testLogoutCallsDelegateMethod() { // Given guard let sut = sut else { XCTFail("Could not get system under test") return } // When delegateStatus = .noneCalled sut.logout() // Then XCTAssertTrue(delegateStatus == .didLogoutCalled, "delegate method was not called") }
  • 80.
     TechFest Bucharest, Sept2018 @pardel No magic numbers UUID().uuidString // Swift 4.2 Int.random(in: 1..<5) Float.random(in: 1..<10) Double.random(in: 1...100) CGFloat.random(in: 1...1000) Bool.random() anArray.shuffle() anArray.shuffled() anArray.randomElement()
  • 81.
     TechFest Bucharest, Sept2018 @pardel No magic numbers func testActiveTasksDontIncludeCompletedTask() { let startTaskCount = taskManager.activeTasks.count XCTAssertTrue(taskManager.add(task: TaskFactory.completed)) XCTAssertEqual(taskManager.activeTasks.count, startTaskCount) } func testCompletedTasksDontIncludeActiveTask() { let startTaskCount = taskManager.completedTasks.count XCTAssertTrue(taskManager.add(task: TaskFactory.active)) XCTAssertEqual(taskManager.completedTasks.count, startTaskCount) }
  • 82.
     TechFest Bucharest, Sept2018 @pardel Input & output in one place func testCanCreateATask() { let name = UUID().uuidString let task = Task(name: name) XCTAssertNotNil(task) XCTAssertEqual(task?.name, name) }
  • 83.
     TechFest Bucharest, Sept2018 @pardel Third party libraries
  • 84.
     TechFest Bucharest, Sept2018 @pardel Test EVERYTHING
  • 85.
     TechFest Bucharest, Sept2018 @pardel Be very specific in your tests We wanted flying cars
  • 86.
     TechFest Bucharest, Sept2018 @pardel App Architecture MVC, MVVM, MVVM-P, Viper, etc. https://www.objc.io/books/app-architecture/
  • 87.
     TechFest Bucharest, Sept2018 @pardel BDD Quick & Nimble import Quick import Nimble class TableOfContentsSpec: QuickSpec { override func spec() { describe("the 'Documentation' directory") { it("has everything you need to get started") { let sections = Directory("Documentation").sections expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups")) expect(sections).to(contain("Installing Quick")) } context("if it doesn't have what you're looking for") { it("needs to be updated") { let you = You(awesome: true) expect{you.submittedAnIssue}.toEventually(beTruthy()) } } } } }
  • 88.
     TechFest Bucharest, Sept2018 @pardel FitNesse http://fitnesse.org package fitnesse.slim.test; public class ShouldIBuyMilk { private int dollars; private int pints; private boolean creditCard; public void setCashInWallet(int dollars) { this.dollars = dollars; } public void setPintsOfMilkRemaining(int pints) { this.pints = pints; } public void setCreditCard(String valid) { creditCard = "yes".equals(valid); } public String goToStore() { return (pints == 0 && (dollars > 2 || creditCard)) ? "yes" : "no"; } // The following functions are optional. If they aren't declared they'll be ignored. public void execute() { } public void reset() { } public void table(List<List<String>> table) { } public void beginTable() { } public void endTable() { } }
  • 89.
    TechFest Bucharest, Sept2018 @pardel https://m.pardel.net
  • 90.
    TechFest Bucharest, Sept2018 @pardel h24.io /techfest
  • 91.
    TechFest Bucharest, Sept2018 @pardel Thank you! paul@codica.eu @pardel codica.eu