Monthly Archives: September 2018

Returning to An Old Project: Part 2

Returning to An Old Project: Part 2
The logical place to start reviewing and fixing up my Devonian app is in the area of managing the database.  I have one class that works on both iOS and MacOS that handles this job.   Reviewing the class… it’s quite a bit of code: well over 800 lines.  Individual classes with high line counts are not that unusual, especially for things like view controllers (if you stick to the classic Model-View-Controller, MVC, design pattern).  However, I’m not happy about that.  I want it to be smaller, leaner, easier to manage, and finally make it easier to transition to another file format or web service for the database.
There were a few tests.  Most failed.  After perusing the tests I discovered a major problem: I didn’t understand what the class actually did.  Tests serve as a way not only make sure your code works, but to do things like ensure contracts with the rest of your code are respected, and, quite importantly, they document the code.  The tests failed in this regard.
Part of the problem with the tests is that I didn’t understand how to write them effectively.  I probably still don’t always write them effectively, but I’m better 2 years on.  Furthermore, since I really didn’t understand how to mock code in Swift, the class was almost impossible to test anyway – mocks would be required to do unit tests.  So, what kind of tests did I have in the end?  Integration tests.  These tests had to use real data and real database in order to work and the database was likely to change over time.
So, chuck the tests and start over.
Now, what to test.  Generally, I want to exercise  all the code in my class.  But, since I wrote this for myself, I made no distinction between public and private methods and variables.  Anything that should be private should not be tested directly, since those things should impact other calls.  In addition, it was worth eliminating any dead code (if I really needed it back, I use version control like any sane developer).   I found a bit more of this stuff as testing went on, so it’s okay to eliminate stuff later.
Finally, it was time to slog through backfilling the tests.  Keep in mind, at this stage I’m only testing to suss out the contract this class is using to interface with the rest of the app, and not make meaningful rewrites or refactors.  So, the goal is to be minimally invasive.   The main exceptions (other than dead  code removal and public/private) include making the code easier to understand and easier to test.
As far as understanding, I had a number of values that were essentially magic numbers: i.e. numbers that seemingly come from nowhere.  Fortunately, I knew what the numbers meant, so I could assign them as variables or enums.  I would have trouble understanding what 832 might be, but if I passed the value around as “George”, it can make more sense.  If the code makes more sense, it will be easier to test.
Going through the testing process quickly brought up the need for mock classes.  In particular, mocks for FileManager and NotificationCenter.  Both of these standard Apple classes are commonly using on Apple platforms.  Most apps will use these somehow.  However, they are both essentially singletons: they both are single objects in your app that you can call from almost anywhere.  Very  convenient, but very hard to test when you do so.
First, the FileManager handles files: creates, deletes, verifies it’s there, etc.  Makes sense to use.   However, all sorts of things can happen while doing these operations.  For example, your code might do X if a file exists or do Y if it doesn’t.  Imagine for a moment your app will ..usually.. successfully create a file.  It almost never fails.  Maybe once in a blue moon something happens but it’s fairly consistent.  How do you test the failure state?  Your file is always created, right?  What if you could get the FileManager to say, “sorry, there’s no file there?”  You need a mock for that.
I’m going something bad and refer to anything that isn’t a real object as a mock: so I’m going to conflate mocks, fakes, and stubs.  Mock are objects that stand in for real objects and track what’s been called on them.  You can these assert that what you expect was called, was indeed called.  However, these objects are essentially non-functioning.   Fakes on the other hand are functioning objects that typically function similarly to the real object but how it works is simplified.  Stubs are similar to mocks but can respond with data.   So if you call a method on a real object that returns a value, a stub can do the same thing with canned responses.  Of the three, mocks and stubs are very similar and, in my usage, are best mixed.  You want to see what’s called, and if I response is required, then  do so.
So, we need a mock(with stubs) FileManager.  But how?  Swift makes it tricky given that it’s type safe.  Indeed, how to we deal  with those singleton calls?  How are those replaced with mocks?  It’s easier than it sounds.
Step 1.  Create a protocol in you app for the FileManager, say “MyFileManagerProtocol”.  In Swift, it will look a bit like this:
protocol MyFileManagerProtocol {
}
Then all you need is to make FileManager conform to that protocol with an extension:
@objc extension FileManager: PTFileManager {
}
Now, Apple’s FileManager conforms to MyFileManagerProtocol.  We’re not quite done yet.  Take a look at the FileManager calls you use.  Simply add those function signatures to your own protocol:
protocol MyFileManagerProtocol {
func urls(for directory: FileManager.SearchPathDirectory, in domainMask: FileManager.SearchPathDomainMask) -> [URL]
func fileExists(atPath path: String) -> Bool
func createDirectory(at url: URL, withIntermediateDirectories createIntermediates: Bool, attributes: [FileAttributeKey : Any]?) throws
func copyItem(at srcURL: URL, to dstURL: URL) throws
}
Since FIleManager already has these methods, it already conforms to your protocol.
Now, let’s change how we call the file manager.   Instead of calling the singleton version we want to inject our protocol version.  So, on init of the class, let’s inject it:
init(fileManager: MyFileManager = FileManager.default {
self.fileManager = fileManager
}
In the class, replace every occurrence of FileManager.default with fileManager (except for the init).  Notice in this using the standard FileManager by default.  This may not be desirable in some cases, but this should be fine.
Now, make a mock (and it will also be a stub).  Be sure to only include the mock in the unit test target, not the app.  Don’t forget to do a “@testable import” as well.
class MockFileManager: MyFileManager {
}
Of course, now you have to make it conform to the protocol.  In this case, we want to make some of these calls to be both mocks and stubs.  I’ll show one:
var fileExistsCalled = false
var returnFileExistsPath: Bool = false
var capturedPath: String = “”
func fileExists(atPath path: String) -> Bool {
fileExistsCalled = true
capturedPath = capturedPath
return returnFileExistsPath
}
What does the above do?  It allows us to verify that the method was called (if called, it sets fileExistsCalled to true).   It allows us to capture what path was used in the call (capturedPath).  Finally, it allows us to return whether or not the file exists.
So a test might look something like (pseudo code):
func test_doMyThing_givenFileDoesNotExist_ThenDoMyOtherThing() {_
// Given
let mockFileManager = MockFileManager() //create the mock manager
mockFileManager.returnFIleExistsPath = false //make sure we know it won’t find the file
let testObject = TestObject(fileManager: mockFileManager)
let expectedResultIfDoesntFileExist = failedResult
let expectedResultIfFileExists = successResult
let expectedFilePath = “/path/to/file”
// When
let result = testObject.doMyThing()
// Then
XCTAssertTrue(mockFileManager.fileExistsCalled) //make sure we called it
XCTAssertEqual(mockFileManager.capturedPath, expectedFile) //make sure we called the correct path
XCTAssertEqual(result, expectedResultIfDoesntFileExist)
}
The notification center is similar, except that you are capturing calls to the notification center.
More next time

Returning to An Old Project: Brining the Power of Unit Testing and Refactoring (and rewriting) To Resurrect the Dead

I have a project that stalled.

The project is an iOS app. A fairly simple, really. The app is, more or less, a graphical interface for database (I suppose most apps are some form of this). The app has been around in one form or another for many years, but doesn’t need to be update that often. The truth of the matter the database that i’m using hasn’t been updated in a while because I’ve never gotten around to write an editor. So, the last major release was around 2015.

In late 2015 or so, I had the brilliant idea of porting the app to MacOS. After all, I eventually need an editor. A twist in that idea was the app would be 100% written in Swift, if possible. The iOS version was written in Objective-C.

It turned out the transition went well. I released the MacOS version in 2016.

It went wrong when I decided it was time to unify the code base of the apps as much as possible. In other words, the iOS app had to move to Swift too. Why? Well, I got a job and this particular app was a relatively low priority.

Fast forward to 2018. I decided that it was time to expand the things I do at work. Thus, I decided to become a Test-Driven Development (TDD) instructor (in training at least). So naturally, I decided to find a relatively small project that I can apply my (limited) TDD chops.

It is time to revisit my Devonian Database iOS and MacOS apps. These apps are available for free on the different App Stores.

The iOS app been in the app store for many years, and I managed to get the last version out in 2015 or 2016. I fixed it up and released as part of a job hunt: have a good working app in the store to prove I can do the work. As part of that effort, though, I decided to release the Mac version of the same app. Cleaver me decided to make this new version entirely swift: no Objective-C code at all. That app was successfully released too.

What got me was the next decision: make my iOS app Swift only as well. The decision made sense, and still does. Minimize the code base and both apps are easier to maintain. While Objective-C is still a powerful language, Swift is an easier language to use (with the exceptions of occasional major changes to the languages and the difficulties in testing and Objective-C interoperability). But it takes time and energy. I got the job and the app fell by the wayside.

I started looking at the app again and it’s a mess. For most developers, looking at your old code is a cringe-worthy experience. Now that I work on a team, I find my old code has one fatal flaw: it was written for me… The problem with “written for me” that that the me of today has no idea what the me of 2015 was thinking. I wrote things quickly. I wrote things using simple and convenient design patterns instead of something that would be more appropriate. I did write a few unit tests (or in many cases integration tests pretending to be unit tests), but they were few and far between and written at a time where I really didn’t understand how to write good Swift tests. Not to mention many didn’t work at all.

So, my first few hours will be spent looking at the core of the app – the database management code. I’ll blog the results of this work.