Подтвердить что ты не робот

Тестирование модуля в Xcode, запускает ли это приложение?

У меня запуталась странная проблема, с которой я не сталкивался раньше.

Когда вы выполняете cmd + U для запуска тестов единиц (например, OCUnit), действительно ли он вызывает main.m, новый appDelegate и запускает приложение, как если бы вы нажали cmd + R?

Я только спрашиваю, потому что я использую CoreData для этого DataLayer. Я издеваюсь над DataLayer в моих тестах, но как только я применил метод getAll, который на самом деле вызывает CoreData, приложение /xcode бросает исключение из модели управляемого объекта, не может быть nil. Который я понимаю, но я не имею в виду фактически новый класс DataLayer, и я поставил точку останова в моем методе loadView mainviewcontroller, где он вызывает метод DataLayer getAll. Это не имеет значения в тестах, потому что это макет, но он, по-видимому, вызывает реальный экземпляр.

Итак, вернемся к моему вопросу, когда при нажатии cmd + U он также запускает приложение, а затем запускает тесты?

4b9b3361

Ответ 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.