Test projects without pain. Yandex Report

We in the Yandex.Maps team for iOS create test projects using a small plugin for CocoaPods and several utility classes. Creating a project is fast and reliable. But maybe we are too bothering and assembling a project manually with the necessary settings and dependencies is not so difficult? In the report, I went from the opposite: first I looked at the manual process, then ours.


- First, a little background. Yandex.Maps are collected for more than a minute. On my computer, building the application takes a little over three minutes. We develop in test projects in order to spend less time on each assembly. We have enough modularity, and for each module we are doing a test project. In this test project, features are being developed.

Creating one test project now takes from 15 minutes to an hour. This is due to the fact that the project must be configured. That is, you need to configure signing so that we can develop on the same devices as we are used to. It is necessary to configure the necessary libraries. In the process of developing the feature itself, we constantly come back and finish the test project so that it satisfies our needs.

Why do we need test projects? To save time on assembly. The smaller the project, the faster it is assembled, the more often we will be able to launch our application on the device and the more comfortable it will be for us to develop.



What should be in the test project? We should be able to launch, debug our application on the same devices on which we are used to launch and debug our main application.

The project should be configured as similar as possible to the main project, so that in the process of transferring features from the test project to the main one, we will not get a surprise. Or at least reduced the number of surprises received to a minimum.

We also need a minimal environment in the test project, so that we do not waste time creating UIWindows, setting up libraries, overturning the call to open the application in the library that deals with authorization, etc.

CocoaPods app_spec


My whole story today will be built around CocoaPods feature called app_spec.



App_spec is like a regular subspec, only it does not generate a framework that can be connected, but an application that can be launched on the device.



And out of the box, CocoaPods allows us to shove Info.plist and xcconfig into app_spec. This already allows us to set up our project quite well.

But if we are going to write huge dictionaries in each pods_spec, with values ​​for Info.plist and xcconfig, then we will grow podspec ourselves and it will be inconvenient to modify them. If we need to change something, we have to go into each podspec file and edit something with pens. In any case, this is inconvenient.



To get rid of this, we made a very simple local plugin for CocoaPods. It even fits entirely in one slide. And he does the one and only thing: gives us DSL resolution CocoaPods, which creates app_spec with already clogged values ​​in Info.plist and xcconfig. This already allows us to configure signing, configure those important things that we want to see configured in our test project.



Question: how to transfer the path to entitlements, more precisely, where to store entitlements so that we can hardcode the path into plug-ins.

There are two ways. Or indicate the path for a file that simply lies in our repository, for example, to entitlements of the main project. Or store in the plugin a special entitlements file for test projects. And using the same plugin, copy it to our developmentpod.



We chose the first path, and here we had some difficulties. The fact is that in our repository developmentpods are in different subfolders. Therefore, we cannot indicate one relative path for them from the developmentpod to the entitlements file.

It is important to emphasize here that you must specify a relative path, because this path remains in pods_spec. This means that he will participate in the calculation of the check amount, which is stored in Podfile.lock. And if there is an absolute path, then this will affect the check amount. This means that the check amount will vary depending on which folder to call $ pod install from, where you have the repository, which machine you run $ pod install on. In general, this leads to conflict.



Now I will show another small extension of the same plugin, which allows us to calculate the relative path to these entitlements.

We know the relative path from our plugin to the file with entitlements, if they are in the same repository. And also during the launch of $ pod install, we know the path to our developmentpod. All the magic that is in this code is a highlighted line where we simply count the relative path, knowing these two absolute paths.



So, what did we get when we already had app_spec configured in this way? We can run our test project on the same devices right away. We got a huge amount of settings, including entitlements, which we need.

Environment


The next thing I want to talk about is the environment, the very minimum necessary environment that we need to start writing code right away.



In our case, we need MapKit, already configured, with the keys transferred, with call forwarding in the AccountManager. This is our authorization library. She also needs to throw system calls, she also needs to transfer keys, including to pass the opening of the application by reference.

We also need a start screen with a map and navigation stack, so that we can use the same navigation stack as in Maps, and it is easier to transfer features from the test project to the main one. And the entry point so that we, without thinking, could immediately start writing useful code.



In your case, this could be, for example, setting up the Facebook SDK, to which you also need to transfer keys; your libraries for login, which you will almost certainly need to skip the application opening link. Some kind of start-up Windows start-up Controller, and the same entry point to run the main script that you want to develop in your test project.



