TDD测试驱动开发(结合需求一步步实现)
Test-driven development
主要是通过不断循环以下两个小步骤将需求一步步实现:在写新代码之前先写好执行失败的测试用例再写最少的代码实现使用前一步的新用例执行成功
实际上是这种开发循环[test, code, refactor, (repeat)
TDD 三大优点:实现的代码都是为了实现需求逻辑的写出的代码都是经过测试的,没有BUG的测试方法可以作为文档,通过测试代码很容易理解代码解决的问题
通过下面的需求实现过程说明TDD的开发过程
航班添加乘客业务逻辑:经济航班可以加任何类型乘客,商业航班只加VIP乘客
移除乘客逻辑:可以任意移除普通乘客,不允许移除VIP乘客
第一版设计
Fight类import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Flight { private String id; private List passengers = new ArrayList(); private String flightType; public Flight(String id, String flightType) { this.id = id; this.flightType = flightType; } public String getId() { return id; } public List getPassengersList() { return Collections.unmodifiableList(passengers); } public String getFlightType() { return flightType; } public boolean addPassenger(Passenger passenger) { switch (flightType) { case "Economy": return passengers.add(passenger); case "Business": if (passenger.isVip()) { return passengers.add(passenger); } return false; default: throw new RuntimeException("Unknown type: " + flightType); } } public boolean removePassenger(Passenger passenger) { switch (flightType) { case "Economy": if (!passenger.isVip()) { return passengers.remove(passenger); } return false; case "Business": return false; default: throw new RuntimeException("Unknown type: " + flightType); } } }
Passenger类public class Passenger { private String name; private boolean vip; public Passenger(String name, boolean vip) { this.name = name; this.vip = vip; } public String getName() { return name; } public boolean isVip() { return vip; } }
通过Airport的main方法测试public class Airport { public static void main(String[] args) { Flight economyFlight = new Flight("1", "Economy"); Flight businessFlight = new Flight("2", "Business"); Passenger james = new Passenger("James", true); Passenger mike = new Passenger("Mike", false); businessFlight.addPassenger(james); businessFlight.removePassenger(james); businessFlight.addPassenger(mike); economyFlight.addPassenger(mike); System.out.println("Business flight passengers list:"); for (Passenger passenger : businessFlight.getPassengersList()) { System.out.println(passenger.getName()); } System.out.println("Economy flight passengers list:"); for (Passenger passenger : economyFlight.getPassengersList()) { System.out.println(passenger.getName()); } } }
改成TDD方式测试
引入Junit5 org.junit.jupiter junit-jupiter-api 5.6.0 test org.junit.jupiter junit-jupiter-engine 5.6.0 test
AirportTestimport org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class AirportTest { @DisplayName("Given there is an economy flight") @Nested class EconomyFlightTest { private Flight economyFlight; @BeforeEach void setUp() { economyFlight = new Flight("1", "Economy"); } @Test public void testEconomyFlightRegularPassenger() { Passenger mike = new Passenger("Mike", false); assertEquals("1", economyFlight.getId()); assertEquals(true, economyFlight.addPassenger(mike)); assertEquals(1, economyFlight.getPassengersList().size()); assertEquals("Mike", economyFlight.getPassengersList().get(0).getName()); assertEquals(true, economyFlight.removePassenger(mike)); assertEquals(0, economyFlight.getPassengersList().size()); } @Test public void testEconomyFlightVipPassenger() { Passenger james = new Passenger("James", true); assertEquals("1", economyFlight.getId()); assertEquals(true, economyFlight.addPassenger(james)); assertEquals(1, economyFlight.getPassengersList().size()); assertEquals("James", economyFlight.getPassengersList().get(0).getName()); assertEquals(false, economyFlight.removePassenger(james)); assertEquals(1, economyFlight.getPassengersList().size()); } } @DisplayName("Given there is a business flight") @Nested class BusinessFlightTest { private Flight businessFlight; @BeforeEach void setUp() { businessFlight = new Flight("2", "Business"); } @Test public void testBusinessFlightRegularPassenger() { Passenger mike = new Passenger("Mike", false); assertEquals(false, businessFlight.addPassenger(mike)); assertEquals(0, businessFlight.getPassengersList().size()); assertEquals(false, businessFlight.removePassenger(mike)); assertEquals(0, businessFlight.getPassengersList().size()); } @Test public void testBusinessFlightVipPassenger() { Passenger james = new Passenger("James", true); assertEquals(true, businessFlight.addPassenger(james)); assertEquals(1, businessFlight.getPassengersList().size()); assertEquals(false, businessFlight.removePassenger(james)); assertEquals(1, businessFlight.getPassengersList().size()); } } }
执行测试用例后发现Airport类没使用到,可以去掉。
Fight覆盖率小于100%,发现getFlightType没使用到,switch块的default case没覆盖,考虑重构移除未使用的代码。
开始着手重构事宜:
可以通过多态代替switch条件,利用多态特性(运行时而非编译时才确定实际调用具体的方法),使得开发的代码符合**开闭原则,**避免每次增加航班类型都要修改存在的类。
第二版设计
Flightimport java.util.ArrayList; import java.util.Collections; import java.util.List; public abstract class Flight { private String id; List passengers = new ArrayList(); public Flight(String id) { this.id = id; } public String getId() { return id; } public List getPassengers() { return Collections.unmodifiableList(passengers); } public abstract boolean addPassenger(Passenger passenger); public abstract boolean removePassenger(Passenger passenger); }
EconomyFlightpublic class EconomyFlight extends Flight { public EconomyFlight(String id) { super(id); } @Override public boolean addPassenger(Passenger passenger) { return passengers.add(passenger); } @Override public boolean removePassenger(Passenger passenger) { if (!passenger.isVip()) { return passengers.remove(passenger); } return false; } }
BusinessFlightpublic class BusinessFlight extends Flight { public BusinessFlight(String id) { super(id); } @Override public boolean addPassenger(Passenger passenger) { if (passenger.isVip()) { return passengers.add(passenger); } return false; } @Override public boolean removePassenger(Passenger passenger) { return false; } }
AirportTestimport org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class AirportTest { @DisplayName("Given there is an economy flight") @Nested class EconomyFlightTest { private Flight economyFlight; @BeforeEach void setUp() { economyFlight = new EconomyFlight("1"); } @Test public void testEconomyFlightRegularPassenger() { Passenger mike = new Passenger("Mike", false); assertEquals("1", economyFlight.getId()); assertEquals(true, economyFlight.addPassenger(mike)); assertEquals(1, economyFlight.getPassengers().size()); assertEquals("Mike", economyFlight.getPassengers().get(0).getName()); assertEquals(true, economyFlight.removePassenger(mike)); assertEquals(0, economyFlight.getPassengers().size()); } @Test public void testEconomyFlightVipPassenger() { Passenger james = new Passenger("James", true); assertEquals("1", economyFlight.getId()); assertEquals(true, economyFlight.addPassenger(james)); assertEquals(1, economyFlight.getPassengers().size()); assertEquals("James", economyFlight.getPassengers().get(0).getName()); assertEquals(false, economyFlight.removePassenger(james)); assertEquals(1, economyFlight.getPassengers().size()); } } @DisplayName("Given there is a business flight") @Nested class BusinessFlightTest { private Flight businessFlight; @BeforeEach void setUp() { businessFlight = new BusinessFlight("2"); } @Test public void testBusinessFlightRegularPassenger() { Passenger mike = new Passenger("Mike", false); assertEquals(false, businessFlight.addPassenger(mike)); assertEquals(0, businessFlight.getPassengers().size()); assertEquals(false, businessFlight.removePassenger(mike)); assertEquals(0, businessFlight.getPassengers().size()); } @Test public void testBusinessFlightVipPassenger() { Passenger james = new Passenger("James", true); assertEquals(true, businessFlight.addPassenger(james)); assertEquals(1, businessFlight.getPassengers().size()); assertEquals(false, businessFlight.removePassenger(james)); assertEquals(1, businessFlight.getPassengers().size()); } } }
来了新需求,增加一种新类型航班:只允许VIP乘客乘坐,其他类型乘客不允许乘坐;可以移除任何类型的乘客。
先实现简单的PremiumFlightpublic class PremiumFlight extends Flight { public PremiumFlight(String id) { super(id); } @Override public boolean addPassenger(Passenger passenger) { return false; } @Override public boolean removePassenger(Passenger passenger) { return false; } }
AirportTestimport org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertEquals; public class AirportTest { @DisplayName("Given there is an economy flight") @Nested class EconomyFlightTest { private Flight economyFlight; private Passenger mike; private Passenger james; @BeforeEach void setUp() { economyFlight = new EconomyFlight("1"); mike = new Passenger("Mike", false); james = new Passenger("James", true); } @Nested @DisplayName("When we have a regular passenger") class RegularPassenger { @Test @DisplayName("Then you can add and remove him from an economy flight") public void testEconomyFlightRegularPassenger() { assertAll("Verify all conditions for a regular passenger and an economy flight", () -> assertEquals("1", economyFlight.getId()), () -> assertEquals(true, economyFlight.addPassenger(mike)), () -> assertEquals(1, economyFlight.getPassengersList().size()), () -> assertEquals("Mike", economyFlight.getPassengersList().get(0).getName()), () -> assertEquals(true, economyFlight.removePassenger(mike)), () -> assertEquals(0, economyFlight.getPassengersList().size()) ); } } @Nested @DisplayName("When we have a VIP passenger") class VipPassenger { @Test @DisplayName("Then you can add him but cannot remove him from an economy flight") public void testEconomyFlightVipPassenger() { assertAll("Verify all conditions for a VIP passenger and an economy flight", () -> assertEquals("1", economyFlight.getId()), () -> assertEquals(true, economyFlight.addPassenger(james)), () -> assertEquals(1, economyFlight.getPassengersList().size()), () -> assertEquals("James", economyFlight.getPassengersList().get(0).getName()), () -> assertEquals(false, economyFlight.removePassenger(james)), () -> assertEquals(1, economyFlight.getPassengersList().size()) ); } } } @DisplayName("Given there is a business flight") @Nested class BusinessFlightTest { private Flight businessFlight; private Passenger mike; private Passenger james; @BeforeEach void setUp() { businessFlight = new BusinessFlight("2"); mike = new Passenger("Mike", false); james = new Passenger("James", true); } @Nested @DisplayName("When we have a regular passenger") class RegularPassenger { @Test @DisplayName("Then you cannot add or remove him from a business flight") public void testBusinessFlightRegularPassenger() { assertAll("Verify all conditions for a regular passenger and a business flight", () -> assertEquals(false, businessFlight.addPassenger(mike)), () -> assertEquals(0, businessFlight.getPassengersList().size()), () -> assertEquals(false, businessFlight.removePassenger(mike)), () -> assertEquals(0, businessFlight.getPassengersList().size()) ); } } @Nested @DisplayName("When we have a VIP passenger") class VipPassenger { @Test @DisplayName("Then you can add him but cannot remove him from a business flight") public void testBusinessFlightVipPassenger() { assertAll("Verify all conditions for a VIP passenger and a business flight", () -> assertEquals(true, businessFlight.addPassenger(james)), () -> assertEquals(1, businessFlight.getPassengersList().size()), () -> assertEquals(false, businessFlight.removePassenger(james)), () -> assertEquals(1, businessFlight.getPassengersList().size()) ); } } } @DisplayName("Given there is a premium flight") @Nested class PremiumFlightTest { private Flight premiumFlight; private Passenger mike; private Passenger james; @BeforeEach void setUp() { premiumFlight = new PremiumFlight("3"); mike = new Passenger("Mike", false); james = new Passenger("James", true); } @Nested @DisplayName("When we have a regular passenger") class RegularPassenger { @Test @DisplayName("Then you cannot add or remove him from a premium flight") public void testPremiumFlightRegularPassenger() { assertAll("Verify all conditions for a regular passenger and a premium flight", () -> assertEquals(false, premiumFlight.addPassenger(mike)), () -> assertEquals(0, premiumFlight.getPassengersList().size()), () -> assertEquals(false, premiumFlight.removePassenger(mike)), () -> assertEquals(0, premiumFlight.getPassengersList().size()) ); } } @Nested @DisplayName("When we have a VIP passenger") class VipPassenger { @Test @DisplayName("Then you can add and remove him from a premium flight") public void testPremiumFlightVipPassenger() { assertAll("Verify all conditions for a VIP passenger and a premium flight", () -> assertEquals(true, premiumFlight.addPassenger(james)), () -> assertEquals(1, premiumFlight.getPassengersList().size()), () -> assertEquals(true, premiumFlight.removePassenger(james)), () -> assertEquals(0, premiumFlight.getPassengersList().size()) ); } } } }
运行测试用例发现有个测试用例失败,需要实现逻辑使测试用到通过;另外发现普通类型的乘客的测试用例是通过的,说明简单的代码已经满足业务逻辑,只需要实现VIP类型乘客的逻辑。
实现VIP乘客的业务逻辑:public class PremiumFlight extends Flight { public PremiumFlight(String id) { super(id); } @Override public boolean addPassenger(Passenger passenger) { if (passenger.isVip()) { return passengers.add(passenger); } return false; } @Override public boolean removePassenger(Passenger passenger) { if (passenger.isVip()) { return passengers.remove(passenger); } return false; } }
偶然发现程序有个BUG,同一个乘客可以重复添加,这应该不允许的,需要增加这段逻辑。TDD方式先写测试用例,使用@RepeatedTest及RepetitionInfo实现重复执行。
AirportTestimport org.junit.jupiter.api.*; import java.util.ArrayList; import static org.junit.jupiter.api.Assertions.*; public class AirportTest { @DisplayName("Given there is an economy flight") @Nested class EconomyFlightTest { private Flight economyFlight; private Passenger mike; private Passenger james; @BeforeEach void setUp() { economyFlight = new EconomyFlight("1"); mike = new Passenger("Mike", false); james = new Passenger("James", true); } @Nested @DisplayName("When we have a regular passenger") class RegularPassenger { @Test @DisplayName("Then you can add and remove him from an economy flight") public void testEconomyFlightRegularPassenger() { assertAll("Verify all conditions for a regular passenger and an economy flight", () -> assertEquals("1", economyFlight.getId()), () -> assertEquals(true, economyFlight.addPassenger(mike)), () -> assertEquals(1, economyFlight.getPassengersSet().size()), () -> assertEquals("Mike", new ArrayList<>(economyFlight.getPassengersSet()).get(0).getName()), () -> assertEquals(true, economyFlight.removePassenger(mike)), () -> assertEquals(0, economyFlight.getPassengersSet().size()) ); } @DisplayName("Then you cannot add him to an economy flight more than once") @RepeatedTest(5) public void testEconomyFlightRegularPassengerAddedOnlyOnce(RepetitionInfo repetitionInfo) { for (int i = 0; i < repetitionInfo.getCurrentRepetition(); i++) { economyFlight.addPassenger(mike); } assertAll("Verify a regular passenger can be added to an economy flight only once", () -> assertEquals(1, economyFlight.getPassengersSet().size()), () -> assertTrue(economyFlight.getPassengersSet().contains(mike)), () -> assertTrue(new ArrayList<>(economyFlight.getPassengersSet()).get(0).getName().equals("Mike")) ); } } @Nested @DisplayName("When we have a VIP passenger") class VipPassenger { @Test @DisplayName("Then you can add him but cannot remove him from an economy flight") public void testEconomyFlightVipPassenger() { assertAll("Verify all conditions for a VIP passenger and an economy flight", () -> assertEquals("1", economyFlight.getId()), () -> assertEquals(true, economyFlight.addPassenger(james)), () -> assertEquals(1, economyFlight.getPassengersSet().size()), () -> assertEquals("James", new ArrayList<>(economyFlight.getPassengersSet()).get(0).getName()), () -> assertEquals(false, economyFlight.removePassenger(james)), () -> assertEquals(1, economyFlight.getPassengersSet().size()) ); } @DisplayName("Then you cannot add him to an economy flight more than once") @RepeatedTest(5) public void testEconomyFlightVipPassengerAddedOnlyOnce(RepetitionInfo repetitionInfo) { for (int i = 0; i < repetitionInfo.getCurrentRepetition(); i++) { economyFlight.addPassenger(james); } assertAll("Verify a VIP passenger can be added to an economy flight only once", () -> assertEquals(1, economyFlight.getPassengersSet().size()), () -> assertTrue(economyFlight.getPassengersSet().contains(james)), () -> assertTrue(new ArrayList<>(economyFlight.getPassengersSet()).get(0).getName().equals("James")) ); } } } @DisplayName("Given there is a business flight") @Nested class BusinessFlightTest { private Flight businessFlight; private Passenger mike; private Passenger james; @BeforeEach void setUp() { businessFlight = new BusinessFlight("2"); mike = new Passenger("Mike", false); james = new Passenger("James", true); } @Nested @DisplayName("When we have a regular passenger") class RegularPassenger { @Test @DisplayName("Then you cannot add or remove him from a business flight") public void testBusinessFlightRegularPassenger() { assertAll("Verify all conditions for a regular passenger and a business flight", () -> assertEquals(false, businessFlight.addPassenger(mike)), () -> assertEquals(0, businessFlight.getPassengersSet().size()), () -> assertEquals(false, businessFlight.removePassenger(mike)), () -> assertEquals(0, businessFlight.getPassengersSet().size()) ); } } @Nested @DisplayName("When we have a VIP passenger") class VipPassenger { @Test @DisplayName("Then you can add him but cannot remove him from a business flight") public void testBusinessFlightVipPassenger() { assertAll("Verify all conditions for a VIP passenger and a business flight", () -> assertEquals(true, businessFlight.addPassenger(james)), () -> assertEquals(1, businessFlight.getPassengersSet().size()), () -> assertEquals(false, businessFlight.removePassenger(james)), () -> assertEquals(1, businessFlight.getPassengersSet().size()) ); } @DisplayName("Then you cannot add him to a business flight more than once") @RepeatedTest(5) public void testBusinessFlightVipPassengerAddedOnlyOnce(RepetitionInfo repetitionInfo) { for (int i = 0; i < repetitionInfo.getCurrentRepetition(); i++) { businessFlight.addPassenger(james); } assertAll("Verify a VIP passenger can be added to a business flight only once", () -> assertEquals(1, businessFlight.getPassengersSet().size()), () -> assertTrue(businessFlight.getPassengersSet().contains(james)), () -> assertTrue(new ArrayList<>(businessFlight.getPassengersSet()).get(0).getName().equals("James")) ); } } } @DisplayName("Given there is a premium flight") @Nested class PremiumFlightTest { private Flight premiumFlight; private Passenger mike; private Passenger james; @BeforeEach void setUp() { premiumFlight = new PremiumFlight("3"); mike = new Passenger("Mike", false); james = new Passenger("James", true); } @Nested @DisplayName("When we have a regular passenger") class RegularPassenger { @Test @DisplayName("Then you cannot add or remove him from a premium flight") public void testPremiumFlightRegularPassenger() { assertAll("Verify all conditions for a regular passenger and a premium flight", () -> assertEquals(false, premiumFlight.addPassenger(mike)), () -> assertEquals(0, premiumFlight.getPassengersSet().size()), () -> assertEquals(false, premiumFlight.removePassenger(mike)), () -> assertEquals(0, premiumFlight.getPassengersSet().size()) ); } } @Nested @DisplayName("When we have a VIP passenger") class VipPassenger { @Test @DisplayName("Then you can add and remove him from a premium flight") public void testPremiumFlightVipPassenger() { assertAll("Verify all conditions for a VIP passenger and a premium flight", () -> assertEquals(true, premiumFlight.addPassenger(james)), () -> assertEquals(1, premiumFlight.getPassengersSet().size()), () -> assertEquals(true, premiumFlight.removePassenger(james)), () -> assertEquals(0, premiumFlight.getPassengersSet().size()) ); } @DisplayName("Then you cannot add him to a premium flight more than once") @RepeatedTest(5) public void testPremiumFlightVipPassengerAddedOnlyOnce(RepetitionInfo repetitionInfo) { for (int i = 0; i < repetitionInfo.getCurrentRepetition(); i++) { premiumFlight.addPassenger(james); } assertAll("Verify a VIP passenger can be added to a premium flight only once", () -> assertEquals(1, premiumFlight.getPassengersSet().size()), () -> assertTrue(premiumFlight.getPassengersSet().contains(james)), () -> assertTrue(new ArrayList<>(premiumFlight.getPassengersSet()).get(0).getName().equals("James")) ); } } } } import java.util.*; public abstract class Flight { private String id; Set passengers = new HashSet<>(); public Flight(String id) { this.id = id; } public String getId() { return id; } public Set getPassengersSet() { return Collections.unmodifiableSet(passengers); } public abstract boolean addPassenger(Passenger passenger); public abstract boolean removePassenger(Passenger passenger); }
TDD风格:先写测试用例再实现业务逻辑,测试覆盖率100%。
超市中,5款不起眼的零食,遇到可放心囤,成分天然又营养几乎每个人都离不开零食,但很多人买零食只顾好看,却忽视零食内在的营养。这样就导致许多零食生产商为吸引消费者,在外包装和广告上下足功夫,价格也越变越贵。逛超市的时候更是容易被花花绿绿
跨年妖股,竞业达天地在线天鹅股份众生药业西安食品要注意今日的指数继续低开震荡收阴,除了连续三连阴之外,这几天的成交量也不是很健康,所以如果不能放量修复的话很有可能会来个放量大阴再次破3000,所以风险不得不防,行情随时发生转变,
南京栖霞山彩枫斑斓如诗如画近日,随着气温的降低,南京栖霞山上的50多万株红叶树种正在逐渐变色。在阳光下俯瞰栖霞山,枫香红枫鸡爪槭羽毛枫等枫叶品种高低错落,呈现出浅黄鹅黄淡红粉红等色彩,仿若一幅五彩斑斓的天然
秦始皇陵为什么没有皇后墓?位于陕西省西安市临潼区城东5千米,这个地方是骊山北麓,这里风景优美,林木枞枞,很早以前这里就被评为我们国家第一批的4A级景区,千古一帝秦始皇的陵墓就在这个地方,据专家考证,秦始皇陵
杨坚的本名叫普六茹坚,李世民的本名叫大野世民,这是真的吗?杨坚和李世民都有自己的鲜卑姓氏,这件事确实是真的。但是,这并不意味着,老杨家和老李家就是所谓的假洋鬼子。北魏从孝文帝时代开始,就由上而下进行了汉化改革。就连皇族自身都改了姓氏,由鲜
玄武门政变,李世民是被逼无奈,真的吗,不反会死吗?各位看官,说来话长,且听我慢慢道来。一,玄武门魅影626年7月2日,天还没亮。太子李建成齐王李元吉,穿过玄武门,走到临湖殿,突然觉得不对,勒马掉头。永远没人知道他到底发现什么异常,
国内最大单体体育馆屋面基本完工可抵御12级大风侵袭图为航拍厦门新体育中心。中建二局安装工程有限公司厦门新体育中心项目供图中新网厦门11月10日电(李思源钟义兴)随着铝单板装饰板在预定位置安装完成,厦门新体育中心凤凰体育馆金属屋面1
西地那非能天天吃吗?吃多久能有效果?治疗期间需要注意什么?西地那非属于一种口服5型磷酸二酯酶抑制剂,能够用于治疗男性功能障碍类的疾病,服用后会增强一氧化氮介导的血管扩张,尤其是会使海绵体中血管达到扩张的目的,进而增加其中的血液灌注量,以达
楚汉相争项羽必败众所周知,公元前202年西楚霸王项羽拒绝了村长跨江东渡的建议而自刎乌江。那么这五年到底发生了什么事呢?为什么说楚汉相争项羽必败呢?大家跟着小编一起来看看过去五年都发生了什么事情!图
清纯可人高颜值网红美女小姐姐第37期现在的网红小姐姐一个比一个貌美如花妩媚温婉,就像一首首集万千赞美于一篇的对颜值的抒情诗。天生尤物的她们统一有着含情脉脉的眸子修长白皙的玉臂玉腿,以及如玫瑰花瓣娇嫩欲滴的烈焰红唇。在
搭一座心灵之桥世间万物皆有灵性,非精灵专属,生灵亦是如此,观动物世界,生灵们纵然释展缱绻相倦之情,一声呼啸即使身置远处仍能四方急奔,一地聚约,那种默契,那种齐力,那种众志成城,舍生取义,舍我其谁