Java PricedHotel类JUnit测试失败:对象引用不一致求助
我正在完成一个涉及多个类的Java作业,测试文件为给定且不可修改的版本。被测试主类是PricedHotel,目前所有测试仅最后一个断言未通过。调试后发现,PricedHotel类中的room和safe对象与Test类中的对应对象不一致,虽已尝试正确引用,但找不到问题根源,寻求技术帮助。
现有代码
PricedHotel类
package ss.week2.hotel; import ss.week2.hotel.bill.Bill; import ss.week2.hotel.bill.BillPrinter; import ss.week2.hotel.bill.StringBillPrinter; import ss.week2.hotel.PricedRoom; public class PricedHotel extends Hotel { public static final double ROOM_PRICE = 100.0; public static final double SAFE_PRICE = 20.0; public PricedRoom r1; public Room r2; private Guest g1; private Guest g2; private StringBillPrinter stringBillPrinter; public PricedHotel(String name){ super(name); r1 = new PricedRoom(1,ROOM_PRICE,SAFE_PRICE); r2 = new Room(2, new Safe()); stringBillPrinter = new StringBillPrinter(); } @Override public Room checkIn(String guestName) { Room room = super.checkIn(guestName); if (!(room instanceof PricedRoom)) { PricedRoom pricedRoom = new PricedRoom(room.getNumber(), ROOM_PRICE, SAFE_PRICE); pricedRoom.setGuest(room.getGuest()); room = pricedRoom; } return room; } public Bill getBill(String guestName, int total, BillPrinter billPrinter){ PricedRoom room = this.r1; PricedSafe safe = (PricedSafe) this.r1.getSafe(); Bill bill = new Bill(billPrinter); if (safe.isActive()) { Bill.Item item1 = new Bill.Item() { @Override public double getPrice() { return ((total*ROOM_PRICE) + SAFE_PRICE); } @Override public String toString() { return guestName; } }; } else { Bill.Item item1 = new Bill.Item() { @Override public double getPrice() { return total*ROOM_PRICE; } @Override public String toString() { return guestName; } }; bill.addItem(item1); } return bill; } }
Test类
package ss.week2.hotel.tests; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import ss.week2.hotel.*; import ss.week2.hotel.bill.Bill; import ss.week2.hotel.bill.StringBillPrinter; import ss.week2.hotel.password.Password; import static org.junit.jupiter.api.Assertions.*; public class PricedHotelTest { /** Test variable for a <tt>Hotel</tt> object. */ private PricedHotel hotel; private String correctPassword; private String wrongPassword; public static final String GUEST_NAME_1 = "Major Gowen"; public static final String GUEST_NAME_2 = "Miss Tibbs"; public static final String GUEST_NAME_3 = "Miss Gatsby"; public static final int NUMBER_OF_NIGHTS = 5; /** * Sets the instance variable <tt>hotel</tt> to a well-defined initial value. * All test methods should be preceded by a call to this method. */ @BeforeEach public void setUp() { hotel = new PricedHotel("Fawlty Towers"); // initialisation of password-variable correctPassword = new Password().getInitPass(); wrongPassword = correctPassword + "_invalid"; } /** * checkIn First room should be a PricedRoom */ @Test public void testCheckIn() { Room room = hotel.checkIn(GUEST_NAME_1); assertTrue(room instanceof PricedRoom, "room should be an instance of PricedRoom"); Safe safe = room.getSafe(); assertTrue(safe instanceof PricedSafe, "safe should be an instance of PricedSafe"); } /** * If there is a free room, getFreeRoom must return a room without guest. */ @Test public void testGetFreeRoomFromNotFullHotel() { Room room = hotel.getFreeRoom(); assertNull(room.getGuest(), "A room is free"); hotel.checkIn(GUEST_NAME_1); Room freeRoom = hotel.getFreeRoom(); assertNotNull(freeRoom, "Another room is free"); assertNotEquals(room, freeRoom, "Another room is free"); } /** * If there is no free room, getFreeRoom must return null. */ @Test public void testGetFreeRoomFromFullHotel() { hotel.checkIn(GUEST_NAME_1); hotel.checkIn(GUEST_NAME_2); Room noRoom = hotel.getFreeRoom(); assertNull(noRoom, "No room available in a full hotel"); } /** * getRoom must not return any room, if the guest is not checked in */ @Test public void testGetRoomBeforeCheckIn() { Room room = hotel.getRoom(GUEST_NAME_1); assertNull(room, "Guest 1 not checked in"); } /** * If the guest is checked in, the returned room must be occupied by the specified guest. */ @Test public void testGetRoomAfterCheckIn() { hotel.checkIn(GUEST_NAME_1); Room room = hotel.getRoom(GUEST_NAME_1); assertEquals(GUEST_NAME_1, room.getGuest().getName(), "Guest 1 checked in"); } /** * A password object must be returned. */ @Test public void testGetPassword() { Room room = hotel.checkIn(GUEST_NAME_1); assertTrue(room instanceof PricedRoom, "room should be an instance of PricedRoom"); Safe safe = room.getSafe(); assertTrue(safe instanceof PricedSafe, "safe should be an instance of PricedSafe"); Password password = ((PricedSafe) safe).getPassword(); assertNotNull(password, "Returned password is not null"); } /** * The Bill should be correctly calculated */ @Test public void testGetBill() { Room room = hotel.checkIn(GUEST_NAME_1); assertTrue(room instanceof PricedRoom, "room should be an instance of PricedRoom"); StringBillPrinter printer = new StringBillPrinter(); Bill bill = hotel.getBill(GUEST_NAME_1, NUMBER_OF_NIGHTS, printer); double nightsTotal = NUMBER_OF_NIGHTS * PricedHotel.ROOM_PRICE; assertEquals(nightsTotal, bill.getSum(), 0.01, "Bill sum should contain only room costs."); assertTrue(printer.getResult().contains(String.format("%.2f", nightsTotal))); // Now check sum again when safe is actually active assertTrue(room.getSafe() instanceof PricedSafe); PricedSafe safe = (PricedSafe) room.getSafe(); assertFalse(safe.isActive(), "Safe should be deactivated after initialisation."); safe.activate(correctPassword); assertTrue(safe.isActive(), "Safe should be activated after activation with password."); Assertions.assertEquals(nightsTotal + PricedHotel.SAFE_PRICE, hotel.getBill(GUEST_NAME_1, NUMBER_OF_NIGHTS, printer).getSum()); } }
Hotel.checkIn方法
public Room checkIn(String name){ if (room1.getGuest() != null && room2.getGuest() != null){ //checks if the two rooms are empty return null; } if (g1 != null && Objects.equals(g1.getName(), name)){ //checks if g1 is already checked in return null; } if (g2 != null && Objects.equals(g2.getName(), name)){ //checks if g2 is already checked in return null; } Room freeRoom = getFreeRoom(); //gets a free room if (freeRoom == null){ // checks if hotel is fill return null; } Guest newGuest = new Guest(name); //new guest object with the name we provided newGuest.setRoom(freeRoom); // set's the new guest to the free room freeRoom.setGuest(newGuest); // set's the guest to the free room if (g1 == null){ g1 = newGuest; } else if (g2 == null) { g2 = newGuest; } return freeRoom; }
问题根源分析
成员变量重复定义导致对象不一致:
PricedHotel类中自行定义了r1、r2、g1、g2,但父类Hotel已经有对应的room1、room2、g1、g2成员。父类的checkIn方法操作的是父类的room1/room2,而PricedHotel的getBill方法直接使用自己定义的r1,这两个是完全独立的对象。Test类中激活的是父类room1对应的safe,而getBill检查的是子类r1的safe,自然状态不一致。checkIn方法未更新父类房间引用:
子类checkIn方法中,当父类返回的房间不是PricedRoom时,新建了一个PricedRoom对象,但仅修改了局部变量room,并未更新父类中对应的room1/room2引用。导致父类存储的还是原来的Room对象,后续操作的对象与getBill中使用的对象不匹配。getBill方法的硬编码与逻辑遗漏:
- 不管传入的guestName是谁,
getBill都硬编码使用this.r1,无法正确获取对应客人的房间; - 当safe处于激活状态时,创建了Bill.Item但未添加到bill中,导致此时bill的sum为0,直接触发断言失败。
- 不管传入的guestName是谁,
修复方案
步骤1:移除子类重复定义的成员变量
删除PricedHotel中的r1、r2、g1、g2,直接复用父类的成员变量(确保父类的这些成员是protected或public,若父类是private则需要提供getter方法)。
步骤2:修改PricedHotel构造方法,初始化父类房间
public PricedHotel(String name){ super(name); // 直接初始化父类的room1和room2 room1 = new PricedRoom(1, ROOM_PRICE, SAFE_PRICE); room2 = new Room(2, new Safe()); stringBillPrinter = new StringBillPrinter(); }
步骤3:修正checkIn方法,更新父类房间引用
@Override public Room checkIn(String guestName) { Room room = super.checkIn(guestName); if (room == null) { return null; } // 如果返回的房间不是PricedRoom,替换父类中对应的房间 if (!(room instanceof PricedRoom)) { PricedRoom pricedRoom = new PricedRoom(room.getNumber(), ROOM_PRICE, SAFE_PRICE); pricedRoom.setGuest(room.getGuest()); // 根据房间号替换父类的room1或room2 if (room.getNumber() == 1) { room1 = pricedRoom; } else if (room.getNumber() == 2) { room2 = pricedRoom; } room = pricedRoom; } return room; }
步骤4:修复getBill方法的逻辑问题
public Bill getBill(String guestName, int total, BillPrinter billPrinter){ // 根据guestName找到对应的房间 Room guestRoom = getRoom(guestName); if (!(guestRoom instanceof PricedRoom)) { // 若不是PricedRoom,按需求处理(这里直接返回空bill) return new Bill(billPrinter); } PricedRoom room = (PricedRoom) guestRoom; PricedSafe safe = (PricedSafe) room.getSafe(); Bill bill = new Bill(billPrinter); Bill.Item item1 = new Bill.Item() { @Override public double getPrice() { if (safe.isActive()) { return total * ROOM_PRICE + SAFE_PRICE; } else { return total * ROOM_PRICE; } } @Override public String toString() { return guestName; } }; // 无论safe是否激活,都要添加item到bill bill.addItem(item1); return bill; }
验证修复
修复后,Test类中激活的safe与getBill中检查的safe是同一个对象,同时getBill会根据客人获取对应房间,且无论safe状态都会正确添加计费项,最后一个断言测试将通过。
内容的提问来源于stack exchange,提问作者ST Saksham Singh Birla

