MakeAWishFoundation / SwiftyMocky

Framework for automatic mock generation. Adds a set of handy methods, simplifying testing. One of the best and most complete solutions, including generics support and much more.
MIT License
1.03k stars 104 forks source link

Compilation Performance Issues #335

Open choule99 opened 1 year ago

choule99 commented 1 year ago

Introduction I work on a fairly large swift project, composed of multiple modules, and one of our requirements is near total coverage, and swiftymocky has been a great help in achieving this kind of coverage.

But we are starting to see a major problem

Problem The problem is that the Mock.generated.swift file has gotten really really large, and while the swift compiler compiles it without issue, the problem is that the single file is the main bottleneck of our testing suite, the file that currently is about 100 000 line of code long takes about 50 to 60 seconds to compile on the best machine we have. (considering that the build, without the tests takes about 30 seconds)

While the mock file is getting compiled, all the 20 cores of the machine are mostly idle except for the 1 core that is actually compiling the mocks. Once complete, xcode gets back on track, and successfully parallelize the rest of the build.

Suggestion Give us a way to split the Mock.generated.swift into multiple files, something like X mocks per file (where X is configured by the project), so instead of 1 giant swift file, we have X multiple smaller files that xcode can parallelize using all the available cores.

So if I had an original file with 50 mocks in it, and configure X to 10, I would have 5 Mock.generated.swift files named like:

Mock.1.generated.swift
Mock.2.generated.swift
Mock.3.generated.swift
Mock.4.generated.swift
Mock.5.generated.swift

Pros This I believe would be a major quality of life improvement, especially for large projects, with a lot of mockable protocols.

Cons A project with a lot of mocks would have to import a lot of files.

pschneider commented 1 year ago

Might not be a direct solution to your project, but you could try to split the Mocks already based on your own definitions (e.g. data layers). The Mockfile supports this already. The trick is to have different files where your extensions with the //sourcery: AutoMockable annotations are declared.

Example:

Let's say you want to split based on an app layer for your "View Models" and "Repositories". Your Mockfile could look like this:

repositories:
  sources:
    include:
      - # path to your source code
      - # path to the file where your protocol extension with //sourcery: AutoMockable are declared containing ONLY repository types
  output: ./<DESTINATION_OF_MOCKED_GENERATED/MockedRepositories.generated.swift
  targets:
  - # your test target
  testable:
  - # your testable import
  import:
  - # list of imports that are required in the mock to compile (e.g. Foundation)

viewModels:
  sources:
    include:
      - # path to your source code
      - # path to the file where your protocol extension with //sourcery: AutoMockable are declared containing ONLY view model types
  output: ./<DESTINATION_OF_MOCKED_GENERATED/MockedViewModels.generated.swift
  targets:
  - # your test target
  testable:
  - # your testable import
  import:
  - # list of imports that are required in the mock to compile (e.g. Foundation)

Result will be that MockedViewModel.generated.swift will only contain mocks related to the view models and MockedRepositories.generated.swift will contain only mocks for the repositories.

You could of course just model a Mockfile like in your example and name the declarations based on numbers and split your //sourcery: Automockable declarations equally between all of them.

Of course that requires some setup and is not automated, but I think it should already help you.

choulepoka commented 1 year ago

I actually added a post-generation command-line utility that takes the gigantic Mocks.generated.swift, and split it in a 1-mock per file fashion, and it's very effective.

As a bonus, Github no longer stalls with out-of-memories when diffing the generated code in a PR.