如何访问私有成员?

ding 2003-10-15 07:00:23
背景:
在某项目中,需要根据方法的形式参数来实例化相应的对象以便对方法进行测试。例如有一个方法:
public void test(SomeObject obj)
其中SomeObject的源代码有可能得不到(调用第三方的包时),需要实例化一个SomeObject,并且可以对SomeObject的各个成员变量(包括私有)随意赋值?

现在的问题在于:
使用java.lang.reflect中的相应方法,只能得到各个成员变量的声明,不能设置私有成员变量的值;
由于不清楚被测试类中各个方法的语义,无法使用现有的方法来对私有成员变量赋值;
由于没有源代码,对源代码打桩也是不可能实现的。

那么,请问,怎样才能实例化一个类,并能对其所有成员随意赋值?
...全文
202 26 打赏 收藏 转发到动态 举报
写回复
用AI写文章
26 条回复
切换为时间正序
请发表友善的回复…
发表回复
swinging 2003-10-17
  • 打赏
  • 举报
回复
回楼上,按你的逻辑,其实说白了还不是静态代码测试,因为你所想构建的自动测试用例其实是在分析源代码基础上构建的,静态代码测试可以使用PMD(这是一个开源项目),功能很强。
另外,我觉得你还停留在面向过程的测试上,面向对象的测试,想自动构建,恐怕还是要做更多工作,首先,就你的例子来说,通常这种分支会使用多态来解决。
简单的一个构造(只是随便想的,不见得就好):
构造两个PERSON子类,一个FEMALE,一个MALE,去掉private char gender;属性,
public String getGender()方法分别实现如下:
FEMALE类:
public String getGender() {
return "FEMALE";
}
MALE类:
public String getGender() {
return "MALE";
}


基本上,面向对象设计都是使用多态来解决复杂的分支结构的,设计模式中比如STATE/Strategy等都是提供分支结构的多态解决方案。象SWITCH这个面向过程中的通用
多分支在面向对象设计不被提倡,一旦需要使用SWITCH,那么大多数可以被重构为多态。
当然,多态还只是面向对象的一个方面而已。

如果你使用下PMD,就更有体会,一旦一个方法中语句分支数量超过10左右(具体忘了,差不多就是这个值)就会提出警告,提示重构代码以获得清晰简洁的结构。


Schlemiel 2003-10-17
  • 打赏
  • 举报
回复
调用setGender()方法呀,这还用问?
不要问“如果没有setGender()方法怎么办”,如果没有这个方法,说明这个class的作者根本没打算让你设置这项属性,至少是没打算让你把它当成bean属性来用。换句话说,你想要的是一种不可能的状态。
ding 2003-10-17
  • 打赏
  • 举报
回复
这样确实可以实例化。但是还需要设置不同的状态。
比如有这样一个方法:
public String getGenderString(Person p)
{
String gender = null;
if (Person.getGender() == "F")
gender = "FEMALE";
else if (Person.getGender() == "M")
gender = "MALE";

return gender;
}
Person类的定义为
public class Person {
...
private char gender;
...

public String getGender()
{
return String.valueOf(gender);
}
...
}

当我们实例化了一个Person对象之后,怎样去设置不同的gender的值,从而去完整的测试getGenderString()方法呢?
Schlemiel 2003-10-17
  • 打赏
  • 举报
回复
那就把参数类型的constructor抓出来,调用无参数ctor或者String参数的ctor
ding 2003-10-17
  • 打赏
  • 举报
回复
我的目标是这样的:
做一个程序,能够根据被测试类的代码自动生成单元测试数据。被测试的类是由外部指定的,测试数据要自动生成。
如果输入参数为原生数据类型(boolean,byte,char,double,float,int,long,short),问题已经解决。
困难之处在于,如果输入参数不是原生数据类型,那么必须构造出输入参数的实例来。
ding 2003-10-17
  • 打赏
  • 举报
回复
sigh,问题越搅越糊涂了。我来详细的解释一下。

