Статьи

Введение в тестирование на iOS

Никто не хочет грузить программное обеспечение с ошибками. Чтобы выпустить мобильное приложение высочайшего качества, требуется гораздо больше, чем процесс обеспечения качества, осуществляемый вручную. Новые устройства и операционные системы публикуются каждый год. Это означает, что существует постоянно расширяющаяся комбинация размеров экрана и версий операционной системы, на которых вы должны протестировать свое мобильное приложение. Мало того, что это будет очень много времени, но попытка протестировать ваше приложение iOS с помощью ручного тестирования пренебрегает целым фрагментом современного процесса разработки программного обеспечения, автоматического тестирования обеспечения качества.

В современном мире доступно множество инструментов, которые можно использовать для автоматического тестирования написанного вами программного обеспечения. Некоторые из этих инструментов поддерживаются с помощью модели с открытым исходным кодом, но есть также основной набор, предоставленный Apple. С каждым новым выпуском iOS SDK Apple продолжала демонстрировать свою приверженность совершенствованию инструментов, доступных разработчикам для тестирования написанного ими кода. Для разработчика iOS, который является новичком в автоматизированном тестировании и заинтересован в начале работы, инструменты Apple — хорошее место для начала.

В этом руководстве будут представлены инструкции по использованию инструмента, предоставляемого Apple для автоматического тестирования, XCTest . XCTest — это модуль модульного тестирования Apple. Модульное тестирование — это тип автоматического тестирования, который проверяет код на самом низком уровне. Вы пишете код Objective-C, который вызывает методы из вашего «производственного» кода, и проверяете, что тестируемый код действительно выполняет то, для чего он предназначен. Правильно ли установлены переменные? Верно ли возвращаемое значение?

Тесты, написанные с использованием инфраструктуры XCTest, могут неоднократно выполняться с кодом вашего приложения, чтобы помочь вам обрести уверенность в том, что вы создаете продукт без ошибок, поскольку новые изменения кода не нарушают существующую функциональность.

По умолчанию каждый новый проект XCode создается с хорошей отправной точкой для написания модульных тестов. Это включает в себя три вещи:

  • отдельная цель для ваших тестов
  • группа для ваших тестовых классов
  • пример теста


Давайте углубимся в структуру модульного теста iOS. Отдельный модульный тест представляется как отдельный метод в любом подклассе XCTestCase где метод возвращает void , не принимает параметров, а имя метода начинается с test .

1
— (void) testSomething{}

К счастью, Xcode облегчает создание тестовых случаев. В новых проектах XCode для вас создается начальный контрольный пример в отдельной группе файлов, в имени которой добавляется слово « Тесты» .

Я создал пример проекта, который можно использовать в качестве ссылки для примеров, приведенных в этом руководстве. Загрузите проект с GitHub и откройте его в Xcode.

В примере проекта вы можете найти группу тестов в папке с именем JumblifyTests .

Чтобы создать первый тестовый пример, щелкните правой кнопкой группу файлов JumblifyTests и выберите « Новый файл» . Выберите Test Case Class из раздела iOS> Source и присвойте новому подклассу имя.

Типичное соглашение о присвоении имен заключается в том, чтобы называть тестовый пример так, чтобы это было имя соответствующего тестируемого класса с суффиксом Tests . Поскольку мы будем тестировать класс JumblifyViewController , XCTestCase подкласс JumblifyViewControllerTests .

В новом подклассе XCTestCase вы увидите четыре метода. Два из них сами по себе являются тестами. Можете ли вы определить, кто они? Помните, что имена тестовых методов начинаются со слова «тест».

Если вы этого не testExample по умолчанию используются testExample методы тестирования: testExample и testPerformanceExample .

Удалите оба теста, потому что мы собираемся написать наш с нуля. Два других метода, setUp и tearDown , переопределяются из суперкласса XCTestCase . Они уникальны тем, что setUp и tearDown вызываются до и после того, как каждый метод теста вызывается соответственно. Они являются полезными местами для централизации кода, который должен выполняться до или после вызова каждого метода тестирования. Здесь находятся такие задачи, как обычная инициализация или очистка.

Импортируйте файл JumblifyViewController класса JumblifyViewController и добавьте свойство типа JumblifyViewController в подкласс XCTestCase .

