On my mind this week because I’m giving a workshop and a talk on unit testing UI code on iOS.
Tests as Documentation. You think you remember what you did and why… but you don’t.
Replace debugging. I almost never use the debugger. If something isn’t working as expected, I just start writing tests and figuring out what is going on. When the bug is fixed, and I’m berating myself for being stupid enough to write it in the first place, or not figure it out sooner, I also have a bunch of tests to show for the time I spent.
Low energy. Seriously there is no 10x engineer, and none of us function at 100% all of the time. I used to rationalise that I should stop work before I start breaking things. But now when I know I’m not going to be effective at creating I test instead. Maybe I’ll write the thing tomorrow morning, but this afternoon I stub the method and write the tests for it.
No perfection in testing. I’ve mainly seen “we should do it this way it would be better, but that requires us to refactor all the things” argued in places with poor test coverage. I think the point is, do you write a test at all? If a test is stupid, it will take seconds to write and you’ll never need to worry about it again. There is no need to worry about the size of your test binary. Speed, yes, but that is far more determined by integration tests than unit tests.
If you want to improve testing of your iOS apps, I have put together a digital workshop.
I’ve started to port Show and Hide to Android. There’s still a lot to do, but I hit a milestone of having it working end to end on the emulator last week, which was exciting.
One of my friends asked if I was using any libraries to make it easier, and the short answer is no. But I think the long answer is potentially interesting so here it is.
1. I want to learn Android
I don’t think there is a better way to do that than building an app from start to finish on that platform. The more libraries and bits and pieces you work with, the more you learn the intricacies of that system, rather than the platform itself.
Sometimes that is exactly what you should be doing. There’s often no point doing something that has already been done. But cross-compiling isn’t re-implementing something that exists already, it’s choosing to write something a third way and hope it works well enough on both platforms (more on this later). At this point, if I haven’t written an entire app already, I don’t think I can really have the information to just decide that cross compiling is the way to go. How could I compare the experience to a problem that I haven’t solved?
2. UI Code
Fundamentally there are two reasons why I don’t think we will ever have a good cross-platform UI solution. The first is that with major releases every year, it would be a huge amount of work to maintain such a thing. Almost no-one has the resources to run such a project, and of those that do even fewer have the incentive. The second is that the UI patterns on iOS and Android are different enough that what is “right” on one platform won’t feel “right” on the other.
3. Non-UI Code
Here I think there can be a good argument for a cross platform solution, depending on what you’re doing. Libraries like Parse are interesting, making it easier to abstract persistence and networking out and share it.
But – the core of Show and Hide on iOS is about 400LOC of optimised C code that is tied to the way that the platform represents images. I don’t even know if that would be possible to share x-platform, and the chances of it being performant enough for my purposes is vanishingly small.
Because it’s a relatively small amount of code and I deeply understand it, moving it to Android took only 1-2 days. I’ve yet to see whether it is performant enough, but this way I’m also in a much better position to optimise it.
Porting the Architecture
Instead of using cross-platform compilation what I’ve been doing is:
Building the UI according to Android best practises (or trying to).
Keeping (initially) the same function definitions for non-UI code.
Changing the implementation to make sense on the platform – e.g. the way that iOS and Android represent images are completely different.
This means that:
The two apps have very similar architecture – the same objects, with similar methods on them.
The unit tests are near identical: given this input expect this output.
It’ll be some time before I can declare success on this as a strategy, but I’m cautiously optimistic.
Filed under “things I didn’t realise anyone else would find useful”.
The Hybrid App
The second iOS app I worked on was a hybrid app, and I became a master of the UIWebView. It looked native. But it didn’t quite feel native because… performance.
We had a bridge that connected the iOS to the Javascript and vice versa. A fairly horrible solution, but this was back when people believed native apps were just a stop gap until the web won, and that this product didn’t really matter on mobile, anyway.
Eventually we discovered that passing data across when calling Obj-C from Javascript was really slow but that calling a Javascript getter from Obj-C was just fine. Crisis averted.
The Location Tracker
This one had a myriad of issues. In theory, the API says you can just register for location updates with accuracy. In practise, if the accuracy is… accurate (<150m, if I recall correctly) the GPS turns on, stays on, and the battery rapidly runs down.
Even if the phone is sat on your desk, not moving (one would have thought the accelerometer would be useful here, but no).
So the answer was to operate with the GPS off most of the time, and then after a notification turn it on again for a limited period.
Then there is the sheer volume of updates you get. If you just send each one with a network call, then… that’s a lot of network calls.
So instead at each point you have to decide, should I send the update now, or wait for better accuracy. Meanwhile, cache the unsent ones.
Now you have an app that is running in the background, and being awoken when there’s an update. But if the app gets kicked out of memory, all your unsent locations are lost.
Fun story – we had it just about working and then I went to Kangaroo island which has terrible cell-phone service, and was taking loads of pictures. So even though I was intermittently on the network, hardly any locations were being sent because the camera was causing the app to be kicked out of memory.
Basically: we had to implement persistent storage for location tracking to work.
The Image Processor
This app was handling a quantity of data (pixels) and had time and space problems.
To reduce the memory usage, I got rid of as many Objective-C objects as I could, and replaced them with C. So the NSArray of NSNumbers became a CGFloat[]. Stopped using UIColor and started working with raw RGB values.
Memory footprint reduced by around 66%, and the app became much, much faster.
(I wrote all the tests on slower and easier to read code, which helped a lot with this process).
Major problem here was how little information there was on the internet. I’m not an expert C coder, and I would look for how to do something in C within Objective-C and the answer was typically “there is almost certainly no need for you to do this“.
The other performance improvement I made was to break up the processing work and do some pre-processing in the background thread, so that the wait is split into two very short waits, as opposed to one, slightly longer, wait.
Things I’ve Learned
Find the One Thing
There’s not, in my experience (assuming you’ve been sensible around the basics of ARC, table views etc) any need to go through your code looking for “things to optimise”. iOS is mostly pretty good. The problems I described above all occurred in the most non-standard places of the app. Getting a deep understanding of how that thing worked was how it was fixed (aside from the first one, which I still think is just weird).
The Profiler is Not That Scary
Memory allocations is the one I’ve been working with most recently, but there is also a way to profile which parts of your code most time is spent in. I can see this being really useful. I used to be really intimidated by the profiler, but having spent more time with it, it’s OK [helpful tutorial].
Performance Tests are Easy, but Slow
I started adding performance tests, typically at stress sizes (10x the data I expect to process), using the (void)measureBlock:(void (^)(void))block function in XCTestCase [more info]. Each performance test runs 10x to give variance, and you can save a per-device baseline. Because these are my most time consuming tests I don’t want to run them when I’m developing against my tests so I moved them out into a separate target (that takes a few minutes to run) so that my core test target runs faster (<30 seconds).
Continuing my obsessive testing strategy, I had to set up KIF for UI Automation Tests. Honestly it’s surprising I didn’t do it earlier, but since the UI is very simple I was getting better ROI doing unit tests – I see KIF as a sanity check, not as a way to debug anything.
Step 1: I went through the app with the Accessibility Inspector on and made sure everything had an accessibility label.
Step 2: I made a new test target for KIF tests, and made sure it ran OK. I made a mistake at first and created a Mac testing bundle rather than Cocoa touch (I don’t know why it was even on that screen, I have never made a Mac anything).
Step 5: Trying to debug baffling error messages about pulling in SenTestCase which shouldn’t be required because I’m using XCTest (the default). Adding it as a dependency out of desperation, getting duplicate symbol errors. Some time figuring out what an #ifndef does (basically – if defined). Feeling very discouraged.
Step 6: Pair programming (or, pair-debugging). In desperation, as we have no other ideas, I delete the (theoretically unused) OCUnit targets. Everything works. I am enraged. We are both confused.
Step 7: Check in time! Oh wait, it’s not, because I have edited a submodule.
Recently I had cause to play with Adobe Air, which I missed when it was popular and it’s been pretty hard to find out what is actually going on with it as much of the information is out of date, other than Adobe’s corporate PR engine.
It’s supposed to make it really easy to create apps that work on the web, desktop, iOS and Android. Which is a cool story I’m sure I’ve heard before.
The first step was installing it, but after much confusion I discovered I had just installed the runtime, and not any way of actually creating apps. So in spotlight I find the installed, and the uninstaller, but not the actual thing.
OK great, so now I just need a development environment. The first recommended one I found was AptanaStudio. Except Air is not supported in Aptana 3, the latest one (I discover this, of course, after I have downloaded, installed Aptana and it is refusing to close on my machine – good feelings).
New way: install the Aptana Plugin for Eclipse and then separately the Air Plugin. This might be the Flash Builder plugin, but given the last mention of this is from 2008, maybe not. More research uncovers that Flash Builder plug in doesn’t work with versions of Eclipse above 3.6. Eventually I find that the Aptana Plugin has it’s own set of plugins, once of which is the Air plugin. But Aptana again barely works.
I end up downloading a trial of Adobe Flash builder. I’ve realised that the bit on the Adobe site that says you don’t need to buy tools from them to develop Air is a lie. Because Air is out of favour, only Adobe is updating their dev tools. Unless you are on Windows, where you can use FlashDevelop apparently.
Luckily they have a one month free trial.
In all, my instructions for getting started with Air:
We all tend to view the world through rose tinted glasses. Depend to look through a lens that colours what we are seeing. Unless take them off, hard to see what the situation is.
Glasses worn by iOS is “we are indie devs”. Sense in community, the little guy, hacking away on the porch or in the evening. Stood for a long time, back when it was actually true. Now maintained even though probably one of the biggest communities of devs in the world.
Not unreasonable. Vast majority of companies producing apps are very small. Even “big” less than 30-40 people.
Is a sense of being small, see things through the lenses of being small.
When we look at Apple, relate to the apple that was Steve and Steve in their garage hacking. All about innovation, friendliness, camaraderie.
We see Apple, we like to see Apple. The Apple we want is the Apple of this 70s breakthrough, this small indie thing. However reality is, Apple is (depending on day of week and season of year) is one of the biggest companies in the world. No longer based in a garage somewhere in California. The thing that is most important to them is the stock price. This is true, because the law says it has to be. Has an obligation that every decision you make is in the best interest of your shareholders. If can be shown not, you can be imprisoned.
“How does this effect us as a public company” – reality, shareholders come first.
Not everyone. But Apple as a company, this has to be what it is about.
Have to ask, how can we make the best investment for investors. If you go to their website, clear they have done that by deciding to be a hardware manufacturer. Click around for a long time before any mention of software.
Chosen to make money through hardware. Strategy – build fanatical customers, who love the hardware. Carefully decided. Look at the presentations, the words they use.
“The every day man’s designer brand” – slightly more than most people than they can afford, but within reach.
Would never do a low end laptop, because it would destroy that brand. Target people with aspirations. Hence the margins on their laptops.
Strategic decisions are around these things. And nothing else. Desirable to customers. Maintain brand. Bottom line.
Apple are not the friendly garage of indie devs. They are a moneymaking machine.
Faster accept that, the easier it is to deal with reality.
Not going “developers developers developers”
Don’t hate developers. Just not their priority.
Do Developers Hate Developers?
Open source projects:
How many have considered users in code open sourced.
How many have sent money?
How many have contributed a significant fix or amount of code?
Argument hear again and again is “Apple should look after us as developers because we make significant contribution to their business”
Financially: tiny. So small they wouldn’t notice if it disappeared.
App ecosystem does enhance Apple’s attractiveness to customers. Will place some value for that reason. Because it creates value for customers who they want to be fanatical about their products.
As developers, the very people making that complaint, we don’t stop to think about people who are producing code that makes a significant contribution to the things that we make. Bit of a hypocrisy. If want that to be true, need to value people who make a contribution to people who help their code.
Don’t hate devs. Just value customers more than you.
Love
Love is not about what you feel, it’s about what you do. It’s in your actions and your deeds.
Sherlocked. Apple have Sherlocked a number of apps. (Replacing a dominant player in the market as an independent – Watson replaced by Sherlock).
Not mean, evil, but because they want to give something to their customers. Just a business decision. Just an attitude about them making better products.
Deprecated. Deprecation is a curtoursy to devs, could just remove the API altogether. Good for customers, because forces things to do things in a better, more efficient way.
Microsoft for many years refused to deprecate anything. People are still running things on XP because they thought they must never get rid of anything that breaks anything. So we all had to live with an OS that is full of crap. So much was there for legacy purposes. But it didn’t make a good product. Better for people who were no longer giving MSFT money than for people who were (buying).
Not about devs. About building a better product for customers.
Changed. That is exactly why things change.
How do Apple Love Developers?
Apple, iOS dev centre membership. $99. Charge again to do the Mac. How much does it cost Apple to provide with what they provide? $99 doesn’t cover it. Nowhere near.
Apple known for small teams, people often shocked by how few people work on something. Doesn’t matter. Have very clever, very expensive California devs working on things for your benefit. Subsidising your career. That is how much they love you.
Windows, you pay 1200GBP every year for Visual studio and MSDN subscription. And that is still a subsidised price.
Next time you complain about the App store pricing. Remember if going to do that, going to start charging a realistic price for tools.
Short while with Mac app store. Xcode was $4. Was like the world had ended. “How could they charge me $5 for the thing that I make my living from?”
Apple understand value of devs, charge very little for tools that allow you to do your job.
Not because love you, but because you create value.
WWDC videos. 2006 would wait months for vids, be delivered on DVD. Apple gained nothing by investing millions of dollars in ensuring you can get the videos on the same day. Investing in you, because it helps them. If you have the latest information, can upgrade your apps faster.
Whichever title. Reality is. They understand your value, and provide reasonable practical support without taking their focus off of their main business and their customers.
Picture of my app working after I fixed a bug… using extensive unit tests.
Maybe it’s my functional programming background, but I love static methods. Not everyone gets this, some people parrot the phrase “static methods are bad for testing” but don’t seem to understand what that means.
Static methods are great for testing. Inputs in, outputs out, side effects – what side effects? What’s not to love?
What they are bad for, is mocking. This is an entirely different problem.
Note: here I’m using static to mean methods without side effects, which technically includes Objective-C methods that are denoted with a + (class methods). Turns out, these methods are not technically static, they take place on another object [detailed explanation]. Static methods in Objective-C should properly be C methods.
I’ve been doing a bunch of image processing work lately, and it looks like this:
Break image down into pieces.
Do stuff to it.
Put it back together.
Each of those things individually are great for static methods. Take an image, break it down – do I have the requisite pieces? Put some pieces through this process, is the result right? Given some pieces, do we get an image with the right properties? Wonderful.
I’ve spent hours (OK, days) writing tests for these things, because I was not an expert on image manipulation and in my opinion there is no better way to deeply understand code. I found and fixed a horrible bug. Next, I need to optimise, but my thorough test suite gives me confidence that I can do this refactoring without breaking things, and that I have a good API.
But – I don’t want to deal with this elsewhere in my app. So I absolutely want this stuff to be mockable. So I use a very simple pattern:
Expose a class method that updates the object.
Have a static method that takes whatever information is necessary, and returns whatever needs to be updated.
Class method calls the static method with the requisite information, and updates itself accordingly.
This becomes easy to test. My static method has tests that cover a full range of inputs and outputs. My class method just needs a single test to ascertain that stuff gets updated.
When I’m mocking this object, I just mock the class method. The static one never gets called.
Some people’s irrational loathing for static methods can cause problems in code review. If you don’t want to argue, two options:
Split the static methods out into a “MyClassHelper” object. For larger things I do this to be tidy.
Just don’t declare the methods as static, but use them as such following this pattern – I really don’t like having to do this. But I have done it.
Other People’s Static Methods
The main issue I run into with static methods, is that other people have used them – I find it is common in API design when classes shouldn’t be subclassed.
In this case, I group things together logically in a wrapper. For example, in my current project I have a class that wraps various static things I need for handling images.
Coverage vs Confidence
I am a proponent of code coverage, and regularly use it to guide me to the places in my code base that need a bit more love and understanding. But it’s important to note that coverage doesn’t necessarily mean confidence.
If you have a (say, static!) method that takes a test image and returns an array of length 9, and you verify at the end of it that the array is indeed of length 9, voila you have coverage. What you don’t have, however, is confidence. What are the 9 things?
In part for this reason, I start small and work up incrementally. If I start writing a large test and get confused, I break it down and write a small one first. So in this case I might write 3 tests:
The result has 9 things in it.
The 9 things are correct.
The completion block gets called.
So if I break the test and 9 things are no longer produced, tests 1 and 2 will both fail. If I break it and 9 wrong things are produced, just test 2 will fail. If I accidentally delete the line that calls the completion block, just test 3 will fail.
So when I break something, multiple tests will fail, and the simplest one that is failing will be where I go first to determine what I’ve done.
The other thing about confidence, is that tests should break when you change stuff. The other thing I use to improve my code confidence is when I make a change that doesn’t result in failing tests and I think it should, I go and write some.
TDD Wut?
I don’t often do pure TDD, when testing UI code I expect it would be difficult to the point of not being worthwhile, or in some cases not even possible. Occasionally I TDD, if I’m trying to figure out how something should behave I will start with test cases. More commonly what I do is:
Write the simplest thing.
Write tests for it.
Refine my code, optimise it etc.
I used to employ this strategy a lot when I was writing lots of Guava. I adore functional programming, but a nested for loop is easier to read, understand and debug with print statements if necessary! Because at least initially, it’s nearly as likely that I have made a mistake in my test as in my code.
TL;DR
Static methods are great. Hide them away and they make testing easier, not harder.
Thanks go to Martin, for reviewing a draft of this post.
Cookie Consent
We use cookies to improve your experience on our site. By using our site, you consent to cookies.