2013-03-27 3 views
4

Есть ли функция библиотеки времени выполнения Objective-C (маловероятна) или набор функций, способных проверять переменные статического (квазиклассического уровня) в Objective-C? Я знаю, что могу использовать метод доступа класса, но мне бы хотелось пройти тест без написания кода «для тестовой среды».Доступ к статическим переменным, имитирующим переменные класса из модульных тестов

Или, существует ли неясная простая методика C для внешнего доступа к статическим варам? Обратите внимание, что эта информация предназначена для модульного тестирования - она ​​не должна быть пригодной для использования в производстве. Я сознаю, что это противоречит намерениям статических варсов ... коллега затронул эту тему, и мне всегда интересно врываться в внутренние объекты ObjC/C.

@interface Foo : NSObject 
+ (void)doSomething; 
@end 

@implementation Foo 
static BOOL bar; 
+ (void)doSomething 
{ 
    //do something with bar 
} 
@end 

Учитывая вышеизложенное можно использовать библиотеку времени выполнения или другой интерфейс C, чтобы проверить bar? Статические переменные являются конструкцией C, возможно, существует определенная зона памяти для static vars? Меня интересуют другие конструкции, которые могут моделировать переменные класса в ObjC и также могут быть протестированы.

+0

check 'objc/runtime.h' –

+1

Эти переменные берутся из C: теги не должны редактироваться. –

+0

Я проверил заголовок runtime, и я часто использую некоторые из этих функций. –

ответ

4

Нет, на самом деле, если вы не подвергли действию переменную static через какой-либо метод класса. Вы можете предоставить метод + (BOOL)validateBar, который выполняет все необходимые проверки, а затем вызывает это из вашей тестовой среды.

Кроме того, это не переменная Objective-C, а скорее переменная C, поэтому я сомневаюсь, что в ходе выполнения Objective-C Runtime может быть что-то еще.

+0

Да, я позвонил, что я уже могу это сделать в своем вопросе. –

+0

+1, хорошие рассуждения о том, что это не переменная Objective-C, а скорее переменная C - я знал это, но, несмотря на фэнтези, это может быть возможно через некоторую лазейку, а также надеяться, что некоторые из парней старой школы C мог бы получить доступ к нему напрямую через адрес памяти или аналогичную технику. –

0

Короткий ответ: accessing a static variable from another file невозможно. Это точно такая же проблема, как и попытка ссылаться на локальную переменную-функции из другого места; имя просто недоступно. В C существует три этапа «видимости» для объектов *, которые называются «привязкой»: внешние (глобальные), внутренние (ограниченные одной «единицей перевода» - свободно, один файл) и " no "(функция-локальная). Когда вы объявляете переменную как static, она получает внутреннюю привязку; ни один другой файл не может получить к нему доступ по имени. Вы должны сделать какую-либо функцию доступа, чтобы разоблачить ее.

Расширенный ответ заключается в том, что, поскольку существует некоторая обтекаемость библиотеки ObjC, которую мы можем делать в любом случае для имитации переменных уровня класса, мы можем сделать несколько обобщенный тестовый код, который вы можете условно скомпилировать. Однако это не так просто.

Прежде чем мы начнем, отметим, что для этого все еще требуется индивидуальная реализация одного метода; нет никакого способа обойти это из-за ограничений связи.

Шаг один, объявить методы, один для настройки, а затем набор для valueForKey: -как доступа:

// ClassVariablesExposer.h 

#if UNIT_TESTING 
#import <Foundation/Foundation.h> 
#import <objc/runtime.h> 

