前言
TDD技术主要分为两个模块需求拆分技术和TDD流程。
需求分析技术是将我们的一个完整的需求拆解成功能单元的技术TDD流程解决的问题是如何使用TDD流程完成功能单元的代码编写。
问题描述
火星漫步者在某块区域中根据指令进行移动,然后采集相应位置的火星数据。
火星车收到的指令分为四类:
探索区域信息:告知火星车,整片区域的长度(X)和宽度(Y)有多大;
初始化信息:火星车的降落地点(x, y)和朝向(N, S, E, W)信息;
移动指令:火星车可以前进(M);
转向指令:火星车可以左转 90 度(L)或右转 90 度(R)。
由于地球和火星之间的距离很远,指令必须批量发送,火星车执行完整批指令之后,再回报自己所在的位置坐标和朝向。
一、需求分析技术
任务拆解(1)每个单元,都需要明确输入和输出。如果你不能确定这些输入和输出,那么你现在要做的事情就是继续理解需求,或者是找产品经理同学讨论。 (2)进行实现的时候,要遵循从右往左的原则。直到需求完全得到实现。
二、TDD流程
绿:以最低的成本实现函数,确保测试用例通过;
重构:使代码更加简洁。
重复上述步骤,直到完成需求。
下面我们就将以【转向】这个函数为例进行示范。转向输入输出分析。
typedef enum DIREDRTION {
DIREDRTION_N = 0, // 北
DIREDRTION_E, // 东
DIREDRTION_S, // 南
DIREDRTION_W, // 西
DIREDRTION_UNKNOW // 未知方向
} DIREDRTION;
- (DIREDRTION)turn:(NSString *)cmd curDirection:(DIREDRTION)curDirection{
return DIREDRTION_UNKNOW;
}
2.1 红
新建一个测试用例,测试用例是我们已知的事实,是我们对函数行为的预期。抽象地说就是:我们明确地知道输入将得到一个怎样的输出。
// 转向函数
// 输入:当前方向(向南)、转向命令(右转)
// 输出:转向后方向(向西)
- (void)testTurnCurDirection{
MarsRover *rover = [[MarsRover alloc] init];
{
DIREDRTION direction = [rover turn:@"R" curDirection:DIREDRTION_S];
XCTAssertTrue(direction == DIREDRTION_W,"turn:curDirection函数验证失败");
}
}
运行测试,测试不通过。红!
2.2 绿
现在我们将以最小的成本去满足这个这个case,这样可以避免我们的程序的过度设计。
// 转向函数
// 输入:当前方向(向南)、转向命令(右转)
// 输出:转向后方向(向西)
- (void)testTurnCurDirection{
MarsRover *rover = [[MarsRover alloc] init];
{
DIREDRTION direction = [rover turn:@"R" curDirection:DIREDRTION_S];
XCTAssertTrue(direction == DIREDRTION_W,"turn:curDirection函数验证失败");
}
}
运行测试,通过。绿!
2.3重构
在写代码的过程中我们往往会闻到一些代码的坏味道,如果闻到了,请立即重构。暂时没闻到,咱们接着进行...
2.4 重复上述过程
我们不停的增加测试用例,并且不断的以最小代价完成代码功能以通过测试。
-(DIREDRTION)turn:(NSString *)cmd curDirection:(DIREDRTION)curDirection{
cmd = [cmd uppercaseString];
if (!([cmd isEqualToString:@"L"] || [cmd isEqualToString:@"R"])) {
return -1;
}
if ([cmd isEqualToString:@"L"]) {
if (curDirection == DIREDRTION_S) {
return DIREDRTION_E;
}else if (curDirection == DIREDRTION_N) {
return DIREDRTION_W;
}else if (curDirection == DIREDRTION_E) {
return DIREDRTION_N;
}else{
return DIREDRTION_S;
}
}
if ([cmd isEqualToString:@"R"]) {
if (curDirection == DIREDRTION_S) {
return DIREDRTION_W;
}else if (curDirection == DIREDRTION_N) {
return DIREDRTION_E;
}else if (curDirection == DIREDRTION_E) {
return DIREDRTION_S;
}else{
return DIREDRTION_N;
}
}
return -1;
}
- (void)testTurn{
MarsRover *rover = [[MarsRover alloc] init];
{
DIREDRTION direction = [rover turn:@"L" curDirection:DIREDRTION_E];
XCTAssertTrue(direction == DIREDRTION_N,"turn:curDirection函数验证失败");
}
{
DIREDRTION direction = [rover turn:@"L" curDirection:DIREDRTION_S];
XCTAssertTrue(direction == DIREDRTION_E,"turn:curDirection函数验证失败");
}
{
DIREDRTION direction = [rover turn:@"L" curDirection:DIREDRTION_W];
XCTAssertTrue(direction == DIREDRTION_S,"turn:curDirection函数验证失败");
}
}
2.5 重构
目前为止,我们已经完成了“转向”函数的编写,但是我们似乎可以闻到一些代码的坏味道,无论是测试代码还是被测代码。事实上他们都是需要被重构的,此处我们拿被测代码作为演示,并且体会单元测试是如何帮助我们进行代码重构的。
DIREDRTION_N = 0, // 北 DIREDRTION_E = 1, // 东 DIREDRTION_S = 2, // 南 DIREDRTION_W = 3 // 西
经过观察,我们很容容易发现如下规律:
左转:最终方向 = (当前方向 + 3) % 4 右转:最终方向 = (当前方向 + 5) % 4
现在我们可以大刀阔斧的重构函数,因为如果我们的修改是不正确的,单元测试的case必然不会通过。重构之后的函数如下所示:
- (DIREDRTION)turn:(NSString *)cmd curDirection:(DIREDRTION)curDirection{
cmd = [cmd uppercaseString];
if (!([cmd isEqualToString:@"L"] || [cmd isEqualToString:@"R"])) {
return DIREDRTION_UNKNOW;
}
if ([cmd isEqualToString:@"L"]) {
DIREDRTION direction = (curDirection + 3) % 4;
return direction;
}
if ([cmd isEqualToString:@"R"]) {
DIREDRTION direction = (curDirection + 5) % 4;
return direction;
}
return DIREDRTION_UNKNOW;
}
运行单元测试,测试通过。
事实上不仅仅是业务代码需要重构,我们的测试代码也需要重新构。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理