是否可以在没有Xcode的情况下使用XCTest单元测试?
我想实现XCTest单元测试,但实际上不想使用XCode来实现。如何做到这一点并不明显,我想知道这是否可能 从目前为止我所发现的情况来看,人们可能会觉得XCTest完全依赖于XCode。我已经找到了命令行支持,但这取决于找到一个XCode项目或工作区 我在这里有什么选择吗,还是干脆删除现有的是否可以在没有Xcode的情况下使用XCTest单元测试?,xcode,xctest,Xcode,Xctest,我想实现XCTest单元测试,但实际上不想使用XCode来实现。如何做到这一点并不明显,我想知道这是否可能 从目前为止我所发现的情况来看,人们可能会觉得XCTest完全依赖于XCode。我已经找到了命令行支持,但这取决于找到一个XCode项目或工作区 我在这里有什么选择吗,还是干脆删除现有的SenTestingKit代码,恢复到一些自制的单元测试代码?我手头有一些这样的代码,但这不是正确的做法 理由/历史: 这不仅仅是我是个老笨蛋。我有一个Objective-C程序,我上一次接触它是在两年前,
SenTestingKit
代码,恢复到一些自制的单元测试代码?我手头有一些这样的代码,但这不是正确的做法
理由/历史: 这不仅仅是我是个老笨蛋。我有一个Objective-C程序,我上一次接触它是在两年前,为此我基于
SenTestingKit
开发了一套合理的单元测试。现在我回到这段代码——我可能至少必须重建它,因为干预了库的更改——我发现SenTestingKit
已经消失了,取而代之的是XTest。哦,好吧
此代码不是使用XCode开发的,因此没有与之关联的
.project
文件,到目前为止,使用SentTestingKit的主程序和Makefilecheck
目标可以愉快地管理测试(部分原因是旧的skool,再次,部分原因是缺乏对IDE的喜爱,部分原因是这是对Objective-C的实验,所以最初坚持我所知道的)。如果您正在寻找基于Xcode的解决方案,请参阅及其链接的解决方案以获取示例
要获得完整的非Xcode解决方案,请继续阅读
几年前我曾问过类似的问题:但从那以后情况发生了变化
随着时间的推移,XCTest中出现的一个有趣的功能是能够运行您的自定义测试套件。我曾经为了我的研究需要成功地实现了它们,下面是一个命令行Mac OS应用程序的示例代码:
@interface FooTest : XCTestCase
@end
@implementation FooTest
- (void)testFoo {
XCTAssert(YES);
}
- (void)testFoo2 {
XCTAssert(NO);
}
@end
@interface TestObserver : NSObject <XCTestObservation>
@property (assign, nonatomic) NSUInteger testsFailed;
@end
@implementation TestObserver
- (instancetype)init {
self = [super init];
self.testsFailed = 0;
return self;
}
- (void)testBundleWillStart:(NSBundle *)testBundle {
NSLog(@"testBundleWillStart: %@", testBundle);
}
- (void)testBundleDidFinish:(NSBundle *)testBundle {
NSLog(@"testBundleDidFinish: %@", testBundle);
}
- (void)testSuiteWillStart:(XCTestSuite *)testSuite {
NSLog(@"testSuiteWillStart: %@", testSuite);
}
- (void)testCaseWillStart:(XCTestCase *)testCase {
NSLog(@"testCaseWillStart: %@", testCase);
}
- (void)testSuiteDidFinish:(XCTestSuite *)testSuite {
NSLog(@"testSuiteDidFinish: %@", testSuite);
}
- (void)testSuite:(XCTestSuite *)testSuite didFailWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber {
NSLog(@"testSuite:didFailWithDescription:inFile:atLine: %@ %@ %@ %tu",
testSuite, description, filePath, lineNumber);
}
- (void)testCase:(XCTestCase *)testCase didFailWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber {
NSLog(@"testCase:didFailWithDescription:inFile:atLine: %@ %@ %@ %tu",
testCase, description, filePath, lineNumber);
self.testsFailed++;
}
- (void)testCaseDidFinish:(XCTestCase *)testCase {
NSLog(@"testCaseWillFinish: %@", testCase);
}
@end
int RunXCTests() {
XCTestObserver *testObserver = [XCTestObserver new];
XCTestObservationCenter *center = [XCTestObservationCenter sharedTestObservationCenter];
[center addTestObserver:testObserver];
XCTestSuite *suite = [XCTestSuite defaultTestSuite];
[suite runTest];
NSLog(@"RunXCTests: tests failed: %tu", testObserver.testsFailed);
if (testObserver.testsFailed > 0) {
return 1;
}
return 0;
}
不要期望代码能够编译,但它应该会给你一个想法。如果你有任何问题,请随时提问。也可以按照XTest framework的标题了解更多关于它的类及其文档的信息。谢谢@stanislaw pankevich提供了一个很好的答案。为了完整性,我在这里加入了(或多或少)我最终完成的完整测试程序,包括一些额外的细节和注释 (我认为这是一个完整的程序,因为它测试功能 在
util.h
中定义,此处不包括)
文件UtilTest.h
:
#import <XCTest/XCTest.h>
@interface UtilTest : XCTestCase
@end
驱动程序runtests.m
(这是主程序,makefile实际调用它来运行所有测试):
#导入“UtilTest.h”
#进口
//定义我的观察对象——我只需要在一个地方做这个
@接口brownetestobservation:NSObject
@属性(赋值,非原子)NSU整数测试失败;
@属性(赋值,非原子)NSUTEGER TestScaled;
@结束
@实现布朗观测
-(instancetype)初始化{
self=[super init];
self.testsFailed=0;
回归自我;
}
//我们可以在这里添加各种其他功能,以便了解
//各种事件:请参见
// https://developer.apple.com/reference/xctest?language=objc
-(void)testSuiteWillStart:(XCTestSuite*)testSuite{
NSLog(@“套件%@…,[testSuite名称]);
self.testscaled=0;
}
-(void)testsuiteddfinish:(XCTestSuite*)testSuite{
NSLog(@“…套件%@(%tu测试)”,[testSuite名称],self.testscaled);
}
-(void)testCaseWillStart:(XCTestSuite*)testCase{
NSLog(@“测试用例:%@,[testCase名称]);
self.testscaled++;
}
-(void)testCase:(XCTestCase*)testCase didFailWithDescription:(NSString*)description inflee:(NSString*)filePath atLine:(nsinteger)lineNumber{
NSLog(@“失败:%@,%@(%@:%tu)”,测试用例,描述,文件路径,行号);
self.testsFailed++;
}
@结束
int main(int argc,字符**argv){
XCTestObservationCenter*center=[XCTestObservationCenter sharedTestObservationCenter];
Brownetestobservation*observator=[Brownetestobservation new];
[中心添加测试观察者:观察者];
类类[]={[UtilTest类],};//在此处添加其他类
int nclasses=sizeof(classes)/sizeof(classes[0]);
对于(int i=0;i 0){
NSLog(@“运行测试:%tu失败”,observer.tests失败);
rval=1;
}
返回rval;
}
生成文件:
FRAMEWORKS=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks
TESTCASES=UtilTest
%.o: %.m
clang -F$(FRAMEWORKS) -c $<
check: runtests
./runtests 2>runtests.stderr
runtests: runtests.o $(TESTCASES:=.o) ../libmylib.a
cc -o $@ $< -framework Cocoa -F$(FRAMEWORKS) -rpath $(FRAMEWORKS) \
-framework XCTest $(TESTCASES:=.o) -L.. -lmylib
FRAMEWORKS=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/FRAMEWORKS
TESTCASES=UtilTest
%.o:%.m
clang-F$(框架)-c$<
检查:运行测试
./runtests 2>runtests.stderr
runtests:runtests.o$(测试用例=0)…/libmylib.a
cc-o$@$<-framework Cocoa-F$(框架)-rpath$(框架)\
-框架XCTest$(测试用例:=.o)-L..-lmylib
注:
类现在已被弃用,并被XCTestObservation
取代XCTestObservation
- 测试结果被发送到一个共享的XCTestObservationCenter,不幸的是,它会干扰stderr(因此必须重定向到其他地方)–似乎无法避免这种情况,只能将它们发送到我的观察中心。在我的实际程序中,我用一个函数替换了
中的runtests.m
调用,该函数会向stdout发出颤音,因此我可以将其与发送到默认观察中心的颤音区分开来NSLog
- 另见 (假设您使用的是XCode)
- …这个
- …以及(eg)
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework/headers中文件标题中的注释
#import "UtilTest.h"
#import "../util.h" // the definition of the functions being tested
@implementation UtilTest
// We could add methods setUp and tearDown here.
// Every no-arg method which starts test... is included as a test-case.
- (void)testPathCanonicalization
{
XCTAssertEqualObjects(canonicalisePath("/p1/./p2///p3/..//f3"), @"/p1/p2/f3");
}
@end
#import "UtilTest.h"
#import <XCTest/XCTestObservationCenter.h>
// Define my Observation object -- I only have to do this in one place
@interface BrownieTestObservation : NSObject<XCTestObservation>
@property (assign, nonatomic) NSUInteger testsFailed;
@property (assign, nonatomic) NSUInteger testsCalled;
@end
@implementation BrownieTestObservation
- (instancetype)init {
self = [super init];
self.testsFailed = 0;
return self;
}
// We can add various other functions here, to be informed about
// various events: see XCTestObservation at
// https://developer.apple.com/reference/xctest?language=objc
- (void)testSuiteWillStart:(XCTestSuite *)testSuite {
NSLog(@"suite %@...", [testSuite name]);
self.testsCalled = 0;
}
- (void)testSuiteDidFinish:(XCTestSuite *)testSuite {
NSLog(@"...suite %@ (%tu tests)", [testSuite name], self.testsCalled);
}
- (void)testCaseWillStart:(XCTestSuite *)testCase {
NSLog(@" test case: %@", [testCase name]);
self.testsCalled++;
}
- (void)testCase:(XCTestCase *)testCase didFailWithDescription:(NSString *)description inFile:(NSString *)filePath atLine:(NSUInteger)lineNumber {
NSLog(@" FAILED: %@, %@ (%@:%tu)", testCase, description, filePath, lineNumber);
self.testsFailed++;
}
@end
int main(int argc, char** argv) {
XCTestObservationCenter *center = [XCTestObservationCenter sharedTestObservationCenter];
BrownieTestObservation *observer = [BrownieTestObservation new];
[center addTestObserver:observer];
Class classes[] = { [UtilTest class], }; // add other classes here
int nclasses = sizeof(classes)/sizeof(classes[0]);
for (int i=0; i<nclasses; i++) {
XCTestSuite *suite = [XCTestSuite testSuiteForTestCaseClass:classes[i]];
[suite runTest];
}
int rval = 0;
if (observer.testsFailed > 0) {
NSLog(@"runtests: %tu failures", observer.testsFailed);
rval = 1;
}
return rval;
}
FRAMEWORKS=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks
TESTCASES=UtilTest
%.o: %.m
clang -F$(FRAMEWORKS) -c $<
check: runtests
./runtests 2>runtests.stderr
runtests: runtests.o $(TESTCASES:=.o) ../libmylib.a
cc -o $@ $< -framework Cocoa -F$(FRAMEWORKS) -rpath $(FRAMEWORKS) \
-framework XCTest $(TESTCASES:=.o) -L.. -lmylib
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
int main(int argc, const char* argv[]) {
@autoreleasepool {
XCTestSuite *suite = [XCTestSuite testSuiteForBundlePath:
[NSString stringWithUTF8String:argv[1]]];
[suite runTest];
// Note that XCTestSuite is very shy in terms of errors,
// so make sure that it loaded anything indeed:
if (!suite.testRun.testCaseCount) return 1;
return suite.testRun.hasSucceeded;
}
}