Java 向测试代码而不是最终用户公开方法?
鉴于: 是否有一种设计模式允许我将第一种方法公开给分散在多个包中的类,将第二种方法公开给最终用户Java 向测试代码而不是最终用户公开方法?,java,design-patterns,Java,Design Patterns,鉴于: 是否有一种设计模式允许我将第一种方法公开给分散在多个包中的类,将第二种方法公开给最终用户 我不想向最终用户公开第一个构造函数,因为它会扰乱API规范,我不想让它形成事实上的标准(类似于sun.misc.Unsafe)。解决方案不能使用依赖项注入或反射。使用最终类和限制作为反射用法,我可以向您推荐 其他选择,但这些都是棘手的解决方案。 我看不到任何简单的设计模式可以解决您的问题。我们受到Java语言规范的限制 您可以依赖于包专用修饰符,该修饰符允许 在API中定义内部对象/处理,并简化AP
我不想向最终用户公开第一个构造函数,因为它会扰乱API规范,我不想让它形成事实上的标准(类似于
sun.misc.Unsafe
)。解决方案不能使用依赖项注入或反射。使用最终类和限制作为反射用法,我可以向您推荐
其他选择,但这些都是棘手的解决方案。
我看不到任何简单的设计模式可以解决您的问题。我们受到Java语言规范的限制
您可以依赖于包专用
修饰符,该修饰符允许
在API中定义内部对象/处理,并简化API的单元测试
所以你可以:
- 声明不带修饰符的方法(因此包私有)。
这样,如果他们使用不同的包,就不应该访问它李>
- 在同一个包中但在测试文件夹中创建测试类,以使包私有方法可见
您的lib可以有智能客户端,它们可以通过使用相同的包级别进行欺骗(应该很少)。我认为您不应该在应用程序运行时关心它,因为它们是有意的复杂操作,而不是典型的API使用
我看到了另一个解决方案,它可以提供一个完美的API,但要复杂得多:当您打包API时,在编译类之前,运行一个任务,删除源代码中不需要的方法。
如果是单一方法,则不应非常复杂,但如果是多种方法,则可能会变得难以操作和维护。您的问题标题提到了测试,但问题本身没有提到。我将假设您这样做是为了测试目的
一般来说,编写专门用于测试的生产代码是一个非常糟糕的主意。所有的生产代码都应该有一个存在的理由。交付到生产环境中的所有代码都应该在某个时候在生产环境中执行,否则就不应该在生产环境中执行。在那里进行维护需要花费金钱和时间,并可能带来安全风险
您应该开始对代码进行单元测试,就好像您是最终用户,而不是了解被测类的实现细节的特权用户一样——这通常被称为黑盒测试。这里的目的是让您更容易更改被测试单元的实现细节,而不必更改测试。通常这是通过先编写测试来完成的。被测单元的所有合作者都应该被模拟或存根,您应该为此调查Mockito
最后,您说解决方案不能使用DI或反射。这种限制非常奇怪,与最佳实践背道而驰。强烈鼓励您改变这一点
解决方案不能使用依赖项注入或反射
如果不能使用反射,则只能访问可访问的成员
专用修饰符
测试类与生产类是不同的类,也不是生产类的内部类。所以它不能访问私有成员。(内部类只能访问外部类的私有成员,因为编译器使用包私有范围创建合成访问方法)
因此,让我们尝试下一个修改器
套餐专用
您可以将测试类与生产类放在同一个包中。然后您可以访问包私有成员。但这也意味着同一个包中的所有其他生产代码都可以访问它们
因此,包私有范围对您来说已经太大了
但如果不能使用反射,就无法访问私有成员。所以私人会员也不适合你。最后,java中没有留下访问修饰符。TL;博士
使用模拟框架,例如,模拟环境
不要在生产代码中强行使用仅测试的实现
它不仅不必要地增加了将要分发的二进制文件的大小,而且正如您所了解的,这很容易使类型的接口变得混乱
即使它不是您正在污染的公共接口,也可能会在团队中造成混乱,因此最好避免将仅测试代码与生产代码放在一起。测试代码应该只存在于它所属的地方:在测试中
你为什么一开始就要这么做?
您在评论中提到,这是您模拟软件环境的方式。模拟sysenv()
和System.getProperty(String)
的问题在于它们是静态方法。这意味着您不能重写这些方法来模拟它们的返回值
您的解决方案似乎是通过SingletonScope
接口访问这些值,这是一种正确的想法(因为您可以简单地模拟SingletonScope
)。问题在于,SingletonScope
仅存在于测试目的:您仅依赖它,以便可以模拟测试值
解决方案
您需要在启动测试之前模拟环境。使用PowerMock,您可以在测试期间将静态方法的返回值设置为特定值
它看起来像:
public final class Dummy
{
/**
* Constructor meant to be used for test code.
* @param scope test-specific "globals"
* @param value some user-supplied value
*/
public Dummy(SingletonScope scope, int value)
{
// ...
}
/**
* Constructor meant to be used by end-users.
* @param value some user-supplied value
*/
public Dummy(int value)
{
scope = someDefaultValue();
// ...
}
}
Map mockedenviormentvalues=。。。;
when(System.sysenv())。然后返回(mockedenviormentvalues);
属性mockedSystemProperties=。。。;
当(System.getProperties())。然后返回(mockedSystemProperties);
他们说这是不可能的,但这里有一个对我来说确实有效的解决方案:
- 将
Dummy
重命名为AbstractDummy
- 将其可访问性从
public
更改为package protected
- 创建新的cl
Map<String, String> mockedEnviornmentValues = ...;
when(System.sysenv()).thenReturn(mockedEnviornmentValues);
Properties mockedSystemProperties = ...;
when(System.getProperties()).thenReturn(mockedSystemProperties);
abstract class AbstractDummy
{
/**
* Constructor common to all implementations.
* @param scope scope of "global" variables
* @param value some user-supplied value
*/
AbstractDummy(SingletonScope scope, int value)
{
// ...
}
}
public final class Dummy extends AbstractDummy
{
/**
* Constructor meant to be used by end-users.
* @param value some user-supplied value
*/
public Dummy(int value)
{
super(MainSingletonScope.INSTANCE, value);
}
}
public final class Dummy extends AbstractDummy
{
/**
* Constructor meant to be used for test code.
* @param scope test-specific "globals"
* @param value some user-supplied value
*/
public Dummy(SingletonScope scope, int value)
{
super(scope, value);
}
}