Test an object that instantiates other objects

[b]问题:[/b]
你想测试一个对象,但这个对象内部还初始化其他对象,这使得测试变得困难

[b]背景:[/b]
面向对象的设计是双刃剑。我们使用聚合来表明一个对象拥有另外一个对象,比如大多数情况下一个汽车都有自己的轮子。而另一方面,为了单独测试一个对象,我们需要将对象像拼图游戏一样拼凑起来。这就是说,测试更期望对象使用组合而不是聚合。如果你要测试的对象初始化了其他的对象,那么只有在保证内部对象的正确性的前提下,你才能测试这个对象,而这就违反了单独测试一个对象的原则。

我们不安地发现,仍然有大量的程序员对进行测试的好处一无所知。这致使他们过多地使用聚合。他们的设计中充满了初始化其他对象的对象,或者常常从全局的位置获取对象。这种编程办法,如果不改进的话,就会造成强耦合的架构,导致测试难以进行。我们知道:我们继承了大量这种设计,甚至直接创建了它们。下面的诀窍介绍了一个基本的测试技巧,它可以带来程序设计上的一些提高,是单独的测试一个对象成为可能。

[b]诀窍:[/b]
为了处理这个问题,你要将要测试的对象的内部对象用其他的对象代替。这就有两个问题需要解决:
1. 如何创建这个对象所包含的对象的替身
2. 如果将它传递给要测试的对象

为了简化讨论,我们使用术语“测试对象”(不要与对象测试相混淆),用来指代那些你要测试的类或者接口的实例。有多种不同的测试对象:假的、残缺的、模拟的。

创建一个接口的测试对象很简单:只要创建一个类,并用最简单的方法实现这个接口就可以了,这是最简单的测试对象。在这里我们使用EasyMock(www.easymock.org)来创建接口的测试对象。之所以使用这个包,是因为它不仅可以避免我们重复的劳动,还能保证我们仿造的接口对象的一致性和统一性。这让我们的测试程序更容易理解。

创建一个类的测试对象,可以创建它的一个子类,然后仿造它的全部方法,或者干脆屏蔽它的全部方法。一个仿造的方法返回某种可预见的、有意义的、硬编码的值,而一个屏蔽的方法不做任何有意义的事情,只需要实现编译要求即可。你也许会发现:在要测试的对象中声明接口,然后在测试时转变为具体类的对象是很有好处的。因为这样你就可以使用前面介绍的EasyMock了。此外,如果你在对象中要使用其他的对象,最好将其声明为接口,这样你的设计就更有弹性。

至于第二个问题,我们有两种方法可以让你将一个测试对象传递给另一个要测试的对象:一种是改造构造函数,另一种是添加一个set方法。我们认为改造构造函数的方法比较简单,这样可以避免在测试中再去调用set方法。为了说明,可以看个例子

public class Deployment{
public void deploy(File targetFile
throws FileNotFoundException{
Deployer.getInstance().deploy(this, targetFile);
}
}

注意,Deployment使用类级别的方法Deployer.getInstance()来生成Deployer。如果你想仿造一个Deployer,你需要将一个Deployer通过某种方法传递给Deployment。我们推荐使用构造函数来传递,所有新加一个构造函数并创建一个实例来保存Deployer;

public class Depliyment{
private Deployer deployer;

public Deployment(Deployer deployer){
this.deployer = deployer;
}

public void deploy(File targetFile)throws FileNotFoundException{
deployer.deploy(this, targetFile);
}

}

怎么好像没看见Deployer.getInstance()方法?我们不能丢掉这段代码:现在我们删除了无参数的构造函数,那么我们需要添加一个新的构造函数。

public class Deployment{

private Deployer deployer;
public Deployment(){
this(Deployer.getInstance());
}
public Deployment(Deployer deployer){
this.deployer = deployer;
}
public void deploy(File targetFile){
deployer.deploy(this, targetFile);
}

}

现在,当产品代码使用无参构造函数创建一个Deployment时,就可以观察到期望的行为:Deployment将使用Singleton Deployer。不过,在我们的测试中,我们使用一个假的Deployer,这样做可以模拟诸如目标文件不存在等情况。下面的代码是一个“故障测试”的Deployer------它永远都认为目标文件不存在

public class FileNotFoundDeployer extends Deployer{
public void deploy(Deployment deployment, File targetFile)throws FileNotFundException{
throw new FileNotFoundException(targetFile.getPath());
}
}

现在,我们可以测试当Deployer部署失败的时候,Deployment如何表现。

public void testTargetFileNotFound() throws Exception{
Deployer fileNotFoundDeployer = new FileNotFoundDeployer();
Deployment deployment = new Deployment(fileNotFoundDeployer);

try{
deployment.deploy(new File("hello"));
fail("Found target file?!");
}
catch(FileNotFoundException expected){
assertEquals("hello", expected.getMessage());
}
}

这个测试演示了如何替换一个对象中引用的测试对象,同时也介绍了如何通过继承仿制测试对象。我们将使用的对象变成了构造函数的一个可选参数:如果我们没有提供的话,那么类也会自动察觉,并创建一个默认的。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ defines a class DateV3 with the following: private member variables: int year, month, day; Has three constructors and one destructor as follows: The first constructor takes three parameters, int y, int m, int n; The second is the copy constructor that takes a DateV3 object as the parameter; The third is the default constructor that takes no parameter; The destructor takes no parameter. (3) Has overloaded operators: int operator-(DateV3 & oneDate); // return difference in days between the calling object and oneDate DateV3 operator+(int inc); // return a Date object that is inc days later than the calling object DateV3 operator-(int dec); // return a Date object that is dec days earlier than the calling object DateV3 operator++(); // overload the prefix ++ operator DateV3 operator++(int); // overload the postfix ++ operator friend ostream& operator<< (ostream& outputStream, DateV3& theDate); // overload the << operator Test class DateV3 in the main function as follows: Declare and initialize an object to represent today, which should be the date that you work on this assignment.Declare and initialize an object to represent your OWN birthday.Express John’s birthday given John is 5 days older than yours. Create Tom’s birthday by using the copy constructor, assuming Tom has the same birthday as you. Display how many days have passed since your birth, John’s birth, and Tom’s birth, respectively. Create an DateV3 object, someday, by cloning Tom’s birthday. Increment someday by the prefix operator ++ once, and by postfix operator ++ once.Display someday, today, your birthday, John’s birthday, and Tom’s birthday. Declare a DateV3 object to represent 28 February 2024, display it, apply the prefix ++ operator on it, display it again, and apply the postfix ++ operator on it and display it again.
最新发布
06-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值