XCTest高效测试:Arrange, Act, 和 Assert
无论你是使用XCTest, Quick或者其他的一些测试框架,你都能通过下面简单的模式写出高效的单元测试:
1. Arrange
2. Act
3. Assert
使用 Arrange, Act, 和Assert
现在。我们来看一个名字叫做Banana的简单类:
// Banana/Banana.swift
/** A delicious banana. Tastes better if you peel it first. */
public class Banana {
private var isPeeled = false
/** Peels the banana. */
public func peel() {
isPeeled = true
}
/** You shouldn't eat a banana unless it's been peeled. */
public var isEdible: Bool {
return isPeeled
}
}
//Banana.h
@interface Banana : NSObject
@property (assign,nonatomic) BOOL isPeeled;
-(void)peel;
-(BOOL)isEdibal;
-(NSString*)offer:(Banana*)banana;
@end
//Banana.m
-(instancetype)init{
self = [super init];
if (self) {
_isPeeled = false;
}
return self;
}
-(void)peel{
_isPeeled = true;
}
-(BOOL)isEdibal{
return self.isPeeled;
}
-(NSString*)offer:(Banana*)banana{
if (banana.isEdibal) {
return @"Hey, want a banana?";
} else {
return @"Hey, want me to peel this banana for you?";
}
}
接下来验证Banana.peel()方法去假定做些什么:
// BananaTests/BananaTests.swift
class BananaTests: XCTestCase {
func testPeel() {
// Arrange: Create the banana we'll be peeling.
let banana = Banana()
// Act: Peel the banana.
banana.peel()
// Assert: Verify that the banana is now edible.
XCTAssertTrue(banana.isEdible)
}
}
//objectes-c .m文件
- (void)setUp {
[super setUp];
// Arrange: Create a banana and peel it.
self.banana = [[Banana alloc]init];
}
-(void)testPeel {
// Act: Create the string used to offer the banana.
[self.banana peel];
// Assert: Verify it's the right string.
XCTAssertEqual(message, @"Hey, want a banana?");
}
使用清晰的测试方法名:
我们必须确保testPeel()方法;如果Banana.peel() 方法不能停止工作;我们知道。
事实上这些是经常发生的当我们的应用代码改变的时候,这意味着:
1. 我们的测试突然停止,因此我们会去修改测试的代码
2. 我们所做的改变我们的应用(这里应该指的是测试代码)应该知道如何工作--也许应为我们新增了一个类的特征--因此我们去改变测试代码
如果测试开始停止,我门如何知道适应于那种情况导致的?也许使你惊讶这个的测试的名字是我们最好的指导。好的测试名字:
1. 清楚告诉测什么的。
2. 清楚告诉测试是通过还是失败
方法 testPeel()是清楚的名字吗?我们来使他更清晰:
// BananaTests.swift
//-func testPeel() {
+func testPeel_makesTheBananaEdible() {
// Arrange: Create the banana we'll be peeling.
let banana = Banana()
// Act: Peel the banana.
banana.peel()
// Assert: Verify that the banana is now edible.
XCTAssertTrue(banana.isEdible)
}
//objectes-c .m文件
- (void)testPeel_makesTheBananaEdible() {
[super setUp];
// Arrange: Create a banana and peel it.
self.banana = [[Banana alloc]init];
}
-(void)testPeel_makesTheBananaEdible() {
// Act: Create the string used to offer the banana.
[self.banana peel];
// Assert: Verify it's the right string.
XCTAssertEqual(message, @"Hey, want a banana?");
}
这个新方法名:
1. 清楚这个方法是测试什么:testPeel 表明他是Banana.peel() 的方法。
2. 清楚知道测试通过:通过方法的返回,makesTheBananaEdible表明香蕉是水果一类的。
条件测试
假设我们想要为人们提供香蕉,使用offer()函数:
// Banana/Offer.swift
/** Given a banana, returns a string that can be used to offer someone the banana. */
public func offer(banana: Banana) -> String {
if banana.isEdible {
return "Hey, want a banana?"
} else {
return "Hey, want me to peel this banana for you?"
}
}
我们的代码做了两件事:
1. 也许售卖的香蕉已经是剥好皮的…
2. …或者售卖的是没有剥皮的
让我对这两个事件进行测试:
// BananaTests/OfferTests.swift
class OfferTests: XCTestCase {
func testOffer_whenTheBananaIsPeeled_offersTheBanana() {
// Arrange: Create a banana and peel it.
let banana = Banana()
banana.peel()
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want a banana?")
}
func testOffer_whenTheBananaIsntPeeled_offersToPeelTheBanana() {
// Arrange: Create a banana.
let banana = Banana()
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want me to peel this banana for you?")
}
}
//oc
-(void)testOffer_whenTheBananaIsPeeled_offersTheBanana {
// Arrange: Create a banana and peel it.
[self.banana peel];
// Act: Create the string used to offer the banana.
NSString *message = [self.banana offer:(self.banana)];
// Assert: Verify it's the right string.
XCTAssertEqual(message, @"Hey, want a banana?");
}
-(void)testOffer_whenTheBananaIsntPeeled_offersToPeelTheBanana {
// Arrange: Create a banana and peel it.
// Act: Create the string used to offer the banana.
NSString *message = [self.banana offer:(self.banana)];
// Assert: Verify it's the right string.
XCTAssertEqual(message, @"Hey,want me to peel this banana for you?");
}
我们的测试名字明确的指示出在那种条件下的测试通过:在whenTheBananaIsPeeled
条件下offer
的应该是offersTheBanana
, 和即使这个香蕉没有剥皮?我们还是要去做一些测试。
在我们的测试代码中每一个测试都有`if
去声明。我们去写一些测试的时候这是一个很好模式:
它确保每种设定的情况都有测试。如果一种情况下测试没有工作;或者测试条件需要改变,我们能够快速的找到在代码中位置并查看它。
XCTestCase.setUp()更短的 “Arrange”步骤
我们的OfferTests和”Arrange”两个包含着相似的代码:两者都创建了一个香蕉。我们应该讲代码移动到一个单独的地方,为什呢?
就像这样,如果我们改变了Banana类的初始化,我们必须去改变每一个测试类的创建的banana;
我们的测试方法将非常的短–这是一件非常好的事情,如果(只有)使得测试类变得更容易阅读。
接下啦,让我们移动Banana 的初始化 到 XCTestCase.setUp() 方法,他在每一测试方法之前调用。
// OfferTests.swift
class OfferTests: XCTestCase {
+ var banana: Banana!
+
+ override func setUp() {
+ super.setUp()
+ banana = Banana()
+ }
+
func testOffer_whenTheBananaIsPeeled_offersTheBanana() {
- // Arrange: Create a banana and peel it.
- let banana = Banana()
+ // Arrange: Peel the banana.
banana.peel()
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want a banana?")
}
func testOffer_whenTheBananaIsntPeeled_offersToPeelTheBanana() {
- // Arrange: Create a banana.
- let banana = Banana()
-
// Act: Create the string used to offer the banana.
let message = offer(banana)
// Assert: Verify it's the right string.
XCTAssertEqual(message, "Hey, want me to peel this banana for you?")
}
}
共享 “Arrange”代码 贯穿到多个测试中
如果你发现你在多个测试类中使用了相似的”arrange” 步骤,你也许想去定义一个辅助函数在你的测试target中:
// BananaTests/BananaHelpers.swift
internal func createNewPeeledBanana() -> Banana {
let banana = Banana()
banana.peel()
return banana
}
你的帮助类中的函数:函数不能有子类,或者能保留任何的状态,子类和多状态可以保证你的测试类容易读。