1
@property (nonatomic) JumblifyViewController *vcToTest;

В методе setUp инициализируйте свойство, как показано ниже.

1
2
3
4
5
— (void)setUp
{
    [super setUp];
    self.vcToTest = [[JumblifyViewController alloc] init];
}

Теперь мы собираемся написать тест для проверки reverseString: метод класса JumblifyViewController .

Создайте метод тестирования, который использует экземплярный объект vcToTest для проверки метода reverseString: В этом тестовом методе мы создаем объект NSString и передаем его в метод reverseString: контроллера reverseString: . Общепринято давать вашему тесту осмысленное имя, чтобы было понятно, что тест тестирует.

1
2
3
4
— (void)testReverseString {
    NSString *originalString = @»himynameisandy»;
    NSString *reversedString = [self.vcToTest reverseString:originalString];
}

На данный момент мы еще не сделали ничего полезного, потому что мы еще не тестировали метод reverseString: . Нам нужно сравнить выходные reverseString: метода reverseString: с тем, что мы ожидаем получить.

Функция XCTAssertEqualObjects является частью инфраструктуры XCTest. Инфраструктура XCTest предоставляет множество других методов для создания утверждений о состоянии приложения, таких как равенство переменных или результаты логических выражений. В этом случае мы заявили, что два объекта должны быть равны. Если это так, тест проходит успешно, а если нет, тест не пройден. Взгляните на документацию Apple для получения полного списка утверждений, предоставляемых платформой XCTest.

1
2
3
4
5
6
7
— (void)testReverseString {
    NSString *originalString = @»himynameisandy»;
    NSString *reversedString = [self.vcToTest reverseString:originalString];
 
    NSString *expectedReversedString = @»ydnasiemanymih»;
    XCTAssertEqualObjects(expectedReversedString, reversedString, @»The reversed string did not match the expected reverse”);
}

Если вы попытаетесь скомпилировать код на этом этапе, вы заметите предупреждение при попытке вызвать reverseString: из тестового примера. reverseString: метод является закрытым методом класса JumblifyViewController . Это означает, что другие объекты не могут вызвать этот метод, поскольку он не определен в заголовочном файле класса JumblifyViewController .

Хотя написание тестируемого кода является мантрой, которой следуют многие разработчики, мы не хотим без необходимости модифицировать наш тестируемый код. Но как мы вызываем закрытый reverseString: метод класса JumblifyViewController в наших тестах? Мы могли бы добавить публичное определение reverseString: метод в заголовочный файл класса JumblifyViewController , но это нарушает шаблон инкапсуляции.

Одним из решений является добавление частной категории в класс JumblifyViewController для предоставления метода reverseString: Мы добавляем эту категорию в подкласс XCTestCase , что означает, что она доступна только в этом классе. При добавлении этой категории контрольный пример будет компилироваться без предупреждений или ошибок.

1
2
3
4
5
@interface JumblifyViewController (Test)
 
— (NSString *)reverseString:(NSString *)stringToReverse;
 
@end

Давайте запустим наши тесты, чтобы убедиться, что они пройдут. Существует несколько способов запуска модульных тестов для приложения iOS. Я наркоман по сочетанию клавиш, поэтому моя самая часто используемая техника для запуска модульных тестов в моем приложении — это нажатие Command-U . Это сочетание клавиш запускает все тесты для вашего приложения. Вы также можете выполнить то же действие, выбрав Test в меню Product .

По мере роста вашего набора тестов, или если вам нравится внедрять тестовую разработку, вы обнаружите, что запуск вашего набора тестов может стать слишком длительным. Или это может помешать вашему рабочему процессу. Очень полезная команда, скрытая в меню XCode, в которую я влюбился, это Command-Option-Control-U . Этот ярлык запускает команду, запускающую тест, в котором находится ваш курсор. После того, как ваш набор тестов проработан и завершен, вы всегда должны запускать весь набор тестов. Выполнение отдельного теста полезно, когда вы пишете новый тест или когда вы отлаживаете неудачный тест.

Команда для запуска одного теста дополняется командой Command-Option-Control-G , которая перезапускает последний запуск теста. Это может быть весь набор тестов или только самый последний тест, над которым вы работаете. Это также полезно, если вы отошли от какого-либо теста, над которым работаете, и все еще находитесь в процессе его отладки.

