In last weeks , I wrote about MVVM with Flow Controller. I created an example in my github and explain my idea about software architecture in Apps. I don’t use TDD in the first moment , I created a first version of the class and after I create a simple test.At first I did small unit tests, such as chamadando the results of the ViewModel. Quickly I ended up covering 50% of the project with tests.
But first I want to talk about architecture versus testing.
In MVVM it is easier to test?
I don’t believe that Unit Test haven’t associate with App Architecture . So the answer is : NO, but the “Loose coupling” is a great point in this case.
Unit Test
This is a very simple case of Unit Test, I create a ListViewModel with our Model. This model has a fixed number of Owl ( 21 units) . Very simple!
import XCTest
@testable import ExampleMVVMFlow
class ExampleMVVMFlowTests: XCTestCase {
func testFailCount() {
let modelOwl = OwlModel()
let list = ListViewModel<OwlModel>(model: modelOwl)
XCTAssertNotEqual(list.count(), 0)
}
func testSuccessCount() {
let modelOwl = OwlModel()
let list = ListViewModel<OwlModel>(model: modelOwl)
XCTAssertEqual(list.count(), 21)
}
func testFailGetItem() {
let modelOwl = OwlModel()
let list = ListViewModel<OwlModel>(model: modelOwl)
XCTAssertNil(list.item(ofIndex: -1))
XCTAssertNil(list.item(ofIndex: list.count()))
}
func testSuccessGetItem() {
let modelOwl = OwlModel()
let list = ListViewModel<OwlModel>(model: modelOwl)
XCTAssertNotNil(list.item(ofIndex: 0))
XCTAssertNotNil(list.item(ofIndex: list.count() - 1))
}
func testSuccessDetailItem() {
let modelOwl = OwlModel()
let list = ListViewModel<OwlModel>(model: modelOwl)
for i in 0 ..< list.count() {
let item = list.item(ofIndex: i)
XCTAssertNotNil(item?.title())
XCTAssertNotNil(item?.text())
XCTAssertNotNil(item?.image())
}
}
}
That’s the advantage of reusing the viewmodel codes using Generic, with a test I will cover all of my lists. This causes few tests guarantee the operation of our project. I did both tests for the positives and the negatives, I adopted a policy of simply return Nil if something went wrong and deal with it in the interface.
Table View Controllers in Swift, with Chris Eidhof
In this talk, we’ll look at how we can work with table view controllers in a more Swifty way. We’ll use generics…
To understand a little better the use of Generics gains for reuse code you can do with Chris Eidhof if you do not know it please stop reading and read what he writes that it is much cooler. :)
UITest
Introduced in XCode 7, UITests now are simple to develop. I’d rather create the test without help tool to record the tests. That’s because I like to really understand the step-by-step I’m modeling for testing. Below is a simple example of detail screen test.
import XCTest
@testable import ExampleMVVMFlow
class DetailViewUITest: XCTestCase {
let app = XCUIApplication()
override func setUp() {
super.setUp()
continueAfterFailure = true
app.launchArguments.append("UITestRun")
app.launchArguments.append("uitest-detail")
app.launch()
}
override func tearDown() {
super.tearDown()
}
func testDetectLabels() {
XCTAssertTrue(app.staticTexts["Name"].exists)
XCTAssertTrue(app.staticTexts["Description"].exists)
XCTAssertTrue(app.images["512px-owly-ambassador"].exists)
}
}
But you must be wondering how the test only tests the details of an item without making the whole process. The idea is that we do not always realize all streams to test an interface, then taking advantage of the Flow Controller created a way to track which stream the test will use.
In the next chapter I explain my strategy.
Routes to Tests
In my previous text MVVM with Flow Controller, I explain about Flow Controller . One problem I’ve always had in the projects that tried to implement UI testing is the case of very large flows with very complex screens, can take too long to run.
But in my view there are two types of UI tests, which test the screens and the interface and the full flow testing an app (for m-commerce, a full-purchase). But for use during the development process to revalidate all possible flows it is not interesting and very time consuming.
//Include this in AppDelegate
if let window = window where NSProcessInfo.processInfo().arguments.contains("UITestRun") {
FlowTestRouter.selectRoute(window, routes: NSProcessInfo.processInfo().arguments)
return true
}
import UIKit
class FlowTestRouter {
static func selectRoute(window : UIWindow , routes : [String]) {
if routes.contains("uitest-list") {
let navigationController = UINavigationController()
let frame = window.bounds
navigationController.view.frame = frame
window.rootViewController = navigationController
window.makeKeyAndVisible()
let owlConf = FlowConfigure(window: nil, navigationController: navigationController, parent: nil)
let childFlow = OwlsFlowController(configure: owlConf)
childFlow.showType = OwlsFlowController.ShowType.List
childFlow.start()
} else if routes.contains("uitest-grid") {
let navigationController = UINavigationController()
let frame = window.bounds
navigationController.view.frame = frame
window.rootViewController = navigationController
window.makeKeyAndVisible()
let owlConf = FlowConfigure(window: nil, navigationController: navigationController, parent: nil)
let childFlow = OwlsFlowController(configure: owlConf)
childFlow.showType = OwlsFlowController.ShowType.Grid
childFlow.start()
} else if routes.contains("uitest-detail") {
let navigationController = UINavigationController()
let frame = window.bounds
navigationController.view.frame = frame
window.rootViewController = navigationController
window.makeKeyAndVisible()
let owlConf = FlowConfigure(window: nil, navigationController: navigationController, parent: nil)
let modelMock = Owl(name: "Name", description: "Description", avatar: UIImage(named: "512px-owly-ambassador"))
let childFlow = OwlDetailFlowController(configure: owlConf, item: modelMock)
childFlow.start()
}
}
}
So taking advantage of the Flow system, I can create UI tests for screens or small sets of intertaces. Now that’s interesting test how much these will be shared code is important to validate that fits on that basis did not affect other parts of the app.
Passing argument as a test flag and which route I want to simulate, I can create different minor flow to make my tests. It is a simple solution and a first version.
Conclusion
I do not believe that increased testing is related to architecture, but the percentage of coverage is easier to achieve. If your concern is quickly cover the largest percentage I think the MVVM will help a lot. Mostly reusing the basic structures with Generic.
In cases of functional tests I liked simulate the improvement of power simpler routes to perform several short tests. At the end also I was able to test different solutions on the same device such as Grid or TableView. This gives an interesting result, and increases test coverage results.
At the end of a 96% coverage aproximadamento 1 hour work. This is very good, even on a small project like this.
Next Steps
Next steps in this MVVM design:
- Expand the project with more models and more interfaces;
- Making ViewController layer more Generic and reusable;
- Create your own networking layer simply.
These items may take a while because I will have to climb a Webservice to facilitate the implementation of new ideas. (2 weeks)
Thanks
I would like the share in last article, in special to :
- Steve “Scotty” Scott
- Krzysztof Zabłocki
Accompany these guys, they talk a lot of interesting things.
This week I made a Maratone and saw all the videos ObjcIO, really one of the most interesting independent sites that exist for Swift and Objective-C developers. In particular, Chris Eidhof that inspired me to try to create my interfaces programmatically because it could create extremely interesting things. Seriously accompany them.
Any suggestion is welcome. Anything come to me on Twitter to discuss. Thank you!