1. Problem description

Mars rovers move around an area on command and collect Mars data from that location.

The commands the rovers receive fall into four categories:

  1. Exploration area information: Tell the rover how long (X) and wide (Y) the entire area is;
  2. Initialization information: the landing location (x, y) and orientation (N, S, E, W) of the rover;
  3. Move command: The rover can move forward (M);
  4. Steering instructions: The rover can turn 90 degrees left (L) or 90 degrees right (R).

Because of the distance between Earth and Mars, the commands have to be sent in batches, and the rovers then report back with coordinates and orientation.

Ii. Task disassembly (Requirements analysis)

(1) Input and output are required for each unit. If you are unsure of the inputs and outputs, the best thing you can do now is continue to understand the requirements or discuss them with your product manager classmates. (2) When implementing, follow the principle of right-to-left. Until the requirements are fully realized.

Three, red, green, refactoring

  • Red: Write test cases to ensure that the tests fail
  • Green: Implement functions at the lowest cost to ensure test cases pass.
  • Refactoring: Refactoring should be done throughout the project. If more than 20 lines of code are not refactoring, it will become difficult to maintain in the future.
  • Repeat the steps until the requirement is completed.

Below we will take the [turn] function as an example to demonstrate.

Typedef enum DIREDRTION_N = 0, // DIREDRTION_E, // DIREDRTION_S, // DIREDRTION_W, // DIREDRTION_UNKNOW // unknown direction} DIREDRTION;Copy the code
- (DIREDRTION)turn:(NSString *)cmd curDirection:(DIREDRTION)curDirection{
		return DIREDRTION_UNKNOW;
}
Copy the code

3.1 the red

Create a new test case. The test case is what we already know, what we expect the function to do. In the abstract, we know exactly what the output will be.

// Input: current direction (turn south), turn command (turn right) // Output: - (void)testTurnCurDirection{MarsRover *rover = [[MarsRover alloc] init]; { DIREDRTION direction = [rover turn:@"R" curDirection:DIREDRTION_S]; XCTAssertTrue(direction == DIREDRTION_W,"turn:curDirection failed to verify "); }}Copy the code

Run test, test fails. Red!

3.2 the green

Now we will satisfy this case with the minimum cost

- (DIREDRTION)turn:(NSString *)cmd curDirection:(DIREDRTION)curDirection{ if ([cmd isEqualToString:@"R"]) { if (curDirection == DIREDRTION_S) { return DIREDRTION_W; }} return DIREDRTION_UNKNOW; }Copy the code

Run the test and it passes. Green!

3.3 refactoring

In the process of writing code, we often smell something bad. If you smell something, refactor it immediately. Not yet. Let’s move on…

3.4 Repeat the process

We keep adding test cases, and we keep doing the code functionality with minimal cost to pass the tests.

-(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; }Copy the code
-(void)testTurn{ MarsRover *rover = [[MarsRover alloc] init]; { DIREDRTION direction = [rover turn:@"L" curDirection:DIREDRTION_E]; XCTAssertTrue(direction == DIREDRTION_N,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"L" curDirection:DIREDRTION_S]; XCTAssertTrue(direction == DIREDRTION_E,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"L" curDirection:DIREDRTION_W]; XCTAssertTrue(direction == DIREDRTION_S,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"L" curDirection:DIREDRTION_N]; XCTAssertTrue(direction == DIREDRTION_W,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"R" curDirection:DIREDRTION_E]; XCTAssertTrue(direction == DIREDRTION_S,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"R" curDirection:DIREDRTION_S]; XCTAssertTrue(direction == DIREDRTION_W,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"R" curDirection:DIREDRTION_W]; XCTAssertTrue(direction == DIREDRTION_N,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"R" curDirection:DIREDRTION_N]; XCTAssertTrue(direction == DIREDRTION_E,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"r" curDirection:DIREDRTION_N]; XCTAssertTrue(direction == DIREDRTION_E,"turn:curDirection failed to verify "); } { DIREDRTION direction = [rover turn:@"x" curDirection:DIREDRTION_N]; XCTAssertTrue(direction == DIREDRTION_UNKNOW,"turn:curDirection failed to verify "); }}Copy the code

For those of you who are careful, you may have noticed that we enclose each case in curly braces. The advantage of this is that we can copy and paste each case without having to worry about maintaining variable names.

3.5 refactoring

So far, we’ve written the “turn” function, but we can smell some bad stuff in the code, both the test code and the code under test. In fact, they all need to be refactored. Here we take the code under test as an example and see how unit tests can help us refactor code.

DIREDRTION_N = 0, // DIREDRTION_E = 1, // DIREDRTION_S = 2, // DIREDRTION_W = 3

Through observation, it is easy to find the following pattern:

Left: Final direction = (current direction + 3) % 4 Right: Final direction = (current direction + 5) % 4

Now we can refactor the function aggressively, because if our changes are incorrect, the unit test case will fail. After refactoring, the function looks like this:

- (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; }Copy the code

Run the unit tests and they pass.

In fact, it’s not just the business code that needs to be refactored, but our test code, too

## Talk about the benefits of TDD again

1. Don’t “think” : In the process of developing with TDD, many functions emerge naturally as the number of test cases increases, without much “thinking”, but we still think this process is intelligent.

2. Deliver the code with confidence: With the guarantee of the test code, we have laid the foundation for the whole test. Although we cannot guarantee the quality of the product, we can say that we have maintained a high standard at the code level.

3. The TDD approach produces code that is granular in function and easy to test, modify, and refactor. (Many people always complain about how difficult it is to write unit tests for existing projects, but in fact these difficulties are due to the design of the code itself. From the start, we need to design code that is easy to test and refactor, and of course, writing unit tests for a project is a good thing whenever you start).

4. Guarantee of reconstruction. In the general development method, especially before the release, many students are afraid to modify the code, because after modifying the code, it is easy to cause a chain reaction, a series of unexpected results. But if we have the assurance of unit tests, we can refactor our code with great confidence, as long as we pass the tests (and of course we guarantee the quality of the cases) we can be confident that our code is still at a high level of quality.

Five, the reference

I put the entire project code on my Github with a simple GUI interface called UnitTestIniOS

Vi. Video demonstration

Xiaozhuanlan.com/topic/39264…