Вы можете увидеть результаты теста в нескольких местах. Одним из таких мест является Test Navigator справа.

Другой вариант, глядя на желоб редактора исходного кода .

В любом из этих двух мест нажатие на зеленый ромб с белой галочкой приведет к повторному запуску этого конкретного теста. В случае неудачного теста вы увидите красный бриллиант с белым крестом в центре. Нажатие на него также запустит этот конкретный тест.

Xcode 6 представил два новых замечательных дополнения к модульному тестированию на iOS и OS X: тестирование асинхронной функциональности и измерение производительности определенного фрагмента кода.

До Xcode 6 не было хорошего способа модульного тестирования асинхронного кода. Если ваш модульный тест вызвал метод, содержащий асинхронную логику, вы не можете проверить асинхронную логику. Тест завершится до того, как будет выполнена асинхронная логика в тестируемом методе.

Для тестирования асинхронного кода Apple представила API, который позволяет разработчикам определить ожидание, которое должно быть выполнено для успешного завершения теста. Последовательность действий следующая: определите ожидание, дождитесь его ожидания и выполните ожидание, когда асинхронный код завершит выполнение. Посмотрите на приведенный ниже пример для пояснения.

1
2
3
4
5
6
7
8
— (void)testDoSomethingThatTakesSomeTime {
    XCTestExpectation *completionExpectation = [self expectationWithDescription:@»Long method»];
    [self.vcToTest doSomethingThatTakesSomeTimesWithCompletionBlock:^(NSString *result) {
        XCTAssertEqualObjects(@»result», result, @»Result was not correct!»);
        [completionExpectation fulfill];
    }];
    [self waitForExpectationsWithTimeout:5.0 handler:nil];
}

В этом примере мы тестируем метод doSomethingThatTakesSomeTimesWithCompletionBlock . Мы хотим связать успех или неудачу нашего теста со значением, которое возвращается в блоке завершения, вызываемом тестируемым методом.

Для этого мы определяем ожидание в начале метода теста. В конце метода теста мы ожидаем, что ожидание будет выполнено. Как видите, мы также можем передать параметр timeout.

Фактическое утверждение теста выполняется внутри блока завершения тестируемого метода, в котором мы также выполняем ожидание, которое мы определили ранее. В результате, когда тест выполняется, тест ожидает, пока ожидание не будет выполнено или произойдет сбой, если истечет время ожидания и ожидание не будет выполнено.

Еще одним дополнением к модульному тестированию в Xcode 6 является возможность измерять производительность фрагмента кода. Это позволяет разработчикам получить представление о конкретной информации о времени тестируемого кода.

С помощью тестирования производительности вы можете ответить на вопрос «Каково среднее время выполнения этого фрагмента кода?» Если есть раздел, который особенно чувствителен к изменениям с точки зрения времени, которое требуется для выполнения, то вы можете использовать тестирование производительности, чтобы измерить количество времени, которое требуется для выполнения.

Вы также можете определить базовое время выполнения. Это означает, что если тестируемый код значительно отклоняется от этого базового уровня, тест не пройден. Xcode будет многократно выполнять проверяемый код и измерять время его выполнения. Чтобы измерить производительность фрагмента кода, используйте measureBlock: API, как показано ниже.

1
2
3
4
5
6
— (void)testPerformanceReverseString {
    NSString *originalString = @»himynameisandy»;
    [self measureBlock:^{
        [self.vcToTest reverseString:originalString];
    }];
}

Нажмите на появившееся информационное сообщение.

Установите или измените базовое время выполнения теста производительности.

Из этого руководства вы узнали, как использовать XCode для создания модульных тестов для проверки приложений iOS программным и автоматическим способом. Попробуйте его на существующей кодовой базе или на чем-то совершенно новом. Независимо от того, полностью ли вы посвятили себя модульному тестированию или добавили пару тестов, вы только добавляете ценность своему проекту, создавая более строго проверенное программное обеспечение, которое менее подвержено риску будущих изменений. Модульное тестирование — это только начало автоматизированного тестирования программного обеспечения. Есть несколько дополнительных уровней тестирования, которые вы можете добавить в приложение для iOS.