Java 在公共方法中拦截私有方法返回的对象

Java 在公共方法中拦截私有方法返回的对象,java,junit,mockito,junit4,powermock,Java,Junit,Mockito,Junit4,Powermock,我需要对一个方法进行单元测试,并且我希望模拟该行为,以便能够测试该方法中必要的代码部分 为此,我希望访问由我尝试测试的方法中的私有方法返回的对象。我创建了一个示例代码,给出了我试图实现的基本概念 主类 Class Main { public String getUserName(String userId) { User user = null; user = getUser(userId); if(user.getName().equals("Stack")) {

我需要对一个方法进行单元测试,并且我希望模拟该行为,以便能够测试该方法中必要的代码部分

为此,我希望访问由我尝试测试的方法中的私有方法返回的对象。我创建了一个示例代码,给出了我试图实现的基本概念

主类

Class Main {
  public String getUserName(String userId) {
    User user = null;
    user = getUser(userId);
    if(user.getName().equals("Stack")) {
      throw new CustomException("StackOverflow");
    }

    return user.getName();

 }

 private User getUser(String userId) {
  // find the user details in database
  String name = ""; // Get from db
  String address = ""; // Get from db
  return new User(name, address);
 }
}
测试班

@Test (expected = CustomException.class)
public void getUserName_UserId_ThrowsException() {
  Main main = new Main();
  // I need to access the user object returned by getUser(userId)
  // and spy it, so that when user.getName() is called it returns Stack
  main.getUserName("124");
}  

您可以使用PowerMock的mockPrivate,但我不推荐它。 如果你有这样一个问题,通常意味着你的设计是糟糕的。
为什么不保护该方法?

只有两种方法可以访问私有:

  • 使用反射
  • 扩大范围
  • 也许在等待Java9使用新的作用域机制
  • 我会将范围修饰符从private更改为package范围。使用反射对于重构来说是不稳定的。如果您使用PowerMock之类的帮助程序,这并不重要。它们只减少反射周围的锅炉板代码

    但最重要的一点是,不要在whitbox测试中进行太深的测试。这会导致测试设置爆炸。试着将代码切成小块。

    方法“getUserName”需要从用户对象获得的唯一信息是名称。它将验证名称,并引发异常或返回异常。因此,在测试中不必引入用户对象

    因此,我的建议是,您应该将从用户对象检索名称的代码提取到一个单独的方法中,并将此方法作为包的作用域。现在不需要模拟用户对象,只需模拟主对象。但该方法只有很少的可用信息才能正常工作

    class Main {
    
        public String getUserName(String userId) {
            String username = getUserNameFromInternal(userId);
            if (userName.equals("Stack")) {
                throw new CustomException("StackOverflow");
            }
            return user.getName();
        }
    
        String getUserNameFromInternal(String userId) {
            User user = getUser(userId);
            return user.getName();
        }
    
        ...
    
    }
    
    测试:

    @Test (expected = CustomException.class)
    public void getUserName_UserId_ThrowsException() {
      Main main = Mockito.mock(new Main());
      Mockito.when(main.getUserNameInternal("124")).thenReturn("Stack");
      main.getUserName("124");
    }
    

    在私有方法中调用
    new
    的问题

    答案是不要求助于PowerMock;或更改该方法的可见性

    合理的答案是将对“给我一个用户对象的东西”的依赖“提取”到它自己的类中;并向“Main”类提供该类的实例。因为这样你就可以简单地模仿“工厂”对象;让它做你想做的事

    意思是:您当前的代码很难测试。与其解决由此引起的问题,不如花时间学习如何编写易于测试的代码;例如,通过观察这些作为起点


    给出您的最新评论:当您处理遗留代码时,您确实希望使用PowerMockito。要理解的关键部分是:不要“模仿”那个私有方法;您宁愿考虑模拟对
    新用户()的调用;如前所述。

    我不能确切地说,使方法受到保护就是提高代码质量,而是正确地模拟“从数据库获取”部分。@Egor我不确定我是否完全遵循,但模拟私有方法如何帮助我创建一个监视用户对象?我文章中的代码只是一个示例。想象一下getUserName以多种方式使用User对象的情况。假设我们在getUserName中调用了大约15个用户对象方法。但我只想模拟其中一个方法调用的行为。我该怎么做呢?@bharathp如果一个方法对某个对象进行了15次其他调用,那么这再次表明设计糟糕,有人不了解OO的用途;-)如果在getUserName中调用15个方法,则不会影响将该方法置于测试之下的方法。但正如@GhostCat提到的,这是一个糟糕设计的指标,你应该质疑你是否违反了单一责任原则。但是,将该方法分成几部分并进行测试仍然是潜在重构的第一步。@GhostCat这只是一个示例,可能是一个错误的示例:D假设我正在尝试构建一个对象的JSON表示字符串,这将要求我调用buildJSON方法中该对象的所有getter。这也会被认为是糟糕的设计吗?假设我不能使用任何json库,请你详细说明你的第三段。不幸的是,我正在测试的代码是遗留代码,我不会花时间更新它。非常感谢播放列表的链接。我的意思是:如果你想在这里写你自己的代码;然后你可能想退一步,学习。。。如何以“更好”的方式做到这一点。但是,当处理您无法触及的现有代码时,PowerMock是唯一的答案。请参阅我的更新答案及其第4段;-)我不认为这是一个遗留代码和“新”代码的问题。在这种情况下,问题是白盒测试或黑盒测试。问题是:您是关心控制流,还是关心在不知道如何产生特定结果的输入参数。如果您关心控制流,那么您就隐式地关心代码质量。如果您只想确保对象方法生成与规范匹配的结果,那么就没有真正的句柄来强制执行代码质量,因为每个控制流都变成了“实现细节”。当然,一般来说扩展范围不是一个好主意。但是,只要JAVA没有引入新的语言机制,在不扩展方法作用域的情况下使白盒测试更容易,那么这就是详细实施代码质量的唯一方法。在测试情况下,您还有其他访问需求,而JAVA目前没有解决这个问题。代码质量不仅与对象依赖性有关,还与控制流有关。而黑盒测试本质上拒绝查看内部。