简体   繁体   English

如何用白盒实例化一个抽象类,以用可以在其中操纵参数的测试逻辑替换方法

[英]How to Whitebox instantiate an abstract class for the purpose of replacing a method with test logic where I can manipulate the arguments

Preamble ( please read ): 序言( 请阅读 ):

  • Dealing with legacy code that I can't refactor without an act of congress (but I have the sources). 处理遗留代码, 如果没有国会行为我无法重构 (但是我有消息来源)。
  • This legacy code interacts with a lot of abstract classes (and their various Implementations) 这些旧代码与许多抽象类 (及其各种实现)进行交互
  • Mocking is not enough because I need to intercept the method arguments sent to the abstract methods and manipulate them inside my replacement (test) logic 模拟还不够,因为我需要拦截发送给抽象方法的方法参数,并在替换(测试)逻辑中对其进行操作
  • I know I could just extend those abstract classes on my test package and provide implementations for the methods I want to replace. 我知道我可以在测试包中扩展这些抽象类,并提供要替换的方法的实现。 However the goal of this question is to see if there's a way around it, because in the case of abstract classes that have a ton of abstract methods (think SocketChannel from NIO) this is a ton of useless boilerplate code that makes the unit test unreadable. 但是,此问题的目的是查看是否有解决办法,因为在具有大量抽象方法的抽象类的情况下(例如NIO的SocketChannel),这是一堆无用的样板代码,使单元测试难以理解。
  • I know that the legacy code is poorly designed and that this is not the way of writing clean well designed unit-tests; 我知道遗留代码的设计很差,并且这不是编写设计良好的单元测试的方式; that's not in question. 没问题。 Just know that the Legacy code can't be changed easily. 只知道传统代码无法轻易更改。

The question is : How to accomplish this (using PowerMock ) without getting an exception from PowerMock saying that this class can't be instantiated because it's abstract: 问题是 :如何做到这一点(使用PowerMock )而又不会从PowerMock处获得异常,因为该类是抽象的,因此无法实例化该类:

@PrepareForTest({SocketChannel.class})
@RunWith(PowerMockRunner.class)
public class TestThatUsesSocketChannelChannel
{
    replace(method(SocketChannel.class, "read", ByteBuffer.class)).with((proxy, method, args) -> 
    {
        // Line below intercepts the argument and manipulates it 
        ((ByteBuffer) args[0]).clear();
    });
    // The line below throws an exception (because SocketChannel is abstract)
    SocketChannel socketChannel = Whitebox.newInstance(SocketChannel.class);
    // Once here, ideally I can continue my test
}

You can simply mock the abstract class by using plain Mockito: 您可以使用普通的Mockito简单地模拟抽象类:

AbstractClass theMock = mock(AbstractClass.class);

Then inject this mock of the abstract class via PowerMock. 然后通过PowerMock注入抽象类的这个模拟。

Found the answer : Because SocketChannel is an abstract class, in order to use Whitebox.newInstance, there needs to be a preliminary step with ConcreteClassGenerator. 找到了答案 :由于SocketChannel是一个抽象类,为了使用Whitebox.newInstance,需要使用ConcreteClassGenerator进行初步准备。 This creates a run time child of SocketChannel with empty method implementations. 这将创建带有空方法实现的SocketChannel的运行时子级。 Notice that before that I replaced the method I care for. 请注意,在此之前,我已替换了所需的方法。 Conclusion: this allows me to instantiate (an on-the-fly) child of an abstract class without me having to extend it explicitly. 结论:这使我可以实例化(即时)抽象类的子级,而无需显式扩展它。 See the code above, now made to work with such intermediary step: 请参阅上面的代码,现在可以与此类中间步骤一起使用:

@PrepareForTest({SocketChannel.class})
@RunWith(PowerMockRunner.class)
public class TestThatUsesSocketChannelChannel
{
    replace(method(SocketChannel.class, "read", ByteBuffer.class)).with((proxy, method, args) -> 
    {
        // Line below intercepts the argument and manipulates it 
        ((ByteBuffer) args[0]).clear();
    });
    // THIS Fixes it: generate a an on-the-fly class that implements stub methods
    // for the ones that are abstract and then (and only then) pass that to Whitebox
    Class<?> concreteSocketChannelClass = new ConcreteClassGenerator().createConcreteSubClass(SocketChannel.class);
    SocketChannel socketChannel = (SocketChannel) Whitebox.newInstance(concreteSocketChannelClass);
    // Once here, ideally I can continue my test
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM