User Story(用户故事)
作为一位酒店大堂服务员,我想在大堂的城市时钟不准时,用设置自己手机时间的方法,自动统一调整这些城市时钟的时间,来避免逐一根据时差调整这些时钟的繁琐工作。
写测试用例
HotelWorldClocksTest.java
public class HotelWorldClocksTest {
private HotelWorldClockSystem hotelWorldClockSystem;
private PhoneClock phoneClock;
@Before
public void init() {
this.hotelWorldClockSystem = new HotelWorldClockSystem();
this.phoneClock = new PhoneClock(8);
}
@Test
public void the_time_of_clock_London_should_be_1_after_the_phone_clock_is_set_to_9_Beijing_time() {
// Arrange
CityClock londonClock = new CityClock(0);
hotelWorldClockSystem.attach("london", londonClock);
// Act
phoneClock.setHotelWorldClockSystem(hotelWorldClockSystem);
phoneClock.setTime(9);
// Assert
assertEquals(1, londonClock.getLocalTime());
}
@Test
public void the_time_of_clock_NewYork_should_be_20_after_the_phone_clock_is_set_to_9_Beijing_time() {
// Arrange
CityClock newYorkClock = new CityClock(-5);
hotelWorldClockSystem.attach("newYork", newYorkClock);
// Act
phoneClock.setHotelWorldClockSystem(hotelWorldClockSystem);
phoneClock.setTime(9);
// Assert
assertEquals(20, newYorkClock.getLocalTime());
}
@Test
public void the_time_of_clock_London_and_NewYork_should_be_1_and_20_respectively_after_the_phone_clock_is_set_to_9_Beijing_time() {
// Arrange
CityClock newYorkClock = new CityClock(-5);
CityClock londonClock = new CityClock(0);
hotelWorldClockSystem.attach("london", londonClock);
hotelWorldClockSystem.attach("newYork", newYorkClock);
// Act
phoneClock.setHotelWorldClockSystem(hotelWorldClockSystem);
phoneClock.setTime(9);
// Assert
assertEquals(1, londonClock.getLocalTime());
assertEquals(20, newYorkClock.getLocalTime());
}
@Test
public void the_time_of_the_phone_clock_should_be_set_correctly_after_its_setTime_method_is_invoked() {
// Arrange
// Act
phoneClock.setTime(9);
// Assert
assertEquals(9, phoneClock.getLocalTime());
}
}
生成并调试代码
HotelWorldClockSystem.java
public class HotelWorldClockSystem {
private Map<String, Clock> cityClocks = new HashMap<String, Clock>();
public void attach(String name, Clock londonClock) {
cityClocks.put(name, londonClock);
}
public void updateCityClockWithUtcZeroTime(int utcZeroTime) {
for (Clock clock : cityClocks.values()) {
clock.setLocalTime((utcZeroTime + clock.utcOffSet + 24) % 24);
}
}
}
Clock.java
public class Clock {
protected int utcOffSet;
protected int localTime;
public Clock(int utcOffSet) {
this.utcOffSet = utcOffSet;
}
public int getLocalTime() {
return this.localTime;
}
public void setLocalTime(int localTime) {
this.localTime = localTime;
}
}
PhoneClock.java
public class PhoneClock extends Clock {
private HotelWorldClockSystem hotelWorldClockSystem;
public PhoneClock(int utcOffSet) {
super(utcOffSet);
}
public void setLocalTime(int localTime) {
super.localTime = localTime;
if (this.hotelWorldClockSystem != null)
this.hotelWorldClockSystem
.updateCityClockWithUtcZeroTime((localTime
- super.utcOffSet + 24) % 24);
}
public void setHotelWorldClockSystem(
HotelWorldClockSystem hotelWorldClockSystem) {
this.hotelWorldClockSystem = hotelWorldClockSystem;
}
}
最终的类图
测试驱动开发的过程
-
1.”尽量简单”的标准选取第一个测试
- a)用User Story的格式描述产品特性。用‘产品特性’这样基于沟通的字眼替换带有强制、专制和持久色彩的‘需求’一词
- b)从User Story中选取一种简单情况编写第一个测试函数,测试函数名写的长一点作为易于阅读的文档,在一个测试中先写Assert部分,然后推导出Act和Arrange这两部分代码
- c)根据测试函数里的符合自己意图的代码驱动生成生产代码,只写能让编译通过的、尽量少的代码
- d)列出TODO清单,重构是测试通过 2.”尽量有意思”的标准选取下一个测试
- a)写好一个测试的Assert部分后,在以此推导出Act和Arrange部分的意图代码
- b)循着意图代码中红色的编译错误的指引来编写最少的代码消除错误
- c)调试,用最少的代码使测试通过
- d)足够自信时,步子可以迈的大一点 3.迭代的测试
- a)完成所有TODO列表之后,重新审阅代码找出还未测到的情况和遗漏的代码“腐臭”
- b)将子类构造器中出现的重复成员变量上移到父类中,以消除重复
- c)将那些因被替换而不再被调用的代码删掉
- d)将子类中签名相同但实现不同的方法,提取一个抽象方法放到父类中