3.1火灾警报软件的例子
处理单元连接到多个火灾传感器,并连续轮询它们以获取异常读数。发现火灾时,警报声响起。如果火势开始蔓延并且触发了另一个检测器,则会自动呼叫消防队。具体需求如下:
- 如果所有传感器均报告无异常,则表明系统正常,无需采取任何措施。
- 如果触发了一个传感器,则会发出警报声(但由于吸烟者粗心大意,无法抗拒香烟,这可能是误报)。
- 如果触发了多个传感器,则会呼叫消防队(因为火势蔓延到一个以上的房间)。
//实现监视的主类
public class FireEarlyWarning {
private int sensorsDetectingFire = 0;
//方法称为每秒钟由传感器软件
public void feedData(int triggeredFireSensors)
{
sensorsDetectingFire = triggeredFireSensors;
}
//状态报告获取方法
public WarningStatus getCurrentStatus()
{
return new WarningStatus(sensorsDetectingFire > 0, sensorsDetectingFire > 1);
}
}
//状态报告内容(状态类)
public class WarningStatus {
private final boolean alarmActive;
private final boolean fireDepartmentNotified;
public WarningStatus(boolean alarmActive,boolean fireDepartmentNotified)
{
this.alarmActive = alarmActive;
this.fireDepartmentNotified = fireDepartmentNotified;
}
//若为true,警报就会响起。
public boolean isAlarmActive() {
return alarmActive;
}
//若为true,就呼叫消防队。
public boolean isFireDepartmentNotified() {
return fireDepartmentNotified;
}
}
3.5 火警系统的全部Spock测试
class FireSensorSpec extends spock.lang.Specification{
//清楚地解释这个测试的作用
def "如果所有传感器都不活动,那么一切都没问题"() {
given: "所有的火灾传感器都关闭了"
FireEarlyWarning fireEarlyWarning = new FireEarlyWarning()
int triggeredSensors = 0
when: "我们询问消防状况"
fireEarlyWarning.feedData(triggeredSensors)
WarningStatus status = fireEarlyWarning.getCurrentStatus()
then: "不应触发警报/通知"
!status.alarmActive
!status.fireDepartmentNotified
}
def "如果一个传感器是启动的,警报应该发出预防措施"() {
given: "只有一个火情传感器是启动的"
FireEarlyWarning fireEarlyWarning = new FireEarlyWarning()
int triggeredSensors = 1
when: "我们询问消防状况"
fireEarlyWarning.feedData(triggeredSensors)
WarningStatus status = fireEarlyWarning.getCurrentStatus()
then: "只应触发警报"
status.alarmActive
!status.fireDepartmentNotified
}
def "如果不止一个传感器处于启动状态,且我们着火了"() {
given: "两个火灾传感器已经启动"
FireEarlyWarning fireEarlyWarning = new FireEarlyWarning()
int triggeredSensors = 2
when: "我们询问消防状况"
fireEarlyWarning.feedData(triggeredSensors)
WarningStatus status = fireEarlyWarning.getCurrentStatus()
then: "警报被触发,消防部门被通知"
status.alarmActive
status.fireDepartmentNotified
}
}
3.7核反应堆测试
接下来上升到更复杂的场景—核反应堆的测试,涉及到多个输入集。
具体功能如下:
class NuclearReactorSpec extends spock.lang.Specification{
//测试描述需通俗易懂,人类可读
def "全面测试所有核反应场景"() {
given: "一个核反应和传感器的数据"
NuclearReactorMonitor nuclearReactorMonitor =new NuclearReactorMonitor()
//输入
when: "当我们检查传感器数据"
nuclearReactorMonitor.feedFireSensorData(fireSensors)
nuclearReactorMonitor.feedRadiationSensorData(radiation)
nuclearReactorMonitor.feedPressureInBar(pressure)
NuclearReactorStatus status = nuclearReactorMonitor.getCurrentStatus()
//输出
then: "我们根据安全准则行动"
status.alarmActive == alarm
status.shutDownNeeded == shutDown
status.evacuationMinutes == evacuation
//参数的来源
where: "可能的核事故有:"
//输入和输出的定义
pressure | fireSensors | radiation || alarm | shutDown | evacuation
//所有场景的表格表示
150 | 0 | [] || false | false | -1
150 | 1 | [] || true | false | -1
150 | 3 | [] || true | true | -1
150 | 0| [110.4f ,0.3f, 0.0f] || true | true | 1
150 | 0| [45.3f ,10.3f, 47.7f]|| false | false | -1
155 | 0| [0.0f ,0.0f, 0.0f] || true | false | -1
170 | 0| [0.0f ,0.0f, 0.0f] || true | true | 3
180 | 0| [110.4f ,0.3f, 0.0f] || true | true | 1
500 | 0| [110.4f ,300f, 0.0f] || true | true | 1
30 | 0|[110.4f ,1000f, 0.0f] || true | true | 1
155 | 4| [0.0f ,0.0f, 0.0f] || true | true | -1
170 | 1| [45.3f ,10.3f, 47.7f]|| true | true | 3
}
}
3.9 在Spock中使用Stubbing
class CoolantSensorSpec extends spock.lang.Specification{
def "如果当前温差在限制范围内,则一切正常"() {
given: "读取的温度在限制范围内"
//预设温度
TemperatureReadings prev = new TemperatureReadings(sensor1Data:20, sensor2Data:40,sensor3Data:80)
TemperatureReadings current = new TemperatureReadings(sensor1Data:30, sensor2Data:45,sensor3Data:73);
//dummy接口实现
TemperatureReader reader = Stub(TemperatureReader)
//dummy接口返回预设温度
reader.getCurrentReadings() >>> [prev, current]
//被测试的类注入dummy接口
TemperatureMonitor monitor = new TemperatureMonitor(reader)
//被测试的类调用dummy接口
when: "我们查询现在的温度"
monitor.readSensor()
monitor.readSensor()
//两次调用之后的结果
then: "一切正常"
monitor.isTemperatureNormal()
}
def "如果当前温差大于20度,则警报响起"() {
given: "读取的温度不在限制范围内"
TemperatureReadings prev = new TemperatureReadings(sensor1Data:20, sensor2Data:40,sensor3Data:80)
TemperatureReadings current = new TemperatureReadings(sensor1Data:30,
sensor2Data:10,sensor3Data:73);
//Stub的调用
TemperatureReader reader = Stub(TemperatureReader)
reader.getCurrentReadings() >>> [prev,current]
TemperatureMonitor monitor = new TemperatureMonitor(reader)
when: "我们查询现在的温度"
monitor.readSensor()
monitor.readSensor()
then: "警报应该响起"
!monitor.isTemperatureNormal()
}
}
reader.getCurrentReadings() >>> [prev, current]
第一次调用getCurrentReadings方法返回prev,第二次调用返回current。
3.11使用Spock的mocking和stubbing
def "如果当前温差大于20度,则警报响起"() {
given: "读取的温度不在限制范围内"
TemperatureReadings prev = new TemperatureReadings(sensor1Data:20, sensor2Data:40,sensor3Data:80)
TemperatureReadings current = new TemperatureReadings(sensor1Data:30,
sensor2Data:10,sensor3Data:73);
//为接口创建Stub
TemperatureReader reader = Stub(TemperatureReader)
reader.getCurrentReadings() >>> [prev, current]
//为一个具体的类创建mock
ReactorControl control = Mock(ReactorControl)
//被测试的类注入mock和stub
ImprovedTemperatureMonitor monitor = new
ImprovedTemperatureMonitor(reader,control)
//后续场景调用mock方法
when: "我们查询现在的温度"
monitor.readSensor()
monitor.readSensor()
then: "警报应该响起"
//验证mock的调用
0 * control.shutdownReactor()
1 * control.activateAlarm()
}
def "如果当前温差大于50度,则核反应应该停止"(){
given: "读取的温度不在限制范围内"
TemperatureReadings prev = new TemperatureReadings(sensor1Data:20,
sensor2Data:40,sensor3Data:80)
TemperatureReadings current = new TemperatureReadings(sensor1Data:30,
sensor2Data:10,sensor3Data:160);
TemperatureReader reader = Stub(TemperatureReader)
reader.getCurrentReadings() >>> [prev, current]
ReactorControl control = Mock(ReactorControl)
ImprovedTemperatureMonitor monitor = new
ImprovedTemperatureMonitor(reader,control)
when: "我们查询现在的温度"
monitor.readSensor()
monitor.readSensor()
then: "警报应该响起,并且核反应应该停止"
1 * control.shutdownReactor()
1 * control.activateAlarm()
}
0 * control.shutdownReactor()
1 * control.activateAlarm()
表示control的shutdownReactor方法没有被调用,activateAlarm被调用一次。