java 受试单元:植入物或接口?

czq61nw1  于 2024-01-05  发布在  Java
关注(0)|答案(5)|浏览(115)

假设我有接口和实现它的实现类,我想为此编写单元测试,我应该测试接口还是实现?
下面是一个示例:

  1. public interface HelloInterface {
  2. public void sayHello();
  3. }
  4. public class HelloInterfaceImpl implements HelloInterface {
  5. private PrintStream target = System.out;
  6. @Override
  7. public void sayHello() {
  8. target.print("Hello World");
  9. }
  10. public void setTarget(PrintStream target){
  11. this.target = target;
  12. }
  13. }

字符串
所以,我有HelloInterface和实现它的HelloInterfaceImpl。什么是被测单元接口或Impl?
我认为应该是HelloInterface。考虑下面的JUnit测试草图:

  1. public class HelloInterfaceTest {
  2. private HelloInterface hi;
  3. @Before
  4. public void setUp() {
  5. hi = new HelloInterfaceImpl();
  6. }
  7. @Test
  8. public void testDefaultBehaviourEndsNormally() {
  9. hi.sayHello();
  10. // no NullPointerException here
  11. }
  12. @Test
  13. public void testCheckHelloWorld() throws Exception {
  14. ByteArrayOutputStream out = new ByteArrayOutputStream();
  15. PrintStream target = new PrintStream(out);
  16. PrivilegedAccessor.setValue(hi, "target", target);
  17. //You can use ReflectionTestUtils in place of PrivilegedAccessor
  18. //really it is DI
  19. //((HelloInterfaceImpl)hi).setTarget(target);
  20. hi.sayHello();
  21. String result = out.toString();
  22. assertEquals("Hello World", result);
  23. }
  24. }


主线实际上是我注解掉的一条。
第一个月
方法setTarget()不是我的公共接口的一部分,所以我不想 * 意外地 * 调用它。如果我真的想调用它,我应该花点时间考虑一下。例如,它帮助我发现我真正想做的是依赖注入。它为我打开了整个世界的新机会。我可以使用一些现有的依赖注入机制(例如Spring的),我可以自己模拟它,就像我在代码中实际做的那样,或者采取完全不同的方法。仔细看看,PrintSort的准备并不那么容易,也许我应该使用mock对象来代替?

编辑:我认为我应该 * 始终 * 关注接口。从我的Angular 来看,setTarget()也不是impl类的“合约”的一部分,它为依赖注入提供了sally。我认为从测试的Angular 来看,Impl类的任何公共方法都应该被视为私有的。但这并不意味着我忽略了实现细节。

参见Should Private/Protected methods be under unit test?

EDIT-2在多个实现\多个接口的情况下,我会测试所有的实现,但是当我在setUp()方法中声明一个变量时,我肯定会使用interface。

tyg4sfes

tyg4sfes1#

实现是需要测试的单元,当然也是你正在示例化的,包含了程序/业务逻辑。
如果你有一个关键的接口,并且你想确保每个实现都正确地遵循它,那么你可以编写一个测试套件,专注于接口,并要求传入一个示例(与任何实现类型无关)。
是的,对于PrintStream使用Mockito可能会更容易,可能并不总是可以避免使用mock对象,就像您在这个特定示例中所做的那样。

pengsaosao

pengsaosao2#

我总是测试实现--一个类可以实现多个接口,一个接口也可以由多个类实现--每个类都应该被测试所覆盖。
在单元测试中调用setter的要求(将接口转换为实现):

  1. ((HelloInterfaceImpl)hi).setTarget(target);

字符串
这不是合约的一部分,但这是使实现工作的重要部分,应该进行适当的测试。
让我们以JDK为例。您有接口List和两个实现:ArrayListLinkedList。另外,LinkedList实现了Deque接口。如果您为List接口编写测试,您会覆盖什么?数组还是链表?更重要的是,如果是LinkedList,您会选择测试什么接口?DequeList?正如您所看到的,当您测试实现时,您不会遇到这样的问题。
就我个人而言,在单元测试中将接口转换为实现是出问题的明显迹象;)

xxhby3vn

xxhby3vn3#

我会测试接口。
我认为错误在于以这样一种方式编写实现,即它是硬连接的,可以写入System.out;你没有办法用另一个PrintStream重写。我会使用构造函数而不是setter。这样就不需要mock或强制转换。
这是一个简单的例子。我想象一个更复杂的例子会有一个工厂来创建不同的,更复杂的接口实现。希望你不会设计成这样,你会被困住。
在测试中坚持使用接口也会使模拟变得容易得多。

  1. public class HelloInterfaceImpl implements HelloInterface {
  2. private PrintStream target;
  3. public HelloInterfaceImpl() {
  4. this(System.out);
  5. }
  6. public HelloInterfaceImpl(PrintStream ps) {
  7. this.target = ps;
  8. }
  9. @Override
  10. public void sayHello() {
  11. target.print("Hello World");
  12. }
  13. }

字符串
测试如下:

  1. public class HelloInterfaceTest {
  2. @Test
  3. public void testDefaultBehaviourEndsNormally() {
  4. HelloInterface hi = new HelloInterfaceImpl();
  5. hi.sayHello();
  6. }
  7. @Test
  8. public void testCheckHelloWorld() throws Exception {
  9. ByteArrayOutputStream out = new ByteArrayOutputStream();
  10. PrintStream target = new PrintStream(out);
  11. HelloInterface hi = new HelloInterfaceImpl(target);
  12. hi.sayHello();
  13. String result = out.toString();
  14. assertEquals("Hello World", result);
  15. }
  16. }

展开查看全部
uqdfh47h

uqdfh47h4#

我会说这取决于实现和它在接口契约之外的作用。许多实现只实现接口中提供的功能,在其他情况下,接口只是类功能的一小部分。它可能实现多个接口。
最后,您将测试实现。
在一个像你定义的简单案例中,我说的是半斤八两。把你的测试用例写在接口或实现上,只要它充分测试了 * 实现 *,结果是一样的。
举一个不同的例子,我有一个类,它通过装饰真实的读取器和写入器来收集通信信道上的统计数据。我的类现在可以实现这些接口,但它也收集统计数据,这与任何一个合约都没有关系。我当然仍然可以基于这些接口编写测试,但它不会完全测试这个类。

7gs2gvoe

7gs2gvoe5#

我喜欢将接口标注为完整的合约(添加前置条件和后置条件标注),它以半正式的方式指定了实现接口的任何类的需求。从那里你可以生成或编写合约遵从性测试。任何实现了许多接口/合约的类都必须通过它实现的所有合约的测试。
其优点是测试绑定到需求(接口升级为合约),并且任何声称实现合约的新类都要测试它是否实现。

相关问题