I wrote a blog post about Cedar way back in the day. Now, Xcode ships with OCUnit.
I’ll go through a basic way of adding tests to an existing project, as that’s a very common task, and not very well documented thus far. Props to the following blog posts- I’m consolidating their advice basically in a single one, with screenshots.
Why do tests? Well, it’s a great way to work. If this is new to you and you’re more interested, check out tons of resources referring to Test Driven Development (TDD). Of course, if you read this from a Google search, you already know what that means.
1. Add New Target
1. In Xcode 4 (.3.3, for me), add a Target. Took me forever to find this. It’s the “+” circle at the bottom of the Project pane:
2. Set to Cocoa Touch Unit Testing Bundle
2. Change Target Build Phases & Settings
Click “Build Phases”, expand “Target Dependencies” and add your App.
Click “Build Settings”, and add:
Under “Links”, “bundle Loader”, “build/Debug-iphoneos/[yourapp].app/[yourapp]
Under “Unit Testing”, “test host” put in “$(BUNDLE_LOADER)” (will populate with bundle loader).
3. Change App’s Bundle Settings
Set “Symbols Hidden by Default” to “NO” – frankly, this is usually already set to No.
4. Test to Run!
So let’s run this, even though the sample test sends a failure. Still, it’s good to see that in action right off.
Now, if you’ve installed this into an already made project, you will select the target in the “manage scheme” area, then select Product/Test from the menu.
One distinction- if you’ve started a project with OCUnit “baked in”, then you don’t have to select the scheme/target in the upper left, simply use Product/Test from the menu.
5. Check Out Those Errors
Unlike the lovely test frameworks in rails, and some other languages, OCUnit prints out boring black and white console log errors: so have your console up and running when you Product/Test. Here’s some live test results!
* Testing your Bay Area cred… this title is from the ad that ran 50s… 90s in the SF Bay Area for Berkeley Farms, that had a very catchy tagline: “Farms? In Berkeley? Moooo…” Several radio spots can be heard on their site now, and you can check out some old photos of this neat Berkeley dairy.
I really have never thought I had Imposter Syndrome. I’m not a shrinking violet, I tend to talk pretty authoritatively, I’m confident, like to speak in public, etc. Yet, I joined a mailing list for women-techs and during discussions this term came up. I looked it up, and started locating this behavior in a few of my interactions.
Imposter Syndrome is the feeling of inauthenticity, that you will be “found out,” that you don’t belong, and roughly that everyone has reached some basic level of knowledge or performance, that you haven’t.
Your accomplishments are due to:
luck
timing
or as a result of deceiving others
I only got that award/Hackfest win because of tokenism, or because our competition wasn’t that great. Or, they thought it did something it patently didn’t (good/fake demo?). Not only do I think these things occasionally, but, others tell me this. I’m not kidding. Right after presenting Ruckus at AngelHack this guy accused our demo of not working, of being faked. To our faces. Most presenters didn’t even have a demo, and very few had one that worked.
For me, the imposter syndrome rears its ugly head during technical interviews. No matter if I’m a subject expert, no matter if I don’t know the backgrounds of the guys (and they are always guys) interviewing me, I will give them more authority than they’re worth, and think they know things I don’t know, and doubt or diminish my own accomplishments.
Afterwards, when reviewing the transcript in my mind, I am amazed at my lack of confidence and surity, and, generally, how I give them more than the benefit of the doubt. My solution to this- work more and more on doing LinkedIn checks before the interview, or, ha, ask what material’s going to be in it so I’m prepared. Also, as women (or people,) we’re taught not to be braggy, but we have to be able to state our accomplishments clearly.
I also tend to over-estimate the knowledge and experience of speakers at conferences. Recently volunteered to be on a panel simply because I wanted to attend and I was wait-listed. It was on contracting in iOS. Turned out I was on the upper-median spectrum of experience on the panel. Why did I assume that they were qualified to speak with authority? Because they were men? I didn’t know the facilitator, and yet assumed he knew his stuff. So in a way the Imposter Syndrome feeds off itself, as more people undervalue themselves, they are also overvaluing those around them.
We all know that an accurate understanding of your own knowledge is what helps you grow and learn. It’s actually quite rewarding when I find out a pocket of knowledge that I don’t have, and I’m eager to get better at it. I am actually, as I write this, embarrassed to admit my latest learning. I recently tackled and won a battle with threaded programming, in Android and iOS, that has me eager to redevelop all of my old titles. I should write about it, but, having had bombed a (cough) technical interview about threading in Rails, I am shy of mentioning that it’s hard, and that I’ve figured it out, and it has really improved my apps. I’ve also done some serious image and sound encoding and now want to add voice and image creation to all of my old games. I got shot down on StackOverflow by some guy about a sound encoding issue, so guess what, I’m shy about writing about that triumph.
Anyway, it’s a journey, but having an honest assessment of your own skills is vital to learning, so in a way it’s inhibiting our abilty to publicly admit: I didn’t know this, and now I do, and I can now share it with others.
Maybe, perhaps because I’m an English major, I tend to notice patterns in my speech. So, I noticed recently that I keep saying the same phrases in discussions regarding mobile app development:
- secret sauce
- no login
- no back button
- mentoring
- phase 2
- did the customer want that
- don’t say user
- is it needlessly complex
These discussions came up in hackfests, in client work, and in advising on technical projects. What the repetition of these phrases means to me, is that I need to reinforce certain concepts. So we all like Lean Startups and Customer Development, but are we essentially practicing those behaviors in our day to day work? The fact that I have to repeat them means that they’re having a hard time sinking in. You can know something, but to do it is another thing entirely.
Secret sauce
What this app actually contributes, essentially, that is different in the market. The copyrightable/trademarkable essence. The actual chemical recipe for turning water into wine, for example. It’s the core concept that you’re testing, and everything else is a distraction, and muddies the customer testing. You need to narrow down the experience to highlight this secretsauce.
No login
It’s very hard to start a project and not begin at the login process. But, you know what? Logins are assuming that people are going to come back. It’s not actually a pre-requisite for an app. You can grab session information, conduct a Facebook login later down the line, heck, just leverage Facebook entirely for login, etc. There are other options. Is it part of the secretsauce? Largely, it’s not.
No back button
I really hate them. It assumes that it’s important to store the customer’s “state” at all time, and it assumes that it’s core to the app idea or secretsauce. Back buttons are key to making a bloated big application with lots of overhead programming. This was a revolution in thought to me, to exclude them, and now that I’ve gotten over it, I’m not going back! I’ve met really horrified faces when I say this, but it’s true. Integrating a back button means sometimes 3x fold more work, so only include it if it’s part of the “secretsauce.” Usually, users can go through a flow and come back to home. No back button.
Mentoring
First introduced by none other than the wonderful Shaherose Charania of Women2.0, I admit it took me a while to get on the same page, and now I’m totally embracing it. This concept is awesome. Basically it’s a license to mentor, coach, or delegate the work to someone else. You hire a mobile developer, but you can also, (gasp) hire a developer to MENTOR you in mobile development, and then you can (gasp!) do the stuff yourself. You know, teach a man to fish, etc. It’s key to find someone who has good teaching skills, knows their stuff, and has time and interest (it’s non-competitive) to teach you.
Phase 2
During development, you will think of new things, and they will be great ideas. And you should keep a list of those ideas. And you should not involve them in the current phase. You should say no. Say no to the new idea. Keep focused, deliver the secretsauce.
Did the customer want that?
During development, you will think of new things. They will be awesome things. But you know what? You’re not the target demographic. I think the hardest part of Customer Development is not making an app that is precious and owned and used entirely by you, but spread out in the world and owned and used by others. So you have to constantly validate new ideas by the customer base (when you get them). We’re too close to the project and our opinions and use cases aren’t really worth much, essentially.
Don’t say user
From Sarah Allen, who said in a talk, “why call them users… are they drug addicts?” that cracked me up. Stylistically it’s outdated to call your customer a user. We are not all at terminals with our keyboard inputs. We’re at the playground watching our kid, checking Facebook, or in a club taking photos of old college friends, etc. Getting an idea of your customer and thinking of them that way is a big fat step towards making relevant, good apps. Recently at a hackfest my only feedback on a powerpoint pitch for an art festival presentation was – “call them festival goers” because it’s going to show that we “get it” to the judges. It shows you’re connected to the customer and not a bot. This is an old concept in Customer Relationship Management, but still is so maligned and not used, it’s sad. Basically, getting used to saying “festival goer” is 1/2 way to actually getting to know, and develop cool stuff, for your customer. Imagine going into a Disney pitch and saying, “user” instead of “parent” or “delighted kid” (who will become a parent, of delighted kids, etc.).
Is it needlessly complex?
A great thread on a geeky point probably worth an entirely new blog post, this thread brings up some key points- essentially the original poster wrote an abstracted class against the wishes of his client, and he was terminated.
I don’t consider myself a great programmer- my strengths are not in impressing other geeks, but impressing my clients and making great apps. One mantra that helps me “get it done”: I don’t make a routine, method or subclass unless it’s merited by repeat use. Essentially, don’t go all abstraction unless you need to. I can overprogram shit on my off hours. OK sorry to get all scatological, but certain architectural decisions are smart, some are … needlessly complex. For example, offloading logic onto the server, because you’re going to do two mobile versions, and you are using the iPhone simply as an interface controller: good idea. Building out a datamodel and data feed for a series of buttons that could be static in the first version: needlessly complex ( and, assuming that the options will change without customer input… “phase 2″ and “did the customer want that”).
Do you have mantras? Would love to hear, if you do.
For a long time, years in fact, I used the site as a reference. You have an error message, and you can search for it, and find a lovely discussion of fixes, problems, etc. I had joined a year ago, but got some grief and didn’t login in again for a year.I’d run into a very difficult bug and posted it (Facebook deep linking on Android emulator). A guy answered in 4 minutes and helped me troubleshoot- there was no clear answer, but I was grateful for a sane head and new set of eyes. Full of good karma, I decided to give back. I made some classic newbie missteps before I got my footing. Of course, I ran into some douchebag opinionated guys, (“you don’t know what you’re talking about.”) but still pushed through, until there I was, up late one night, in a chat session with some high school student writing their first iPhone app. It was karma, and also wanting the +10 associated with being the correct answer.
As I got into the various features, I started exploring the user profiles. That’s when I realized that I’d only come across one visible woman’s profile, among the hundreds, or thousands of profiles I’d seen up until then. I posted an innocent question to a women’s engineering list:
Lately most of my annoying-male-behavior stuff comes from StackOverflow, which led me to think, are my lady programmers on there?
I’m enjoying the “roulette” style questions, going to “unanswered” and seeing if I know anything. And also trying to give back as it’s saved me a few times in the last week.
Anyway, I’m me on there, if you’re on there it’d be nice to know your username.
I was hoping to see maybe one or two – or to just see if they got nasty responses to their questions like I did. What happened is an 11-day discussion, 57 emails, from women about how largely they disliked the site, lurked, or joined and left.
So there’s good news, there are women on StackOverflow. The visible ones are far below the representative % of women in the industry.* So you can safely determine that it’s an unfriendly-to-women place. Many “men” are women. Some women have two profiles, one that they use to ask what they consider dumb questions, one where they answer questions.
Here’s a list of comments from our 57-thread discussion about why women aren’t on StackOverflow:
- The blatant one-upmanship of the site turns them off
- There’s nothing they can contribute (seriously, many women feel that way)
- They don’t want the grief of getting downvoted (because they are a woman) * (more on this later)
- Like me, just didn’t consider contributing
- They use neuter or male profiles
- One or two women were early users and got turned off by the online behavior of the sexism and discrimination they endure in real life.
We are now discussing creating a hosted, private question and answer site similar to SO. I honestly don’t think that’s a good idea for improving the visibility of women in tech. As one mailing list programmer wrote to me, “It’s a battle some of us just don’t want to fight.”
My Short History on Stack Overflow
Milestone #1: My first post- an answer (kinda ballsy!) Notice: no upvotes. Still, proud of it, and the content was solid.
Milestone #2: My first accepted answer! On the site, the question author selects the correct answer. I was chosen of 3!
Milestone #3: Answering (and being selected as the answer) in a language you don’t consider yourself all that great at. Oh, and people are grateful?!?!
Tips To Having Fun On StackOverflow
I largely use SO as a place to gain confidence, and a good prep for interviews. I also use it to procrastinate. I am a geek, so I like to browse it much like a bookstore, looking up issues or languages that have crossed my path recently. Mainly, of course, I use it to find answers to questions.
If you want to participate in the community, and not lurk, there are some tips to having fun:
- Post code. Your answers will be up-voted, and selected, if you do the effort of actually writing a sample few lines of code, or finding old code and popping it in. Most developers don’t read non-code formatted text, it’s true.
- Be polite, but don’t grovel, apologize, use disclaimers, or caveats. Simple, and direct.
- Don’t chat – use the chat tool if it gets to be a back-and-forth discussion.
- Comments are comments, answers are answers. That was my newbie problem, getting them mixed up; putting comments in the Answers box and vice-versa.
- Use tags – in searching, in finding questions to answer, in writing your question. It makes site more usable and faster. It took me a few days to find the use for it, and it is very useful.
- Don’t rise to the bait, avoid attention-seekers, etc. There are douches here, just avoid them. Let the moderators do the policing.
* Check out this Fiery discussion on “meta” StackOverflow regarding just this issue of women lurking, but not joining, StackOverflow, has quite a few references to QuantCast, a demographic analysis engine. One comment (that I didn’t verify its veracity) says 26% of degrees in CS are awarded to women. The QuantCast statistic reported in the thread is that 20% of those viewing the site are women (how it can determine this, I don’t know), with the author experientially saying “no women were in the active/leader board” for StackOverflow.
* Downvoting without reason: this is a pernicious behavior on StackOverflow that occurs to men, as well. Basically site users with a certain site-age can up or down vote answers, comments, questions, etc. The etiquette is to add a reason, if you downvote. Women perceive, that it happens more to them than men. We can only really verify it with the site statistics, or perhaps a sociological experiment of some kind? I’ve experienced downvoting, but it’s hard for me to tell if it’s from my age-level on the site (I think that has a serious impact) or my gender. Or, of course, it’s a bad answer (never!).
The key was writing the streamed URL code to a local disk space (always same file). Despite the purported support, Android 2.2.1 does not support WAV streaming. That is, you can’t play it directly from a URL.
Basically wrote our own “setDataSource” to buffer the data locally before playback. Then, another key is to wait until MediaPlayer has “prepared”, this is done on a Listener method. Only do “start()” once it’s “onPrepared”.
This demo API code was invaluable for breaking down the process flow and troubleshooting. I borrowed the setDataSource method from Davadum Srinivas – great stuff. Also, so nicely formatted and written!
I had determined WAV to be the right format to record and playback on iPhone and Android, and it all tested well, on my device which is 2.2.3. Sadly, my colleague’s (and 2 of his roommates!) devices are 2.2.1 and that broke the easy playback.
Here’s the code, if you like to scan before downloading (like I do)
Set “background” to a drawable xml file we will define, such as “pink_background”
android:background="@drawable/pink_background"
Create the pink_background xml file in the “drawable” folder in your Android project. We’ll get to its contents in a sec.
Create a “button_style.xml” in “res/values” directory. This will contain common styles to all buttons. We’ll go over this below.
In main.xml (layout), add the style reference:
style = "@style/bStyle"
The Background XML File
This file contains 3 states- default, pressed, and focused. We mainly change default and pressed. The colors are the “high” (on the top, lighter shade) and the “low”, the bottom or darker shade. The angle is the gradient degree. Note the stroke color, which is the border color, and gives the button a nice polish. Also, the rounded corners are done here. We keep it consistent with the iPhone at 7dip.
Posted by banane on June 4th, 2012 — in android dev
Ah, just spent a while figuring this out. You have a progress bar, you’d like it to start, and, you’d like it to stop. There are lots of examples of start a progress bar, but very little on how to stop, or interrupt it. That’s the key- you are interrupting a running while loop.
The idea is that in one method (which the Start button launches) you begin the thread that starts the progressbar animation. This isn’t a simple background, or asynchronous task, as you need to constantly update the user interface.
So make sure to set the thread as a property: in mine, it’s “mThreadProgress.” In the other button, you send an interrupt to that thread, “mThreadProgress.interrupt()”. The thread’s run() method has an exception catch for interrupt, so when that happens, I ‘break’ out of the while. In your “stop” method, adjust the interface to reflect a zero’ed out, or stopped progressbar by resetting the value to zero. While the thread is active (and it’s always active) the while() loop will keep checking, so make sure to add that added conditional, “!mThreadProgress.isInterrupted()”.
Posted by banane on June 1st, 2012 — in iphone dev
My app- Lyrics- is pretty simple graphically. The only real object we have is a button. So, it’s nice to get the most out of my designer, and he has repeatedly been a fan of gradient buttons with a different visual gradient when pressed (highlighted in iPhone talk). While iPhone is great for a lot of things, just right out of the box, adjusting the default button style tends to be very difficult. We also did this in Android.
Normal
Highlighted
Subclass UIButton to use Highlight method
Subclass UIButton. Do this first so the subclass is available in Interface Builder. I put some layer properties common to all buttons in the initialization of the object. This is just a snippet- here’s a full zip.
The main reason I subclassed though – instead of doing it all in the view class – was to use the “setHighlight” method. This uses the button’s nice default click animation, and you can give it instructions during a highlight. Here, I flip the two layers I’ve made. One is a darker gradient layer than the other.
Create your XIB, select the button object. Set the class to “lyrButton” (or whatever you name your subclass).
Set the button as:
Clear/transparent background
Custom
Highlight reverses
Assign tags to them, starting with 1. We will create a “btns” array that stores the kind of buttons we have- for me, “rec”,”play”,”stop”, etc.
Do this for as many buttons as you want.
Setup Buttons in View Class
I chose to setup the buttons in my main view class, because I do this as a batch, really related to the view and not individual buttons. Also, I establish a big colors array and only want to do that once. You could break this up even more and put in the subclass if you want.
Setup a button property. I use this to take XIB settings and leverage them in the code (instead of doing entire button programmatically). We get our button bounds, for example, from this default button.
In IB, link the property to the default button.
Create your color array- I used a dictionary, with keys the represent the type of button (i.e. “rec”), the dark/light type of color it is, and whether it’s a highlight or “normal” layer. This was the hardest part to keep right, and we used a Google doc with the designer to double-check the colors.
Build a “create gradient” layer method, pass into it the button type and which layer (highlight or normal).
// our colors dictionary has this composite key- button type, high/low color, and layer (highlight // or "normal") NSString*low_key =[NSString stringWithFormat:@"%@_low_%@",btnType,gType]; NSString*high_key =[NSString stringWithFormat:@"%@_high_%@",btnType, gType];
// use gradient layer’s "colors" method to let QuartzCore build the gradient [gradientLayer setColors: [NSArray arrayWithObjects: (id)[[colors objectForKey:high_key] CGColor], (id)[[colors objectForKey:low_key] CGColor], nil]];
return gradientLayer; }
Create a “setupButtons” method, to loop through the view, grab each subclassed button, and apply the gradient layers. I also grab the appropriate border color (from the colors array) for each button and apply it here. It gives it that extra polished look.
-(void)setupButtons{
// keep track of which buttons are which in our loop // to build the appropriate gradient color NSArray*btns =[[NSArray alloc] initWithObjects:@"",@"rec",@"play",@"stop",@"share",nil]; for(UIButton *bt in[[self view] subviews ]){
// tags are the index in the array of which button is which, best way to // integrate with interface builder if([bt tag] > 0){
// build each gradient and plop into button, 0 is bottom/not visible, 1 is visible // the subclassed "highlight" method flips this order
// add a border color specific to this kind of button, get it from colors array // using the key, "[type]_border" NSString*border_key=[NSString stringWithFormat:@"%@_border",[btns objectAtIndex:[bt tag]]]; [[bt layer] setBorderColor:[[colors objectForKey:border_key] CGColor]];
} [btns release]; }
}
- Update 6/5, thanks @domsware, tips on convenience methods and the !hidden in the layer switcheroo.
-(void)viewDidLoad { // do once on build [self setupColors]; [self setupButtons]; [super viewDidLoad];
}
-(void)setupButtons{
// keep track of which buttons are which in our loop // to build the appropriate gradient color NSArray*btns =[[NSArray alloc] initWithObjects:@"",@"rec",@"play",@"stop",@"share",nil]; for(UIButton *bt in[[self view] subviews ]){
// tags are the index in the array of which button is which, best way to // integrate with interface builder if([bt tag] > 0){
// build each gradient and plop into button, 0 is bottom/not visible, 1 is visible // the subclassed "highlight" method flips this order
// our colors dictionary has this composite key- button type, high/low color, and layer (highlight // or "normal") NSString*low_key =[NSString stringWithFormat:@"%@_low_%@",btnType,gType]; NSString*high_key =[NSString stringWithFormat:@"%@_high_%@",btnType, gType];
// use gradient layer’s "colors" method to let QuartzCore build the gradient [gradientLayer setColors: [NSArray arrayWithObjects: (id)[[colors objectForKey:high_key] CGColor], (id)[[colors objectForKey:low_key] CGColor], nil]];
return gradientLayer; }
-(void)setupColors{
// this is a more complicated interface and design, but very pleasing // different palate colors for each state, high and low // high is a lighter shade than low
My goal was to record little snippets on both iPhone and Android, and share those files between users, on Facebook. As usual, I want old Android users (2.2+) and iPhone (3+) users to participate in this app.
I originally recorded in iPhone CAF, then realized the file size was ginormous. So I downgraded the sample rate. That worked fine. Then, realized Android could do nothing with this. I know, lack of planing, ya di ya di. I thought briefly of setting up a server to re-encode the CAF files for Android. Turns out, on banane.com (hosted by TigerTech) the FFMPEG installation is very vanilla and doesn’t use the right libraries.
But let’s look at Android- what can it do? So, using the AMR (THREE_GPP) format, I used MediaRecorder and that worked great. Nice small files, OK quality. Android is a pretty open architecture, so you can use another library, AudioRecorder (vs. MediaRecorder) to record in almost any format you want. Problem is, there are a lot of bells and whistles built into MediaRecorder that make it a simpler choice. With AudioRecorder, if you want to record in WAV, you have to -no joking- go bit by bit through the files and build your own audio file, with headers and data. It’s a lot of code. I went down this road for an hour than realized, I wanted to stay in the happy MediaRecorder world. So now- what can I do with AMR files? Turns out, not a helluva lot. New Android operating systems handle MP3, but the iPhone doesn’t record in that, and I wanted to support older Android versions.
Long story short- I finally figured out a solution:
1. Build Amazon EC2 instance
2. Install FFMPEG with AMR libraries
3. Record iPhone with PCM_16 audio format, and give it the “wav” file extension.
4. Upload to Amazon’s S3 service.
5. Record Android with AMR, and do conversion script:
a) Kick off, asynchronously, a conversion script on my EC2 instance
b) Script converts .3gp to .wav
c) Upload to Amazon’s S3 service.
Now, file is available as *.wav for both Android and iPhone users.
Setting up the Amazon EC2 server with FFMPEG and enable it with just the libraries I need (AMR). That took about 4-5 hours. And it’s not because I did anything wrong, I was actually pretty flawless (yay!) in my Linux installation. I can’t find the awesome tutorial I used to install FFMPEG (should have written this blog post weeks ago) but this one looks good: Justin Hartman’s Install FFMPEG
I made a few tactical changes to my apps. If you’re interested in doing this kind of thing, I recommend thinking about these things beforehand.
- What phones and operating systems you want to support, historically (old phones?)
- Playback vs. recording – is recording absolutely necessary?
- Streaming vs. simple file loading and playback
- Audio quality (speech is lower than, say, music)
Finally figured this out and thought I’d blog about it.
You’re setting up a short-code listening MMS app. Keep in mind:
- The receiving (server) script should handle $_POST to the address
- Setup that script in the developer portal’s app page
- I included a lot of server logging (manually) in the script
- With the Sencha front-end, I re-wrote the image files to the client directory /assets/data/ etc. I json-encoded (was in a custom DB format in the example)
- The format of the incoming MMS data is very… customized. So I output to a log, then used as a test format. That decreases the expense of testing with real MMS messages.
- Basically, you find the filename, content-type, and base64 actual image data, and write that to your own custom datastructure.
I can post sample code, but my working versions are at: