📜 ⬆️ ⬇️

Testing iOS Applications

In this article I want to talk about testing iOS applications and a small automation of this process.
Under the cat will be considered tools for modular and functional testing and give simple examples.

Test application


As an example, I decided to write a simple calculator. This application has no practical use and has very poor functionality, but in my opinion it is quite enough to give a start to writing tests.

For convenience, I laid out the finished application on the githab.

Unit Tests


For writing unit tests I use a wonderful tool - Cedar .
It allows you to write tests in the style of RSpec , which improves the structure and readability of the code.
')
On the githaba, a fairly complete description of how to build the framework was given, but when the build issue came up on several machines, a simple script was written on bash that performed this whole routine.

To install, you need to clone the project and run the install.sh script, Cedar will be installed in / opt / cedar and symbolic links will be added to the user's home directory (for more convenience when connecting to projects).

After Cedar is assembled, you need to set up a test target.
  1. Add to your project a new target (Empty Application), I called it UnitTests.
  2. Link Cedar to Target (Link Binary With Libraries)
  3. Add to Other Linker Flags -ObjC -all_load -lstdc ++
  4. Remove AppDelegate. This can not be done, but we do not need it.
  5. Edit main.m as shown below.

int main(int argc, char *argv[]) { @autoreleasepool{ return UIApplicationMain(argc, argv, nil, @"CedarApplicationDelegate"); } } 

Let's write the first test.
Create a FooBar.mm file in the UnitTests target with the following content:

 #import <Cedar-iOS/SpecHelper.h> using namespace Cedar::Matchers; SPEC_BEGIN(FooSpec) beforeEach(^{ NSLog(@"before each"); }); afterEach(^{ NSLog(@"after each"); }); describe(@"Foo", ^{ it(@"YES should be YES", ^{ YES should equal(YES); }); }); SPEC_END 


Now you can launch a target for tests, if everything is set up correctly, a simulator will start with a table, in the cells of which your tests will be described.

Let's return to the calculator. Suppose we have some singleton class that will do the calculations, let's call it CalculationManager. It should have a method that should return the instance of this class, let's call it sharedInstance.
Let's write a test for this case.
Create an empty CalculationManager class in your main target and add another test file (for example, CalculationManager.mm) with the following contents:

 // CalculationManagerSpecs.mm #import <Cedar-iOS/SpecHelper.h> using namespace Cedar::Matchers; #import "CalculationManager.h" SPEC_BEGIN(CalculationManagerSpecs) describe(@"CalculationManager", ^{ it(@"sharedInstance should not return nil", ^{ id instance = [CalculationManager sharedInstance]; instance should_not be_nil; }); }); SPEC_END 


At start it is visible that the test falls.
Add the implementation so that the test passes successfully and continue.

Add a couple of tests for addition and subtraction operations.

 describe(@"calculations should be correct", ^{ CalculationManager *manager = [CalculationManager sharedInstance]; it(@"addition should be correct", ^{ NSInteger left = 5; NSInteger right = 37; NSInteger etalonResult = 42; NSInteger realResult = [manager add:left with:right]; realResult should equal(etalonResult); }); it(@"subtract should be correct", ^{ NSInteger left = 14; NSInteger right = 12; NSInteger etalonResult = 2; NSInteger realResult = [manager subtract:right from:left]; realResult should equal(etalonResult); }); }); 


That's all, we will assume that the application is sufficiently covered by unit tests, and proceed to interface testing.

Interface testing


There are a lot of tools for testing iOS applications, but I want to talk about those that I use myself, namely Calabash-iOS and Frank .

These tools are very similar, they both allow you to write tests on Cucumber and both are implemented in Ruby, the only difference is in functionality.
In one of the projects I had to migrate from Frank, I just ran tests using Calabash and they all went almost immediately, I only had to change a few steps a little.

Now I stopped at Calabash. I think that many iOS developers are not familiar with Cucumber, therefore I want to tell a little how it works and how to write tests.

Cucumber

In no case do I pretend to be true to this description, I’ll just describe how I understand his work, and I hope this description will bring some clarity and help those who still have not decided to use it.

So, in Cucumber there are several main “entities”:

A feature is a set of several logic-related scripts (or not related, as the programmer decides). It consists of a title and a brief, informative description. For example:

 Feature: Manage Orders As a User I should be able to manage Orders through iOS application 

Scenario is a specific scenario describing some use case. It consists of a name and a set of steps.

 Scenario: Create Order #steps 

Step - description of a specific user action (click on a button / link, enter text, svayp itp).

 When I fill in "Title" with "FuuBar" And I touch "Save" button Then I should see alert view titled "Saved successully" 

Step definition - implementation of a specific user action. It looks like this:

 When /^I touch "([^"]*)" button$/ do |button_text| touch("button marked:#{button_text}") end 


When running tests, Cucumber takes one step and searches for the desired implementation using a regular expression, runs this implementation and takes the next step. Not sure that this is completely true, but I hope the essence is clear.

Let's add Calabash to our project.
Go to the directory with the project and run the following commands:

 [sudo] gem install calabash-cucumber calabash-ios setup calabash-ios gen 

Calabash added another target to our project, by default it has a project_name-cal template. We need to do a build for this purpose.
Now we are almost ready to run the tests.
After generation, a hint is displayed. How to run tests.

 DEVICE=iphone OS=ios5 cucumber 


but on the execution of this command, everything falls, because calabash does not know where our application is. To do this, you need to specify another variable - APP_BUNDLE_PATH. By default, Xcode 4.x stores applications at

~/Library/Application\ Support/iPhone\ Simulator/xx/Applications/hash/app_name.app

where xx is the iOS version, and hash is the unique key generated by Xcode for the application.
Try to find your .app and do the following

 APP_BUNDLE_PATH='~/Library/Application\ Support/iPhone\ Simulator/xx/Applications/hash/your_app-cal.app' DEVICE=iphone OS=ios5 cucumber 

Now everything should go well.

Guard

This method is not very convenient, but it is quite justified, because calabash cannot know where our app is. And here comes to the aid of the Guard.
Guard is a gem that monitors the file system and when it changes files it monitors for performing any operations. The list of guards is quite extensive, but we need guard-calabash-ios .

To install and use it, do the following:

 gem install guard-calabash-ios guard init calabash-ios 

This will create a Guardfile - a file which describes the properties needed by the guard and the files that you need to monitor. ( Detailed settings can be found on github . )
The final touch is to open the Xcode settings and set Derived Data as Relative. Now Xcode will store the builds in the project directory, which will allow the guard-calabash-ios script to find the APP_BUNDLE_PATH we need automatically.
Now to run the tests you need to do the following in the project folder.

 guard 


We write tests

Now that everything works more conveniently, we can start writing our UI tests.

Calabash has created a features folder that contains our scripts and steps implementation.
Let's make sure that our calculator will allow the user to add or subtract two numbers, and show the correct result in the alert view.

Edit my_first.feature

 Feature: Add numbers As a User I should be able to perform calculations Scenario: Add numbers When I fill in "left" with "15" And I fill in "right" with "10" And I touch "add" Then I should see "25" 

If you still have guard running, then when you save the file, it will automatically run tests, and only the modified file will be tested. This is very convenient if you have several feature files, since it is not necessary to wait after each line until all the tests are run.

So, all the tests failed, which is logical.

Let's add a UI.

To access calabash controls, you need to set their accessibility label. In addition, buttons can be accessed by writing on them, and text fields by placeholder.

I made a primitive interface: two text fields and two buttons in the navigation bar, "+" and "-".
After we added controls to our screen, we need to do the following:

1. Add outlets for buttons and text fields.
2. Set the placeholders to our “left” and “right” text fields
3. Set accessibility labels for buttons

 self.addButton.accessibilityLabel = @"add"; self.subtractButton.accessibilityLabel = @"subtract"; 

4. Hang handlers on our buttons.

 - (IBAction)addButtonTapped:(id)sender { CalculationManager *calculationManager = [CalculationManager sharedInstance]; NSInteger left = [self.leftTextField.text integerValue]; NSInteger right = [self.rightTextField.text integerValue]; [self showResult:[calculationManager add:left with:right]]; } - (IBAction)subtractButtonTapped:(id)sender { CalculationManager *calculationManager = [CalculationManager sharedInstance]; NSInteger left = [self.leftTextField.text integerValue]; NSInteger right = [self.rightTextField.text integerValue]; [self showResult:[calculationManager subtract:right from:left]]; } 


5. Add a method to display the result

 - (void)showResult:(NSInteger)result { NSString *resultString = [NSString stringWithFormat:@"%d", result]; [[[[UIAlertView alloc] initWithTitle:@"Result" message:resultString delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease] show]; } 


6. Go to the terminal with running guard and press Enter, this will launch all your scripts, we have one and if you did everything correctly, then the tests will pass successfully.

Now we will write the test for subtraction.

 Scenario: Subtract numbers When I subtract 15 from 38 Then I should see "23" as result 

After launch, Cucumber will report that it does not know such steps, and will offer to implement them.
Copy and edit a little what he put in the file calabash_steps.rb (project_dir / features / steps_definitions /)

 When /^I subtract (\d+) from (\d+)$/ do |subtrahend, minuend| step %{I fill in "left" with "#{minuend.to_s}"} step %{I fill in "right" with "#{subtrahend.to_s}"} step %{I touch "subtract"} end Then /^I should see "(.*?)" as result$/ do |result| res = query("view:'UIAlertView'", "message").first res.should == result end 


In real life, we would most likely use the same methods as in the first scenario, but here I wanted to show how step definitions look, how to call other steps from the implementation of steps (step% {}), how to get to any value (query) and how to write assert (should).

On this test all.

Conclusion


The described tests and the application look completely ridiculous, but I set my goal to describe with this example the main features that will allow you to immediately start using TDD / BDD, I hope that this came out for me and would be useful for the article.

As a logical conclusion I will cite the links again:

Source: https://habr.com/ru/post/144673/


All Articles