Maintainable Mobile Apps

I’m Ivan an iOS developer. At E&V Technology GmbH we make different mobile apps, helping our customers to find a property of their dream. One of these products is the Search app, having a more than 5 years history of exposing properties from the global E&V network for iPhone users.

When you develop the extendable app with a long-lifecycle the maintainability of the source code is of high importance. We want to be sure that every addition of a business value, that leads to the change of the source code, keeps the correct app’s behavior for the user. Either for a small feature or for a whole new screen.

To make this happen we split app code to units according to the Clean Architecture by R.C. Martin. And cover business logic and model units with tests. Each of these Unit Tests can be considered as a yes/no question to the app behavior and helps us to verify the correctness of the behavior after every change of the source code in seconds.

When you add Unit tests to a project, two general challenges arise.

  1. How to be sure that all necessary questions are asked to a program’s Unit?


  2. How to make sure that count of questions is small?

And the second challenge is critical for maintainability of unit tests code itself because if their count is too big then they become fragile, in other words, no one wants to support them and prefers to drop on the floor.

To deal with these challenges in iOS mobile apps we can do one more step. To write code that only satisfies tests, that is when we write a Test at first and only then we write an implementation of the Unit. This test-first approach has several advantages:

  • Unit code is verified


  • Count of necessary tests is minimal


  • As a consequence, tests are easily maintainable


This is Test Driven Development!


Steps of TDD

Usually, three well-known steps of Test Driven Development are discussed. I prefer to consider four steps. The first step is to evolve a test list. The test list plays an important role in the method. This list is created from the user story you’re going to implement.




Hamburg - Engel & Völkers Technology

After choosing the test from the list you implement it and make the project to compile. After running the test fails, that is a Red state. Next, you implement the Unit’s code to make the test pass, that is a Green state. And finally, you refactor Unit code and then Test code, making sure that tests are green after each modification, that is called a Refactoring state. When you know the next test from the list you go to Red phase again with the new test. If a next test is unknown you return to the Overview phase.

Iteration over that short cycle, again and again, gives new ideas for tests that can be added as new entries to the list. And the source code of the Unit is written in a highly focused manner. Same time at the end of each iteration the code of the app is compilable and runnable. That helps to build Units in small steps and to be sure that on each step the program behavior is appropriate. Refactoring step helps to make the Unit’s code compact and elegant.

What Units can be developed with TDD

In mobile app development we use TDD to make Units of business logic for a screen (Interactor), and model Units of app-wise business logic. UI layer containing UIViewController and UIView classes is easier to be developed with Interface Builder and no-test approach. As also as Presenter layer that is formatting the output. These classes are to be covered with UI tests.

Structure of the tests

Each test is intended to verify only one aspect of the Unit behavior and have a name that reflects this.

class DSGVOInteractorImplPrivacyPolicyTests: XCTestCase {
...
func testOpenPrivacyPolicyURL_withDataSecurityURLStringIsSet__opensURL() {
let urlString = "http://someurl.com"
shopModel.dataSecurityUrl = urlString
let (interactor, urlOpener) = DSGVOTest.makeInteractorImplAndURLOpenerMock(exposee: exposeeModel)
interactor.openPrivacyPolicyURL()
let url = URL(string: urlString)
expect(urlOpener.openedURL).to(equal(url))
}
}

In the example above the name of the test contains the required word “test” following by the tested method name. In the middle is the context of the call. And the last part is the expected result of the call that should happen.

When the test method fails its name quickly says about the lost behavior directly in the test navigator, no need to read the body.

The test’s body is organized as Given When Then sections. Where Given is the section where an instance of the tested object and its dependencies are prepared. When is the section with the call to the tested method, and Then is the section where the call result is checked.

Requirements verification

Taken structure of the test helps to make a list of tests from an issue’s requirements. And if it’s hardly possible then it’s a sign to clear the requirements. That makes TDD a good tool for verification of requirements.

Interface design

Implementing a Unit’s code that satisfies test makes you think about Unit’s dependencies as of mock objects that are needed to simulate the desired context. This helps to design the Unit’s interface according to the Open-Closed principle.

Benefits of TDD

There are several benefits of applying Test Driven Development for apps with long life-cycle. First of all, the test-first approach makes the programming intentional. That means, you think first on what you will test and implement.

Next, when you design unit to be Testable you automatically make it extendable and conforming to open-closed principle.

Third, you have a minimum necessary amount of Unit Tests. Units and Tests are clean and elegant due to refactoring step. And at the end, the Unit’s code becomes verifiable and maintainable.

Test Driven Development helps us to produce high-quality mobile apps.




Follow us on social media


Array
(
[EUNDV] => Array
(
[67d842e2b887a402186a2820b1713d693dd854a5_csrf_offer-form] => MTM5MjE5NzU3NkJ4d29xancwTDVhZWFIRzEycXAxcW9SdElHdVBqMTdV
[67d842e2b887a402186a2820b1713d693dd854a5_csrf_contact-form] => MTM5MjE5NzU3NnlHcUR0Y2VlTXVPUndLMHZkMW9zMnRmRlgxaUcwaFVG
)
)