我想实施XCTest单元测试,但并不是真的想使用XCode来完成它。如何做到这一点并不明显,我想知道这是否可行?
从目前为止我发现,人们可以得到XCTest完全依赖于XCode的印象。我找到了xcodebuild test
命令行支持,但这取决于找到XCode项目或工作区。
我在这里有任何选择吗,或者我只是删掉现有的SenTestingKit
代码并恢复到一些家庭酿造单元测试代码?我有一些这样的代码,但它不是正确的事情。
依据/病史:
这不仅仅是我的老朋友。我有一个Objective-C程序,这是我两年前提到过的,为此我根据SenTestingKit
开发了一套合理的单元测试。现在我回到这段代码 - 我可能至少要重建这个东西,因为介入库的变化 - 我发现SenTestingKit
已经消失,被XCTest取代。好吧....
此代码不是使用XCode开发的,因此没有与之关联的ta .project
文件,现在使用SenTestingKit的主程序和Makefile进行测试。 {1}}目标(部分原因是老式的,部分是缺乏对IDE的喜爱,部分原因是这是对Objective-C的实验,所以最初坚持我所知道的。)
答案 0 :(得分:2)
如果您正在寻找基于Xcode的解决方案,请参阅this及其链接解决方案。
对于完整的基于非Xcode的解决方案,请继续阅读。
几年前我曾经问过类似的答案:Is there any non-Xcode-based command line unit testing tool for Objective-C?但事情发生了变化。
随着时间的推移,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;
}
要编译此类代码,您需要显示XCTest所在文件夹的路径:
# in your Makefile
clang -F/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks XCTestDriver.m
不要指望编译代码,但它应该给你一个想法。如果您有任何疑问,请随时询问。还要按照XCTest框架的标题来了解有关其类及其文档的更多信息。
答案 1 :(得分:2)
谢谢,@ stanislaw-pankevich,给出了一个很好的答案。在这里,为了完整起见,我包括(或多或少)完整的测试程序,其中包括一些额外的细节和评论。
(从我的角度来看,这是一个完整的程序,因为它测试功能
在util.h
中定义,此处未包括在内)
档案UtilTest.h
:
#import <XCTest/XCTest.h>
@interface UtilTest : XCTestCase
@end
档案UtilTest.m
:
#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
驱动程序runtests.m
(这是主程序,makefile实际调用它来运行所有测试):
#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
注意:
XCTestObserver
类,并替换为XCTestObservation
。NSLog
中的runtests.m
调用替换了一个与stdout聊天的函数,因此我可以将其与聊天程序区分为默认的ObservationCenter。/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/Library/Frameworks/XCTest.framework/Headers
答案 2 :(得分:0)
这可以正常工作:
使用下面的代码创建一个运行程序命令行工具。 更新:Xcode提供了在/Applications/Xcode.app/Contents/Developer/usr/bin/xctest中相当独立的运行程序 如果您不想创建自己的简单运行器,则可以使用此版本。
使用测试套件的完整路径运行该工具。
示例跑步者代码:
#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;
}
}