Import tests from one Unit test target into another - ios

We started to split our swift iOS app into multiple frameworks to speed up our TDD feedback cycle. Each of these framework is a project with it's own unit test target. All of these are part of a larger workspace.
The issue is we can't run multiple test targets with our current CI setup. I was wondering if it's possible to bundle together all of our test targets into one for CI but keep them separated for development (maybe by importing/running tests from all the different targets into a "CI unit test target")?

You could certainly add another target and add REFERENCES to the files for the unit tests. They may more may not need modification of the module import names. A file may be referenced in multiple projects, and this could even be automated, as there are tools such as PBXProj for python to allow reading/writing of the Xcode project.
Alternatively, but I am not sure, it may be possible to create an aggregate target with the other items added - this is just speculation.
Perhaps a better question is why can't multiple test targets be run? Surely a script or fast lane could help?

Related

Xcode: merge Unit tests into a single target

I'm developing a modular app with >30 modules, where each module has its own Unit test target. I also have a special test scheme which runs all Unit tests from all the targets.
The problem is - it's too slow. While the tests run fast, it takes a lot of time for Xcode to switch from one test target to another. The "all tests" scheme is supposed to be run in CI so I would really like to improve the performance.
I tried to convert all Unit test targets into static frameworks and link them to a new "merged" Unit test target. In this target I then created a single AllTests.swift file importing the frameworks and manually invoked tests on each of them. The performance improvement was ~4x.
However, this will be quite hard to maintain this file. My assumption is, since they are linked statically, there can be a way to make Xcode run imported in such a way tests automatically. If this works I'll be able to fully automate this process, keeping test targets for development and generating the "merged" target in CI.
What else I tried:
Investigated the option of using SourceKitten to parse the project and generate the AllTests.swift file automatically. This would require building the project one more time, eliminating the performance improvement.
Instead of linking frameworks, include references to source files into the merged target. This introduces a bunch of "multiple files with the same name" and "same declaration" errors that cannot be automatically resolved.
Made all declarations in the static frameworks public.
Googled a lot. This SO question is quite close but the solution doesn't work for me.
So the main question is - is there a way to make Xcode automatically include Unit tests that are part of a static framework the current target is linked against? Or any other suggestion on how I can automate this process will be very much appreciated.

Should I use a dedicated bazel BUILD file for tests?

Is there any bazel convention for defining all of a project's test targets in some subfolder tests with its own BUILD file. Is that layout preferred to one main BUILD file that combines everything in one place: bins, libs, and tests?
An all in one build file could get lengthy, but it has the benefit of using in-package sibling target references, ie the ":siblingTarget" references.
Short answer: Yes.
Why would you like to couple your test with the implementation? You should always try to reduce dependencies between different parts of your code. That makes it easier to understand. See also here.
Imaging you find some BUILD file that contains implementation and test rules (e.g. cc_library, cc_binary, cc_test). Imagine some developer needed for some test some library - and only for testing proposes (e. g. cc_library(name="some_lib"). How would you know if this library is only needed for testing? If it is in a BUILD file contained in test folder then it would be clear from the folder structure that there is a high probability that this lib is only needed for testing proposes.

iOS Xcode SPM failed to demangle superclass

My app is composed of many projects (frameworks), one for each main feature and a common framework with all sorts of things that I need to access in multiple of my features.
I'm using Xcode 11's Swift Package Manager to add dependencies.
The common framework contains a RxSwift dependency, which I use throughout the whole project.
I'm facing problems when I try to use RxTest in any of my feature frameworks.
If I add RxTest via SPM to the test target directly and run the tests, I get
failed to demangle superclass of 'class name' from mangled name 'other class name'
and many
Class 'class name' is implemented in both 'common framework path' and 'test target path'
where all these classes are Rx related. The 'failed to demangle' error crashes the test and only occurs when I try to initialize a RxTest class.
If I add RxTest to the common framework, the tests run fine, but when I run the app, I get
dyld: Library not loaded: #rpath/XCTest.framework/XCTest
Which makes sense, because I'm adding a test framework to a non-test framework, and it's not something good to do.
So basically, I wasn't able to get a configuration where both the tests and the app run fine. Either the app runs or the tests run.
How can I get this working? Is there a way to include RxTest on the common framework only when I build it on a test target? Or should RxTest only be included on the test targets and I'm missing some configuration?
Xcode with SPM dependencies cannot handle same SPM dependency in multiple targets that are dependent on each other right now. Each dependency needs to be only in single target at the moment. I dont know why as of now, but I'll try investigate more and file bug if it is not filed yet.
Your issue is likely that the library is using static linking instead of dynamic linking. In SwiftPM you can specify a library as being static or dynamic if you want or you can just let the build system decide which is what most packages do. Xcode seems to favor the static approach when it builds with SwiftPM which results in the build issues you are experiencing.
If you modify the Package.swift to have RxTest be a dynamic library instead it should work. You can easily test this by cloning RxSwift and modifying this line:
.library(name: "RxTest", targets: ["RxTest"]),
into:
.library(name: "RxTest", type: .dynamic, targets: ["RxTest"]),
and then dragging the local copy of RxSwift into your Xcode Project Navigator.
It will then use your local copy of the package instead of the one cloned by Xcode.
Once you do this you can link it against any targets you need and it should work. If that does actually fix the problem then your long term solutions are likely:
1) Have a fork that simply changes it to a dynamic library.
2) Convince the RxSwift community to change their products to dynamic or to vend dynamic versions in addition to the default.
3) Don't use RxTest or similar things in multiple places.
It is also worth noting, that Xcode 11.3 and earlier do not support archiving with dynamic Swift Packages. So if you go down the dynamic route you will have to wait for Xcode 11.4.
Workaround:
I had the same issues. My project configuration is:
Workspace
ProjectAppOne
AppTargetOne (Embed and sign FrameworkTarget)
ProjectAppTwo
AppTargetTwo (Embed and sign FrameworkTarget)
ProjectCoreFramework
FrameworkTarget
testsTareget
SPM Dependencies (including RxSwift)
My workaround was:
Duplicate the FrameworkTarget and create a FrameworkTargetT (make sure that are both building with no issues).
FrameworkTarget is still used to build the applications targets, so be sure that is the only one used in the imports of the application targets.
Add RxTest to FrameworkTargetT
Remove RxTest from FrameworkTarget
Set in your tests #testable import FrameworkTagetT
Adjust the FrameworkTarget to not run the testsTarget
Set the FrameworkTargetT to run the testsTarget
Adjust your CI fastlane/scripts, that involve testing target and call the FrameworkTarget directly
At the end the project structure was looking like:
Workspace
ProjectAppOne
AppTargetOne (Embed and sign FrameworkTarget)
ProjectAppTwo
AppTargetTwo (Embed and sign FrameworkTarget)
ProjectCoreFramework
FrameworkTarget
FrameworkTargetT
testsTareget
SPM Dependencies (including RxSwift)
I do not think is the ideal solution, it is easy to import the wrong framework in code or test, so double check that you are happy with that. But while we are waiting for the SPM team to sort the problem, it could be used and removed easily. This is also easy if you have already a framework separated code.
PS: you need to remember to add every new file to both of the targets FrameworkTarget and FrameworkTargetT.

