portrait picture

TIMO ZIMMERMANN

balancing software engineering & infosec

Unit testing command line applications in Swift

posted on Wednesday 13th of February 2019 in

A joke I hear since the release of Swift 2 is “Swift is production ready, except when it is not” and it feels like I ran into one of the parts where this might be true. I decided to write a small command line application last weekend and since I wanted to see how it feels like working with Swift outside of iOS I took the opportunity and fired up Xcode. Oh, if I would have only know what a wild ride and confusing internet search session I had ahead of me.

The most important reason for me to keep up with Swift outside of the iOS platform is the hope to use it for server side development one day. While it feels like Swift is getting more complex with each release and might suffer from feature creep one day, I still like using it and it feels like an elegant and powerful tool in the toolbox, most of the time.

Starting the application is pretty straight forward. Open Xcode, select command line applications and press “Run”. Easy enough. The first thing that might catch your eye is the fact that no test target was created. But this requires only a few clicks, so that is quick to fix, even if it is a bit inconvenient. As usual when setting up tests I imported the project and asserted a random structs static value. Just a sanity check to make sure everything is wired up properly.

Undefined symbols for architecture x86_64:
  "Token.eof.unsafeMutableAddressor : Swift.String", referenced from:
      implicit closure #1 : @autoclosure () throws -> Swift.Bool in XRI.XRITests.random() -> () in TokenTests.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

So, um,… okay? I have to admit I have not seen this one before. Or I can at least not remember it. There are various solutions you get when searching for this error thrown by the linker, but most of them are not as helpful as you would hope.

I spare you the journey of discovering what the actual error is, to make it short: You cannot test a Swift target compiling to an executable. Technically getting rid of main.swift does the trick, but this might be a bit inconvenient for programs that are, well, build to be executed.

The workaround I am currently using is creating an ordinary library and adding two targets, one for testing and one for running the code. You can take the easy way out and simply add all source files to your target to run the code or you create a proper library like someone who actually cares about abstraction and design.

I clearly have some catching up to do with macOS development, I think the last time I worked on a serious project on this platform was around the transition to OSX. I really hope my solution is not the correct one, even if you find it in various places when searching the web, but simply me not being up to date on how to properly start a project like this. Otherwise I would really have to question the Swift advocates who praise Swift for server side or tooling development, because this is clearly not the way to go.