在测试领域中,一个很大的问题是如何构造出测试数据。方法有很多,有根据规约的,有根据前置和后置条件的,还有一个大类就是根据代码本身。根据规约和根据前置后置条件都是来测试程序的正确性;根据代码本身构造测试数据的目的往往不是来测试程序的正确性,事实上这样就像你所说的,无法从自身证明自身的正确性。这种情形下,目的往往都在于测试程序的健壮性,来确认程序在各种输入下都能够工作正常。
而测试健壮性又有一些结构性的指标,比如说语句覆盖、分支覆盖、条件/分支覆盖、修正式条件分支覆盖等等。当然最为完整的是路径覆盖。路径覆盖已经被证明为一个停机问题,不存在解决方法。只能在一些稍弱的指标上面下功夫。文献已经证明,这种结构性覆盖测试能够发现很多问题。目前很多公司,还有军工领域的软件,都要求达到语句覆盖和分支覆盖的双百覆盖。而修正式条件分支覆盖能够起到更好的作用。所以,这样的工作是有意义的。
我的目标是发展出一套方法来自动构造出能够对Java当中的方法进行修正式条件/分支覆盖测试的测试数据集来。目前,使用了一些新的算法,使得形式参数如果为原生数据类型,可以达到90%以上的覆盖率。
但是如果形式参数不是原生数据类型,还没有找到很好的解决办法。因为不清楚被测试代码的语义(事实上也不可能得到),输入参数很难构造出来。还是以上面的方法为例:
public String getGenderString(Person p)
{
String gender = null;
if (Person.getGender() == "F")
gender = "FEMALE";
else if (Person.getGender() == "M")
gender = "MALE";

return gender;
}
Person类的定义为
public class Person {
...
private char gender;
...

public String getGender()
{
return String.valueOf(gender);
}
...
}
当要测试getGenderString()方法的时候,首先必须给出Person的一个实例。而这个实例必须可以拥有不同的状态,使得getGenderString()方法可以走不同的执行路径(也就是不同的条件/分支)。实例化的问题相对来说比较容易处理,利用Java的反射机制(java.lang.reflect包)可以很容易的解决。Schlemiel(维特根斯坦的扇子) 也提到了这一点。获得一个对象的实例之后,怎样才能设置对象的状态呢?
Schlemiel(维特根斯坦的扇子) 提到的使用类似于setGender方法的事情,事实上要求必须清楚每个方法的语义。但是对于工具而言,如何才能知道setGender是设置gender得值呢?如果相应的方法不是setGender而是writeGender呢?
swinging 2003-10-17
  • 打赏
  • 举报
回复
to ding()
你的测试没有任何意义。
首先,你测试的依据是什么?是类代码?如果是类代码,那么类代码所描述的当然是它自己了,你有什么好测试的?比如上面你给的例子,类中很显然说明,如果Person.getGender() == "F"则返回一个"FEMALE",如果Person.getGender() == "M"返回一个"MALE",其它情况返回NULL,好了,假设现在你在外部都知道了,你怎么测试?你输入一个M看看里面是不是输出MALE?倒,你根据程序代码得出的结论居然让程序本身去证明,那你的测试根本是没有任何意义的。
就象你说的,当我们实例化了一个Person对象之后,怎样去设置不同的gender的值,从而去完整的测试getGenderString()方法呢?设置什么样的gender值得出什么结果根本是你在拿到这段程序前就该知道的事情,要是不知道你有什么好测试的?
说起来有点晕,摆个简单的例子,假设有下面方法。
public Object getNull() {
return null;
}

好了,假设你之前不知道要测什么,于是你跑类里面看了看,哦,这个方法是返回一个null的,于是你说,好了,我知道要测试什么了,是测试这个方法是否每次调用都返回一个null,于是你去写你的测试了。那么你到底要测试什么?程序告诉你它会准确得返回一个NULL,于是你就测试这个?你在测试编译器能否正确编译?还是你在测试JAVA虚拟机是否能正确执行这些代码?除了这2个理由,你想得出第三个?
Schlemiel 2003-10-16
  • 打赏
  • 举报
回复
如何构造一个String类型的对象?难道你是在问这个问题?
String param = new String("Parameter 1");
ding 2003-10-16
  • 打赏
  • 举报
回复
可能是我没有把话说清楚。
我要做的是对给了源代码的类进行单元测试,为单元测试准备数据来达到条件/分支覆盖率。被测试类的源代码我是已经有的,但是七种方法的输入参数有可能是没有源代码的(最简单的,比如说String类)。问题是如何构造这些输入参数。
Schlemiel 2003-10-16
  • 打赏
  • 举报
回复
我觉得既然别人已经给你是一个class而不是源码,你就应该做黑盒测试。内部的白盒测试是这个class的作者的责任,他自己的单元测试应该覆盖到可能出错的边际情况。明明是一个黑盒拿到你手上,你偏要想把它打开,这怎么行呢?就算你找到好办法了,好的,过两天程序员一重构,内部实现全变了,你还怎么玩?人家又没有责任的,class的可观察行为全都没变,是你自己要把脑袋伸进去挨夹。
ding 2003-10-16
  • 打赏
  • 举报