How did we do this? By making a single AppDelegate template. There we initialize all the necessary environments, libraries. We forward all the necessary calls, transfer the necessary keys. Creating a test project, it remains for us to create a new AppDelegate for the test project and inherit from the template.

After that, we override the one and only method that is called after the template AppDelegate has done everything necessary. And there we already have the starting ViewController and on top of it we can run our script, which we want to debug in the test project.

Was / became


How can I create a test project in the forehead, and how can I create it using the method that I described today?

Creating a test project in the forehead:

- We go to Xcode, create a new project (File> New Project).

- Enter the name, preferably without errors, sign the Bundle ID. We are setting up this project.

- Change the version of Swift, change the target version. We are trying to run it all on the device, edit signing, and so on. This is a rather dreary process.

- Next, we create a podfile, connect dependencies there, install pods.

- After that, we go and begin to very tediously and gently configure our AppDelegate, transferring through the pieces of initialization of the libraries that we need.

- Create a start UIWindows with our ViewController

And only after that we can start the useful code. Fairly unpleasant procedure, agree.

How can we create a test project using the best practices that I described in today's report?

- We create sample_app_spec, using the extension, for CocoaPods, with which we started.

- Create an AppDelegate, and inherit it from the template AppDelegate.

After that, we can start writing useful code.

Creating a test project in this way is much faster and more reliable. We will definitely not make a mistake in signing settings, we will definitely not make a mistake in project settings. All the environment necessary for starting work immediately approaches us.

Can it be easier?


You can ask a question - can this be done easier? Of course.



I have identified three difficulty levels. The first is what I talked about today, all at once, when we make a pumped-in plugin for CocoaPods, which can repair paths to entitlements, and a template AppDelegate.

The second method is ideal in the ratio of efforts to profit. It contains only the plugin for CocoaPods, which was the first in today's story. That is, it simply substitutes the values ​​in Info.plist and xcconfig. It already makes life a lot easier.

If you do not need your entitlements file in your test applications, if your test applications are quite different from each other and do not need common components, this method is very suitable for you.

The third method is the easiest, does not require any preparation. Just start using app_spec. The project that is generated from app_spec is created in the same Workspace as your main project. Therefore, it already inherits, for example, Swift, target version and all the settings that you have in Workspace. It will be much easier for you to start working with him. That's all, thank you for your attention. Today I will tell you how to create test projects without pain, and how we create test projects in Yandex.Maps.

- First, a little background. Yandex.Maps are collected for more than a minute. On my computer, building the application takes a little over three minutes. We develop in test projects in order to spend less time on each assembly. We have enough modularity, and for each module we are doing a test project. In this test project, features are being developed.

Creating one test project now takes from 15 minutes to an hour. This is due to the fact that the project must be configured. That is, you need to configure signing so that we can develop on the same devices as we are used to. It is necessary to configure the necessary libraries. In the process of developing the feature itself, we constantly come back and finish the test project so that it satisfies our needs.

Why do we need test projects? To save time on assembly. The smaller the project, the faster it is assembled, the more often we will be able to launch our application on the device and the more comfortable it will be for us to develop.



What should be in the test project? We should be able to launch, debug our application on the same devices on which we are used to launch and debug our main application.

The project should be configured as similar as possible to the main project, so that in the process of transferring features from the test project to the main one, we will not get a surprise. Or at least reduced the number of surprises received to a minimum.

We also need a minimal environment in the test project, so that we do not waste time creating UIWindows, setting up libraries, overturning the call to open the application in the library that deals with authorization, etc.

CocoaPods app_spec


My whole story today will be built around CocoaPods feature called app_spec.



App_spec is like a regular subspec, only it does not generate a framework that can be connected, but an application that can be launched on the device.



And out of the box, CocoaPods allows us to shove Info.plist and xcconfig into app_spec. This already allows us to set up our project quite well.

But if we are going to write huge dictionaries in each pods_spec, with values ​​for Info.plist and xcconfig, then we will grow podspec ourselves and it will be inconvenient to modify them. If we need to change something, we have to go into each podspec file and edit something with pens. Or not pens. In any case, this is inconvenient.



To get rid of this, we made a very simple local plugin for CocoaPods. It even fits entirely in one slide. And he does the one and only thing: gives us DSL resolution CocoaPods, which creates app_spec with already clogged values ​​in Info.plist and xcconfig. This already allows us to configure signing, configure those important things that we want to see configured in our test project.



