How do I unittest a command line application?

I am new to swift and xcode and I'm looking for instructions on how to unit test code on a command line application.


I've tried to build one myself but the compiler is giving me link errors that I can't debug. If there is some tutorial I couldn't find on google please point me to it - but most of the documentation out there is for learning swift via playgrounds - but I'd like to learn using unittesting and code katas.


Whenever I build I always get linker errors. Also no sort of code detection works like autocomplete for the classes I write.


Undefined symbols for architecture x86_64:

"Doubler.Doubler.__allocating_init () -> Doubler.Doubler", referenced from:

DoublerTests.DoublerTests.testExample () -> () in DoublerTests.o

"type metadata accessor for Doubler.Doubler", referenced from:

DoublerTests.DoublerTests.testExample () -> () in DoublerTests.o

ld: symbol(s) not found for architecture x86_64

clang: error: linker command failed with exit code 1 (use -v to see invocation)


import XCTest
@testable import Doubler

class DoublerTests: XCTestCase {

    override func setUp() {
        super.setUp()
    }

    override func tearDown() {
        super.tearDown()
    }

    func testDoubler() {
        let x = Doubler()
        XCTAssertEqual(10, x.double(3))  // this should compile but fail the test
    }



}


Doubler:

import Foundation

class Doubler {
    init (){
   
    }
    func double(n: Int) -> Int {
        return n * 2
    }
}
Post not yet marked as solved Up vote post of lucidguppy Down vote post of lucidguppy
9.2k views

Replies

What version of Xcode are you using?

I tried this out on Xcode 7.3.1 and didn’t have problems. Specifically:

  1. I created a project from the command line tool template.

  2. I created a “Doubler” target from the framework template.

  3. I created a “DoublerTests” target from the unit tests template.

  4. I put your Doubler class into the “Doubler” target.

  5. I put your DoublerTests class into the “DoublerTests” target.

  6. I selected the “DoublerTests” scheme and chose Product > Test.

  7. Things built and ran and the

    testDoubler()
    test failed as expected.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Could you explain why it's not possible to define unit tests for the original target? It works if you define a second (framework) target, but XCode won't let you define unit tests for the target that gets created by default for the tool.

I’m not sure what you mean. The following works for me:

  1. Create a new project using the macOS > Command Line Tool template.

  2. Choose File > New > Target.

  3. Select the macOS Unit Testing Bundle template.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Yes, you can add the unit testing bundle, but it won't let you set Target to be Tested. So, you won't be able to call into your main target (as far as I can tell). I'm not sure what that bundle is good for without a test target.


But if you add (as in your first example), a second target and select Framework. Then, you'll be able to add the framework under Target to be Testedand you'll be able to call into it.


I'm new to testing inside of XCode, so maybe I'm just doing something stupid. But I struggled for a while because my unit tests were throwing linker errors because the test target wasn't set.

Yes, you can add the unit testing bundle, but it won't let you set Target to be Tested.

Right. That’s because the machinery to load your test bundle within a process only works if the process is based on a GUI framework (like Cocoa or Cocoa Touch). A command line tool typically does not use a GUI framework and thus the test machinery is unable to load your bundle into that tool.

However, you can test without an app target. If you set the Target to be Tested popup to None, Xcode will load your test bundle into its built-in ‘test runner’ tool,

xctest
.

This works well for most but there are some gotchas. Specifically, if the code you’re using requires entitlements then you won’t be able to test it like this because there’s no way to give those entitlements to

xctest
. The standard workaround for that is to add a dummy test target app to your project.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thanks for this explanation, but I am still a little confused. When I left the Target to be Tested to None, I just got linker errors. That's how I wound up here on the forums 🙂


But when I added a framework and moved my code there, I was able to set the target and test against it.

When I left the Target to be Tested to None, I just got linker errors.

Indeed. How you proceed from there depends on the final destination of your code:

  • If you intend to eventually put the code in a framework, then creating a framework target is the right answer.

  • If the code is destined for your command line tool proper, it’s reasonable to link it directly into your test bundle. That is:

    1. Select the source file in the Project navigator on the left.

    2. Show the File inspector on the right.

    3. In the Target Membership slice, you should see that your file is included in your command line tool target. Check the box to also include it in your test bundle target.

I work on a lot of command line tools and I end up using this technique a lot. There are some drawbacks (like the entitlement issue I noted earlier) but in most cases it’s a quick and easy path to a solution.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
  • Create a new Unit Test Bundle target, File -> New Target ... -> Unit Test Bundle
  • Add the target as a test target to your Command Line Tool scheme, Product -> Scheme -> Edit Scheme ...
  • Select the Test action in the left hand list
  • Add New Test Target by clicking at the + at the bottom left area of the dialog window

Now you can Product -> Test your app.

  1. Add File -> New -> File -> Unit Test Case Class
  2. Add source files to both targets: project and tests