Hundreds of targets, same code base in xcode configuration

We are at 400+ targets in xcode. It still works fine but there has to be a better way to set this up by keeping the same code base but not having all those targets which could slow down xcode.
Android Studio lets you update the appname, which loads that folder from disk so only that project is loaded to run and program against. In XCode that is not the case, all targets are available.
It's been years but is there a better way now, with hundreds of targets that doesnt involve Git or Branching? The questions in regards to this are old and only for a few projects, we are talking hundreds here.
Your question lacks enough context to make a specific recommendation but in general...
Use Frameworks
If you can, combine sensible things into a single (or multiple) framework target. Frameworks can be more than fancy wrappers around a dynamic library, they can contain helper tools and such as well.
Use Workspaces
If there is a logical grouping to your existing targets you can separate them out into their own Xcode projects. Once you have them in their own projects you can create a workspace that references those individual projects. Even if the combined workspace loads in everything upfront (I don't think it does tho) you can still open and use the separate projects for a fast and fluid experience when working on the components.
Use static libraries
If you have a ton of targets such that one requires A, B, and C, but another needs B, C, D then you can actually put A, B, C, and D together in a static library and rely on the linker to strip out unused code from each individual target. This obviously does not reduce the number of targets you have, but you can make the static library its own project and include it in a common workspace. This will also speed up compilation as the files only need be compiled once.
Parameterize Targets or Use Schemes
If your targets are simply wrapping some external build tool/script with hardcoded parameters (I've actually seen this) you can actually pass a ton of existing variables from xcode to these external tools and eliminate "duplicate" targets. Similarly you can add new schemes if some of your targets are simply permutations of each other. A good example I've seen of this are people that have a separate target for "sanitized" (address sanitizer, etc) builds you can instead create a sanitization scheme instead of a target.
Use "Script" Build Phases
If some of your targets are doing something such as linting then you can instead employ a script build phase to call the linter instead of having a separate target to do it.
Offload Targets to an External build System
Xcode can have targets that simply call out to an external tool/script using the Script build phase (and using variable parameters as mentioned above). This can make sense to do if you already use another build system (make, cmake, etc) for another platform. Use Xcode only for the Mac/iOS specific targets and offload everything else to a cross platform build system.
If the build system outputs errors in a format Xcode understands it will even show file and line errors the same as native Xcode targets. I've seen people write thin wrappers around external tools to catch parse and reprint errors into such a format.

What targets should I be adding my files to in my xcode project

I'm working on an xcode app using swift and xcode 8.1.
I keep adding files such as pictures and .plist files to my main folder with my storyboard, but each time it asks what targets I want to add it to. Should I be adding it to my test targets as well? If so, why? Whats the rule to know what targets to add it to.
No, you shouldn't add your files to the test target. Unit test target has access to your application files anyway.
According to the Apple documentation, target is a single build artifact. Because of that, you should only add the files which are building blocks of a specific target.
A target specifies a product to build and contains the instructions for building the product from a set of files in a project or workspace. A target defines a single product; it organizes the inputs into the build system—the source files and instructions for processing those source files—required to build that product. Projects can contain one or more targets, each of which produces one product.
For the most basic scenario with one application target and two test targets the general rule is as follows
Add application classes to the application target.
Add unit test case classes to the unit tests target.
Add UI test classes to the ui tests target.
In more complex scenarios you can have more targets in your application. You can have iMessage extension, share extension etc. You can also have multiple targets for building variants of the same applications but the general rule stays the same.

Resources