0

I am in the process of learning Swift 5 and what better way to write some exercises. But this is becoming a bit of a headache for me. This is what I am trying to do.

I want to write a simple CLI application, but I also want to include unit tests. Let´s say the app is called simple_app

So I went ahead, opened XCode v12.4 and click in New Project. I chose Console Application for MacOS using Swift language. That gives me a folder called simple_app that contains an .xcodeproj and another folder again with the simple_appname. Inside that second folder is the actual source code.

It seems Swift project use the convention to look for a main.swift file as the entry point. So far so good. Now let's say I have something as simple as this:

func add(_ numA:Int, _ numB:Int) -> Int {
    return numA + numB
}

func main() {
    print(add(4, 5))
}

main()

This works as expected. So now, I want to add unit tests. After some reading it seems I need to add a new target.I went ahead and used Xcode to create a new target which I called simple_app_tests. This created some nice templates. But when I try to import the simple_app mmm target? package? (not sure what that is now) it won't compile anymore.

Some further reading ahead I found out that I cannot link a main.swift file to the test target. That kind of makes sense. It would have 2 entry points: the one for testing, and my main file. So more reading. And this is where I kind of got stuck. It seems I need to split my application into a library/package which is independent of the main executable AND then another artifact that contains the main.swift file. Adding a Package through Xcode create another target with its own tests. So I end up with 3 artifacts(not sure what they are actually): simple_app, simple_app_tests and simple_app_package which also contains more targets.... And here is where things are getting too complicated for such a simple app.

Most tutorials/books and stackoverflow questions are more related to IOS development and those solutions for some reason haven't work for my case or are not clear to me. I keep getting linking errors or I end up with a bloated project for such a simple app.

So my question is. Is there a way to structure simple_app so I am able to write both tests and functionality without over-complicating the structure of y project?

Note: I know that sometime it is ok to split your code into libraries and executable (Rust needs that kind of split) to be able to compile and run tests. Just need some guidance on how it is correctly done on swift. Thanks.

dospro
  • 450
  • 1
  • 5
  • 17
  • I use swift package manager to do this. I use `swift package init --type executable` which creates an executable target and a test target, then I manually create an additional library target and optionally a test target for the library. The way I think about it, the first test target just runs the compiled executable -- no referencing any code that I wrote. The test target that I add for the library gets access to my code so I can test small functions and methods on their own. If you want to write tests for a CLI app, you will need an executable target, a library target, and at least one test. – deaton.dg May 20 '21 at 03:27
  • 2
    See also [swift-argument-parser](https://github.com/apple/swift-argument-parser). I tend to use this to write my executable target. The executable target is maybe only one or two files, and the rest of the functionality is implemented in a library. – deaton.dg May 20 '21 at 03:29

1 Answers1

1

You cannot unit test a command line tool main file, because it is not a testable target.

But you can test a framework. So make a framework target, make an accompanying unit test target, and put your testable code into the framework.

However, a command line tool can't include a framework; it is just source files. So the source files of the framework need to be included with the command line tool target too. You do not embed or link the framework into the command line tool; that can't work.

So the command line tool sees the framework not as a framework but just as part of the same "module" as itself. Meanwhile, the test target sees the framework as what it is testing. And all is well.

I should mention that this is also a good way to structure the unit testing of a GUI iOS app, because the tests do not require the app to be loaded and run in the Simulator.

See https://developer.apple.com/forums/thread/52211 for further discussion.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • I've created a little GitHub repo example that you can download and try out and study. https://github.com/mattneub/TestingCommandLineTool – matt May 20 '21 at 05:24
  • Nice example thanks. One question reminds. Why a framework and not a package? – dospro May 20 '21 at 22:43
  • No real reason, package solution could be fine too! Packages didn't exist when I started using this technique. :) – matt May 21 '21 at 00:22
  • If you only want to test a specific file than you can also link that file in the compiled sources to test them. – Silve2611 Mar 09 '22 at 10:45