Unit testing 如何模拟静态成员变量

Unit testing 如何模拟静态成员变量,unit-testing,junit,mocking,mockito,Unit Testing,Junit,Mocking,Mockito,我有一个类ClassToTest,它依赖于ClassToMock。 public类ClassToMock{ 私有静态最终字符串成员_1=FileReader.readMemeber1(); 受保护的void方法(){ ... } } ClassToTest的单元测试用例 公共类ClassToTest{ 私有类tomock\u mock; @以前 public void setUp()引发异常{ _mock=mock(ClassToMock.class) } } 在setUp()方法中调用m

我有一个类ClassToTest,它依赖于ClassToMock。

public类ClassToMock{
私有静态最终字符串成员_1=FileReader.readMemeber1();
受保护的void方法(){
...
}
}
ClassToTest的单元测试用例

公共类ClassToTest{
私有类tomock\u mock;
@以前
public void setUp()引发异常{
_mock=mock(ClassToMock.class)
}
}
在setUp()方法中调用mock时,FileReader.readMemeber1();被执行。有没有办法避免这种情况?我认为一种方法是在方法中初始化成员_1。还有其他选择吗


谢谢

您的
ClassToMock
FileReader
紧密结合,这就是您无法测试/模拟它的原因。而不是使用工具来破解字节码,以便您可以模拟它。我建议你做一些简单的重构来打破依赖关系

第一步。封装全局引用 Michael Feathers的精彩著作中也介绍了这一技巧:

标题几乎是自我解释的。不是直接引用全局变量,而是将其封装在方法中

在您的情况下,
ClassToMock
可以重构为:

public类ClassToMock{
私有静态最终字符串成员_1=FileReader.readMemeber1();
公共字符串getMemberOne(){
返回成员1;
}
}
然后您可以轻松地使用Mockito模拟
getMemberOne()

已更新旧步骤1无法保证
Mockito
mock安全,如果
FileReader.readMemeber1()
抛出异常,则测试将失败。所以我建议增加一个步骤来解决这个问题

步骤1.5。添加Setter和Lazy Getter 由于问题是
FileReader。只要加载
ClassToMock
,就会调用readMember1()。我们不得不推迟。因此,我们惰性地进行getter调用
FileReader.readMember1()
,并打开一个setter

public类ClassToMock{
私有静态字符串成员_1=null;
受保护的字符串getMemberOne(){
if(成员_1==null){
MEMBER_1=FileReader.readMemeber1();
}
返回成员1;
}
公共无效setMemberOne(字符串memberOne){
成员_1=成员一;
}
}
现在,即使没有
Mockito
,您也应该能够制作一个假的
ClassToMock
但是,这不应该是代码的最终状态,一旦您准备好测试,您应该继续执行步骤2。

第二步。依赖注入 一旦您准备好了测试,您应该进一步重构它。现在,不再单独读取
成员_1
。这个类应该接收来自外部世界的
成员\u 1
。您可以使用setter或构造函数来接收它。下面是使用setter的代码

public类ClassToMock{
私有字符串成员1;
公共无效setMemberOne(字符串memberOne){
this.memberOne=memberOne;
}
公共字符串getMemberOne(){
返回成员1;
}
}
这两步重构非常容易,即使不需要测试也可以完成。如果代码没有那么复杂,您可以只执行步骤2。然后您可以轻松地测试
ClassToTest


更新12/8:回答评论 请参阅此问题中我的另一个答案。

更新12/8:回答评论 问:如果FileReader是一些非常基本的东西,比如日志记录,需要 每节课都有。你会建议我采用同样的方法吗 在那里

视情况而定。

在进行这样的大规模重构之前,您可能需要考虑一些事情

  • 如果我将
    FileReader
    移动到外部,是否有合适的类可以从文件中读取并将结果提供给需要它们的每个类

  • 除了让课程更容易测试外,我还有其他好处吗

  • 我有时间吗

  • 如果任何一个答案是“不”,那么你最好不要这样做

    但是,我们仍然可以通过最小的更改来打破所有类和
    FileReader
    之间的依赖关系。

    根据您的问题和评论,我假设您的系统使用
    FileReader
    作为从属性文件读取内容的全局参考,然后将其提供给系统的其余部分

    这项技术也在迈克尔·费瑟的精彩著作中介绍过:再次

    第一步。将
    FileReader
    静态方法委托给实例。 改变

    公共类文件读取器{
    公共静态文件读取器getMemberOne(){
    //读取文件的代码。
    }
    }
    

    公共类文件读取器{
    private static FileReader singleton=new FileReader();
    公共静态字符串getMemberOne(){
    返回singleton.getMemberOne();
    }
    公共字符串getMemberOne(){
    //读取文件的代码。
    }
    }
    
    通过这样做,
    FileReader
    中的静态方法现在不知道如何
    getMemberOne()

    第二步。从文件读取器中提取接口
    公共接口AppProperties{
    字符串getMemberOne();
    }
    公共类FileReader实现AppProperties{
    private static AppProperties singleton=new FileReader();
    公共静态字符串getMemberOne(){
    返回singleton.getMemberOne();
    }
    @凌驾
    公共字符串getMemberOne(){
    //读取文件的代码。
    }
    }
    
    我们现在使用
    AppProperties
    将所有方法提取到
    AppProperties
    ,并在
    FileReader
    中提取静态实例

    第三步。静态设定器
    公共类FileReader实现AppProperties{
    private static AppProperties singleton=new FileReader();
    公共静态无效setAppProperties(AppProperties属性){
    单件=道具;
    }
    ...
    ...
    }
    
    我们开了一家商店
    testImplementation group: 'org.powermock', name: 'powermock-core', version: '2.0.9'
    
    FileReader fileReader = mock(FileReader.class);
    Whitebox.setInternalState(ClassToMock.class, "MEMBER_1", fileReader);