回复

java.lang.reflect只能对公有的成员进行访问。
测试用例是想自动生成。也就说,给定了被测试类的源代码,试图生成针对被测试类的基于代码的结构化测试用例。这样要分析代码的结构。
honkyjiang 2003-10-16
  • 打赏
  • 举报
回复
使用java.lang.reflect中的相应方法可以对对象封装的一些信息进行处理 ..
一个人的年龄.自然这个成员的有效取值范围绝对不应该超过0-200 这里就相当于条件覆盖了......... 你没有源代码 不知道语义 怎么给条件啊?? 既然连条件(判断的依据)都没有
.. 怎么去进行条件覆盖呢?? 你的测试用例怎么给啊??
ding 2003-10-16
  • 打赏
  • 举报
回复

呵呵,那么如何不修改状态而又能达到目标?
Schlemiel 2003-10-16
  • 打赏
  • 举报
回复
你都想修改别人对象的私有状态了,还有什么一致性好说的?
ding 2003-10-16
  • 打赏
  • 举报
回复

to Schlemiel(维特根斯坦的扇子)首先谢谢各位的关注.

需要随意对私有成员赋值的动机来源于在对一个方法进行结构化测试(比如说条件/分支覆盖),需要给方法提供不同的参数以便于能够最大限度的走遍方法当中的所有条件/分支.由于不知道方法本身的语义(方法是任意指定的,没有什么信息可以指明方法的语义),所以需要寻找一种途径,能够设置对象的状态.
Schlemiel(维特根斯坦的扇子)的方法我曾经使用过,但是这样的方法难以保证对象状态的一致性.例如有一个类有一个私有成员,表明了一个人的年龄.自然这个成员的有效取值范围绝对不应该超过0-200,可是我可以随便设一个10000,这样的状态还有什么意义?
我曾经考虑过的方法有如下几项:

方法 缺点
代码注射.直接修改对象相对应的字节码, 实现过于复杂,并且也
在代码中添加一个方法,通过这个方法 难以保证对象状态的一致性。
来对私有成员进行修改。

Schlemiel的方法。相对来说,这是最 也难以保证对象状态的一致性。
容易的一种方法。

扫描源代码,找出哪些方法修改了哪 需要有源代码,如果没有源代码
些数据成员,然后通过这些方法修改 ,则无能为力。而很多方法的参
数据成员。 数都有可能是标准库中的类或者
第三方的类。另外,直觉告诉我
这个方法还有一点点不对劲,
没有找出到底是什么。

总之来说,这个问题涉及到面向对象语言的语义问题,很困难。能不能采取另外的方式?怎样才能不需要设置对象的状态呢?
Schlemiel 2003-10-16
  • 打赏
  • 举报
回复
越说越糊涂了。要测的接口不是你自己写的吗?测试数据不是应该由你自己来组织吗?怎么会不知道参数的类型呢?
要不你把问题说详细点,不然真是搞不拎清怎么回事。
ding 2003-10-16
  • 打赏
  • 举报
回复
问题是,我怎么知道给我的代码是一个String对象呢?怎么才能实例化一个我不知道是什么玩意的类呢?





Schlemiel 2003-10-15
  • 打赏
  • 举报
回复
可能是绝对可能地,但是绝对是不应该这样做地……

Field[] all = SomeObject.class.getDeclaredFields(); //取到所有field,包括private的
// 假设你要找的是private String _mail;这样一个field
Field fldMail = null;
for (int i = 0; i < all.length; i++) {
if (all[i].getType().equals(String.class) && all[i].getName().equals("_mail")) {
fldMail = all[i];
}
}
if(fldMail != null) { //恩,找到这个field了
fldMail.setAccessible(true);
// AccessibleObject.setAccessible,现在这个Field对象是public的了
fldMail.set(obj, "anon@fake.com"); //好了,篡改别人的mail地址……
}

再说一遍,奉劝你不要这样做,太容易沦为同事的笑柄了。
jscsqb 2003-10-15
  • 打赏
  • 举报
回复
测试的时候不要去管类的实现的细节,这些都应该是“透明”的。
seaman0916 2003-10-15
  • 打赏
  • 举报
回复
既然私有变量都能随意赋值了,私有的意义何在? 岂不是Java的大漏洞?

我觉得不太可能!
加载更多回复(6)

62,612

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