Question: how to transfer the path to entitlements, more precisely, where to store entitlements so that we can hardcode the path into plug-ins.

There are two ways. Or indicate the path for a file that simply lies in our repository, for example, to entitlements of the main project. Or store in the plugin a special entitlements file for test projects. And using the same plugin, copy it to our developmentpod.



We chose the first path, and here we had some difficulties. The fact is that in our repository developmentpods are in different subfolders. Therefore, we cannot indicate one relative path for them from the developmentpod to the entitlements file.

It is important to emphasize here that you must specify a relative path, because this path remains in pods_spec. This means that he will participate in the calculation of the check amount, which is stored in Podfile.lock. And if there is an absolute path, then this will affect the check amount. This means that the check amount will vary depending on which folder to call $ pod install from, where you have the repository, which machine you run $ pod install on. In general, this leads to conflict.



Now I will show another small extension of the same plugin, which allows us to calculate the relative path to these entitlements.

We know the relative path from our plugin to the file with entitlements, if they are in the same repository. And also during the launch of $ pod install, we know the path to our developmentpod. All the magic that is in this code is a highlighted line where we simply count the relative path, knowing these two absolute paths.



So, what did we get when we already had app_spec configured in this way? We can run our test project on the same devices right away. We got a huge amount of settings, including entitlements, which we need.

Environment


The next thing I want to talk about is the environment, the very minimum necessary environment that we need to start writing code right away.



In our case, we need MapKit, already configured, with the keys transferred, with call forwarding in the AccountManager. This is our authorization library. She also needs to throw system calls, she also needs to transfer keys, including to pass the opening of the application by reference.

We also need a start screen with a map and navigation stack, so that we can use the same navigation stack as in Maps, and it is easier to transfer features from the test project to the main one. And the entry point so that we, without thinking, could immediately start writing useful code.



In your case, this could be, for example, setting up the Facebook SDK, to which you also need to transfer keys; your libraries for login, which you will almost certainly need to skip the application opening link. Some kind of start-up Windows start-up Controller, and the same entry point to run the main script that you want to develop in your test project.



How did we do this? By making a single AppDelegate template. There we initialize all the necessary environments, libraries. We forward all the necessary calls, transfer the necessary keys. Creating a test project, it remains for us to create a new AppDelegate for the test project and inherit from the template.

After that, we override the one and only method that is called after the template AppDelegate has done everything necessary. And there we already have the starting ViewController and on top of it we can run our script, which we want to debug in the test project.

Was / became


How can I create a test project in the forehead, and how can I create it using the method that I described today?

Creating a test project in the forehead:

- We go to Xcode, create a new project (File> New Project).

- Enter the name, preferably without errors, sign the Bundle ID. We are setting up this project.

- Change the version of Swift, change the target version. We are trying to run it all on the device, edit signing, and so on. This is a rather dreary process.

- Next, we create a podfile, connect dependencies there, install pods.

- After that, we go and begin to very tediously and gently configure our AppDelegate, transferring through the pieces of initialization of the libraries that we need.

- Create a start UIWindows with our ViewController

And only after that we can start the useful code. Fairly unpleasant procedure, agree.

How can we create a test project using the best practices that I described in today's report?

- We create sample_app_spec, using the extension, for CocoaPods, with which we started.

- Create an AppDelegate, and inherit it from the template AppDelegate.

After that, we can start writing useful code.

Creating a test project in this way is much faster and more reliable. We will definitely not make a mistake in signing settings, we will definitely not make a mistake in project settings. All the environment necessary for starting work immediately approaches us.

Can it be easier?


You can ask a question - can this be done easier? Of course.



I have identified three difficulty levels. The first is what I talked about today, all at once, when we make a pumped-in plugin for CocoaPods, which can repair paths to entitlements, and a template AppDelegate.

The second method is ideal in the ratio of efforts to profit. It contains only the plugin for CocoaPods, which was the first in today's story. That is, it simply substitutes the values ​​in Info.plist and xcconfig. It already makes life a lot easier.

If you do not need your entitlements file in your test applications, if your test applications are quite different from each other and do not need common components, this method is very suitable for you.

The third method is the easiest, does not require any preparation. Just start using app_spec. The project that is generated from app_spec is created in the same Workspace as your main project. Therefore, it already inherits the Swift version, target version and all the settings that you have in Workspace. It will be much easier for you to start working with him.
Thank you for the attention.

All Articles