可测试性软件架构设计之私有类测试问题

 

我们以智能家居为例,一所房子里面有房间和大门,房间包括卧室、厨房等。房间是房子的内部类,并且是在房子的构造函数中初始化的,对外没有暴露接口。

代码示例1:房子类

public class House
{

    private Bedroom bedroom;

    private Kitchen kitchen;

    private FrontDoor door;

    public house() {
        bedroom = new Bedroom();
        kitchen = new Kitchen();
        door = new FrontDoor(); 
    }

    //出门
    public void leaveHouse() {
        //关闭厨房电器
        kitchen.shutdownAllAppliances();

        //关闭卧室电灯
        bedroom.turnLightOff();

        //关闭大门
        lockFrontDoor();

    }

    //检查大门是否关闭
    boolean isFrontDoorLocked(){
        return door.isLocked();
    }


  private void lockFrontDoor(){
        door.lockDoor();
  }
}

我们编写当离开房子时大门是否关闭的测试案例。

案例举例如下:

代码示例 2:测试离开房子时大门是否关闭。



public class HouseTest{

    private House house;

    public void testLockFrontDoor(){
        house = new House();
        house.leaveHouse();
        assertTrue(house.isFrontDoorLocked());
    }
}

当我们测试 House 类的时候,如果使用房间类(Bedroom、Kitchen)的一些功能,可能会有些问题。在很多情况下 House 类的内部逻辑可能很复杂,它也可能依赖其他类的复杂功能。例如,House 类是否会依赖硬件控制类。但是我们的测试环境可能并没有硬件控制的测试环境,在这种情况下测试类将无法运行,或者由于依赖环境的缺失一直测试执行失败。

很明显,以上这个 House 类的可测试性就不好。可测试性架构设计建议我们利用接口把类从依赖环境中解耦。被依赖实例通过依赖注入方式完成。

在我们的单元测试中,我们要验证 House 的调用逻辑,而不需要真实的调用执行真实的关闭电器、关闭电灯等功能。我们模拟离开房子的过程,然后验证大门是否关闭了。我们需要通过创建模拟类来代替实际类,测试时我们把模拟类注入到 House 实例中完成被测试逻辑。

不幸的是当前 House 类并没有提供注入模拟测试类的机制,House类没有对外暴露内部成员类的方法,也没有机制来替换实际的成员类。

解决成员变量类初始化的一个方法是利用依赖注入(Dependency Injection,DI),在 House 类外面对成员类进行初始化。我们首选把 Kitchen 类和 BedRoom 类换成接口(Interface),然后使用 House 类构造函数注入成员变量。

代码示例 3:House 类重构

public class House
{

    private IBedroom iBedroom;

    private IKitchen iKitchen;

    private FrontDoor door;

    //
    public house(IBendRoom iBedRoom,IKitchen iKitchen) {
        this.iBedroom = iBedroom;
        this.iKitchen = iKitchen;
        door = new FrontDoor(); 
    }

    //出门
    public void leaveHouse() {
        //关闭厨房电器
        iKitchen.shutdownAllAppliances();

        //关闭卧室电灯
        iBedroom.turnLightOff();

        //关闭大门
        lockFrontDoor();

    }

    //检查大门是否关闭
    boolean isFrontDoorLocked(){
        return door.isLocked();
    }

  private void lockFrontDoor(){
        door.lockDoor();
  }
}

测试代码中使用依赖注入机制利用模拟类进行测试,从而避免对真实环境的依赖。

代码示例 4:依赖注入和接口注入模拟类

public class HouseTest{

    //Bedroom 模拟类
    private FakeBedroom fakeBedroom;

    //Kitchen 模拟类
    private FakeKitchen fakeKitchen;

    private House house;

    public void testHouse(){

        fakeBedroom = new FakeBedroom();

        fakeKitchen = new FakeKitchen();

        house = new House(fakeBedroom,fakeKitchen);
        house.leaveHouse();

        assertTrue(house.isFrontDoorLocked());
    }

}

为了能正确模拟 Bedroom 和 Kitchen 的行为,FakeBedroom 和 Bedroom 应该实现 IBedRoom 接口,FakeKitchen 和 Kitchen 应该实现 IKitchen 接口。

我们当前的代码示例目的就是把被测试类 House 从依赖类中剥离出来。如果不隔离依赖类,相关被测试目标类将很难测试,以后的代码维护也变得非常困难。

验证两个对象之间的交互是否正确是常见的一种测试。在示例代码 1 中,我们是为了验证当离开房子时,相关离开流程是否正确执行,例如厨房的电器设备应该关闭等。比较明智的设计应该是,House 类向 Kitchen 类发送关闭电器的指令,Kitchen 类负责关闭厨房电器。请见示例代码 4。

为了模拟 Kitchen 的行为,我们需要在 FakeKitchen 类中增加一些验证逻辑。FakeKitchen 类需要实现 IKitchen 接口,以便可以使用依赖注入(DI)。

示例代码 5: FakeKitchen类

public class FakeKitchen implements IKitchen{
    private boolean shutDownAllAppliancesFlag;
    public FakeKitchen(){
        this.shutDownAllAppliancesFlag = false;
    }

    boolean wasCalledShutDownAllAppliances(){
        return this.shutDownAllAppliancesFlag;
    }

    public void shutDownAllAppliances(){
        this.shutDownAllAppliancesFlag = true;

    }


}

 

GiTChat:如何提升软件的可测试性架构设计

 

敏捷测试社区公众:

吐司QA
标题

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值