#define ASSOC_OBJ_BY_NAME(v) objc_setAssociatedObject(self, #v, v, OBJC_ASSOCIATION_ASSIGN) 
// Store POD types by wrapping their address; then the getter can access the 
// up-to-date value. 
#define ASSOC_BOOL_BY_NAME(b) NSValue * val = [NSValue valueWithPointer:&b];\ 
objc_setAssociatedObject(self, #b, val, OBJC_ASSOCIATION_RETAIN) 

@interface NSObject (ClassVariablesExposer) 

+ (void)associateClassVariablesByName; 

+ (id)classValueForName:(char *)name; 
+ (BOOL)classBOOLForName:(char *)name; 

@end 
#endif /* UNIT_TESTING */ 

Эти методы семантически больше похожи на протокол, чем категория. Первый метод должен быть переопределен в каждом подклассе, потому что переменные, которые вы хотите связать, конечно, будут разными, и из-за проблемы с привязкой. Фактический вызов objc_setAssociatedObject(), где вы ссылаетесь на переменную , должен быть в файле, где объявлена ​​переменная.

Включение этого метода в протокол потребует дополнительного заголовка для вашего класса, поскольку, хотя реализация метода протокола должна идти в основном файле реализации, ARC и ваши модульные тесты должны видеть объявление, которое ваш класс соответствует протоколу. Громоздкие.Разумеется, вы можете сделать эту категорию NSObject соответствующей протоколу, но тогда вам понадобится заглушка в любом случае, чтобы избежать предупреждения «неполной реализации». Я делал каждое из этих действий при разработке этого решения и решил, что они не нужны.

Второй набор, Аксессоры, очень хорошо работают как методы категории, потому что они просто выглядеть следующим образом:

// ClassVariablesExposer.m 

#import "ClassVariablesExposer.h" 

#if UNIT_TESTING 
@implementation NSObject (ClassVariablesExposer) 

+ (void)associateClassVariablesByName 
{ 
    // Stub to prevent warning about incomplete implementation. 
} 

+ (id)classValueForName:(char *)name 
{ 
    return objc_getAssociatedObject(self, name); 
} 

+ (BOOL)classBOOLForName:(char *)name 
{ 
    NSValue * v = [self classValueForName:name]; 
    BOOL * vp = [v pointerValue]; 
    return *vp; 
} 

@end 
#endif /* UNIT_TESTING */ 

Полностью целом, однако их успешное использование действительно зависит от вашей занятости макросов из выше.

Далее определите класс, переопределение, что создать метод, чтобы захватить ваши переменные класса:

// Milliner.h 

#import <Foundation/Foundation.h> 

@interface Milliner : NSObject 
// Just for demonstration that the BOOL storage works. 
+ (void)flipWaterproof; 
@end 

// Milliner.m 

#import "Milliner.h" 

#if UNIT_TESTING 
#import "ClassVariablesExposer.h" 
#endif /* UNIT_TESTING */ 

@implementation Milliner 
static NSString * featherType; 
static BOOL waterproof; 

+(void)initialize 
{ 
    featherType = @"chicken hawk"; 
    waterproof = YES; 
} 

// Just for demonstration that the BOOL storage works. 
+ (void)flipWaterproof 
{ 
    waterproof = !waterproof; 
} 

#if UNIT_TESTING 
+ (void)associateClassVariablesByName 
{ 
    ASSOC_OBJ_BY_NAME(featherType); 
    ASSOC_BOOL_BY_NAME(waterproof); 
} 
#endif /* UNIT_TESTING */ 

@end 

Убедитесь, что ваш блок тестовый файл импортирует заголовок для данной категории. Простая демонстрация этой функциональности:

#import <Foundation/Foundation.h> 
#import "Milliner.h" 
#import "ClassVariablesExposer.h" 

#define BOOLToNSString(b) (b) ? @"YES" : @"NO" 

int main(int argc, const char * argv[]) 
{ 

    @autoreleasepool { 

     [Milliner associateClassVariablesByName]; 
     NSString * actualFeatherType = [Milliner classValueForName:"featherType"]; 
     NSLog(@"Assert [[Milliner featherType] isEqualToString:@\"chicken hawk\"]: %@", BOOLToNSString([actualFeatherType isEqualToString:@"chicken hawk"])); 

     // Since we got a pointer to the BOOL, this does track its value. 
     NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"])); 
     [Milliner flipWaterproof]; 
     NSLog(@"%@", BOOLToNSString([Milliner classBOOLForName:"waterproof"])); 

    } 
    return 0; 
} 

Я поставил проект на GitHub: https://github.com/woolsweater/ExposingClassVariablesForTesting

Еще одно предостережение в том, что каждый тип POD вы хотите, чтобы иметь возможность доступа потребуется свой собственный метод: classIntForName:,,

Хотя это работает, и я всегда наслаждаюсь обезьяной вокруг с ObjC, я думаю, что это может быть слишком умно пополам; если у вас есть только одна или две из этих переменных класса, самое простое предложение состоит в том, чтобы условно скомпилировать для них аксессоры (сделать фрагмент кода Xcode). Мой код здесь, вероятно, сэкономит вам время и силы, если у вас есть лотов переменных в одном классе.

По-прежнему, возможно, вы можете использовать его. Надеюсь, это было интересно читать, по крайней мере.


* Значение просто «вещь, которая, как известно линкера» - функция, переменная, структура и т.д. - не в ObjC или чувств C++.

+0

Мне нравятся ваши ответы. Они всегда актуальны. Но, возможно, это не подходит в качестве комментария, не так ли?Этот ответ действительно полезен для меня. Спасибо. – 2013-04-07 19:21:48

+0

Спасибо за вашу оценку, @IsaMeg! Похоже, что вы, возможно, просто поддержали несколько моих ответов в быстрой последовательности. Мысль хорошая, но вам следует помнить, что система [может считать это смелое голосование] (http://meta.stackexchange.com/questions/126829/what-is-serial-voting-and-how-does-it -ффект-меня) и автоматически отменяют голоса. Однако я рад, что вы нашли мои сообщения полезными. –

+0

Я одобряю хорошие ответы от людей, которые публикуют хорошие ответы. Звучит правильно. – 2013-04-07 20:48:34

Смежные вопросы