Тестирование модуля в Xcode, запускает ли это приложение?
У меня запуталась странная проблема, с которой я не сталкивался раньше.
Когда вы выполняете cmd + U для запуска тестов единиц (например, OCUnit), действительно ли он вызывает main.m, новый appDelegate и запускает приложение, как если бы вы нажали cmd + R?
Я только спрашиваю, потому что я использую CoreData для этого DataLayer. Я издеваюсь над DataLayer в моих тестах, но как только я применил метод getAll, который на самом деле вызывает CoreData, приложение /xcode бросает исключение из модели управляемого объекта, не может быть nil. Который я понимаю, но я не имею в виду фактически новый класс DataLayer, и я поставил точку останова в моем методе loadView mainviewcontroller, где он вызывает метод DataLayer getAll. Это не имеет значения в тестах, потому что это макет, но он, по-видимому, вызывает реальный экземпляр.
Итак, вернемся к моему вопросу, когда при нажатии cmd + U он также запускает приложение, а затем запускает тесты?
Ответы
Ответ 1
Приложение фактически запущено, но есть трюк, который вы можете использовать, чтобы предотвратить его запуск.
int main(int argc, char* argv[]) {
int returnValue;
@autoreleasepool {
BOOL inTests = (NSClassFromString(@"SenTestCase") != nil
|| NSClassFromString(@"XCTest") != nil);
if (inTests) {
//use a special empty delegate when we are inside the tests
returnValue = UIApplicationMain(argc, argv, nil, @"TestsAppDelegate");
}
else {
//use the normal delegate
returnValue = UIApplicationMain(argc, argv, nil, @"AppDelegate");
}
}
return returnValue;
}
Ответ 2
Здесь представлен вариант ответа Sulthan, который использует XCTest, который является стандартным для тестовых классов, сгенерированных XCode 5.
int main(int argc, char * argv[])
{
@autoreleasepool {
BOOL runningTests = NSClassFromString(@"XCTestCase") != nil;
if(!runningTests)
{
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
else
{
return UIApplicationMain(argc, argv, nil, @"TestAppDelegate");
}
}
}
Это входит в main.m, который должен находиться под поддержкой файлов в стандартном макете проекта.
Затем в вашем каталоге тестов добавьте:
TestAppDelegate.h
#import <Foundation/Foundation.h>
@interface TestAppDelegate : NSObject<UIApplicationDelegate>
@end
TestAppDelegate.m
#import "TestAppDelegate.h"
@implementation TestAppDelegate
@end
Ответ 3
В Swift я предпочитаю обходить обычный путь выполнения внутри application: didFinishLaunchingWithOptions
:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
guard normalExecutionPath() else {
window = nil
return false
}
// regular setup
return true
}
private func normalExecutionPath() -> Bool {
return NSClassFromString("XCTestCase") == nil
}
Код внутри guard
удалит все виды, созданные из раскадровки.
Ответ 4
Если вы используете Swift (у вас, вероятно, нет main.c
), вам необходимо выполнить следующие действия:
1: удалить @UIApplicationMain
в AppDelegate.swift
2: Создайте пустой TestingAppDelegate.swift
import UIKit
class TestingAppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
}
3: Создайте файл с именем main.swift
:
import Foundation
import UIKit
let isRunningTests = NSClassFromString("XCTestCase") != nil
if isRunningTests {
UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(TestingAppDelegate))
} else {
UIApplicationMain(C_ARGC, C_ARGV, nil, NSStringFromClass(AppDelegate))
}
Ответ 5
Да, ваш целевой объект будет иметь целевую зависимость от целевой цели приложения, поэтому цель приложения будет построена при нажатии Cmd + U или Cmd + Shift + U.
Ответ 6
Я нашел другое решение проблемы:
int main(int argc, char * argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, ({
![NSProcessInfo processInfo].environment[@"XCTestConfigurationFilePath"] ?
@"AppDelegate" :
nil;
}));
}
}
Отсюда: http://qualitycoding.org/app-delegate-for-tests/#comment-63984
Ответ 7
Я использую подход Tomasz Bak плюс некоторый код ответа dwb и придумываю следующее:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
BOOL runningTests = NSClassFromString(@"XCTestCase") != nil;
if (runningTests) {
self.window.rootViewController = [UIViewController new];
return true;
}
// Your normal code below this
....
}
Ответ 8
Использование xCode 7 и xCtool
xctool способен выполнять единичные тесты без запуска приложения.
Чтобы сделать это,
1. Обновить целевые настройки для запуска без хост-приложения.
Выберите свой проект → затем проверьте цель → Установите для хост-приложения значение none.
![введите описание изображения здесь]()
2. Установите xctool, если у вас его нет.
brew install xctool
3. Запустите тесты с помощью терминала с помощью xctool.
xctool -workspace yourWorkspace.xcworkspace -scheme yourScheme run-tests -sdk iphonesimulator
Ответ 9
Отличные ответы выше, которые предполагают динамическое изменение делегата приложения во время выполнения.
Небольшая модификация, которую я делаю, заключается в обнаружении unit test run путем запроса NSProcessInfo
. Преимущество состоит в том, что вам не нужно иметь класс, который может быть обнаружен, чтобы проверить, выполняются ли модульные тесты.
int main(int argc, char * argv[])
{
// Put your App delegate class here.
const Class appDelegateClass = [ATAppDelegate class];
NSDictionary *const environmentDictionary =
[[NSProcessInfo processInfo] environment];
const BOOL runningUnitTests =
environmentDictionary[@"XCInjectBundleInto"] != nil;
NSString *delegateName =
runningUnitTests ? nil : NSStringFromClass(appDelegateClass);
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, delegateName);
}
}
Свойство @"XCInjectBundleInto"
в environmentDictionary
- это путь к вашему пакетному тестированию и настроен на Xcode.