繁体   English   中英

如何测试没有参数的 void 方法?

[英]How to test void method with no parameters?

我有另一个服务调用的方法,它只是更改数据库中某些行的一个字段。 方法如下所示:

void errorOrders() {
    List<Orders> orders = OrderDAO.getErrorOrders(); //select only fields with status 'error'
    orders.forEach(order -> order.setStatus(OrderStatus.NEW);
    //some logging etc.
}

有没有办法对这个方法进行单元测试? 我可以将自己注入此方法并检查订单状态是否已更改?

干杯!

我建议您重构您的 class 以使您的代码可测试。 理想情况下,您将注入代表OrderDAO的依赖项:

class ErrorChecker {
    private final OrderDAO orderDAO;

    public ErrorChecker(OrderDAO orderDAO) {
        this.orderDAO = orderDAO;
    }

    public void errorOrders() {
        List<Orders> orders = orderDAO.getErrorOrders();
        orders.forEach(order -> order.setStatus(OrderStatus.NEW);
    }
}

然后您的测试代码将如下所示:

@Test
void testErrorOrders() {
    Order order1 = mock(Order.class);
    Order order2 = mock(Order.class);
    OrderDAO orderDAO = mock(OrderDAO.class);
    when(orderDAO.getErrorOrders()).thenReturn(List.of(order1, order2));
    ErrorChecker errorChecker = new ErrorChecker(orderDAO);
    errorChecker.errorOrders();
    verify(order1).setState(OrderStatus.NEW);
    verify(order2).setState(OrderStatus.NEW);
}

有一些方法可以模拟 static 方法,但我建议重构以注入依赖项,因为它除了可测试性之外还有许多其他好处。

如果您需要将该方法保留为 static 那么您仍然可以模拟它(在 Mockito 的 v3.4+ 中):

@Test
void testErrorOrders() {
    try (MockedStatic mocked = mockStatic(OrderDAO.class)) {
        mocked.when(OrderDAO.getErrorOrders()).thenReturn(List.of(order1, order2));
        ErrorChecker errorChecker = new ErrorChecker(orderDAO);
        errorChecker.errorOrders();
        mocked.verify(order1).setState(OrderStatus.NEW);
    }
}

@ismail 和 @khelwood 已经提供了很好的答案。

  1. 如果您模拟 Object,您可以控制/查看它会发生什么
  2. 如果您更改 Object,您可以通过公共方法访问 state,请使用这些
  3. 如果您更改 Object 其 state 您无法使用普通代码访问,请使用 Java 反射查看成员变量。
  4. 如果您设置对象,将其数据传递给流和其他 output,您可以在其间放置一些额外的流等。 如有必要,使用 inheritance 和反射

在屏蔽 class 上使用反射的简单示例:

package stackoverflow.simplefieldaccess;

public class ShieldedClass {

    private int mStatus;

    public ShieldedClass() {
        mStatus = 666;
    }

    public void setStatus(final int pStatus) {
        mStatus = pStatus; // usually with ints be careful and do checks here, but for the sake of simplicity we leave that out
    }

    @Override public String toString() {
        return getClass().getSimpleName() + "[status:" + mStatus + "]";
    }

}

通过反射以几种方式访问它的代码:

package stackoverflow.simplefieldaccess;

import java.lang.reflect.Field;

import jc.lib.lang.reflect.JcFieldAccess;

public class SimpleFieldAccess {

    public static void main(final String[] args) throws NoSuchFieldException, SecurityException {
        final ShieldedClass so = new ShieldedClass();
        System.out.println("Object.status before change: " + so);
        so.setStatus(667);
        System.out.println("Object.status after change: " + so);

        System.out.println();
        System.out.println("Accessing Object.status via Reflection...");
        final Class<? extends ShieldedClass> cls = so.getClass();
        final Field fieldToChance = cls.getDeclaredField("mStatus");

        {
            System.out.println("\nBad read access");
            try { // will result in java.lang.IllegalAccessException
                System.out.println("\tReading Object.status fiels via Reflection: " + fieldToChance.getInt(so));
                throw new IllegalStateException("UNEXOECTED ERROR!");
            } catch (final java.lang.IllegalAccessException e) {
                System.out.println("\tAs expected: IllegalAccessException");
            }
        }

        {
            System.out.println("\nBad write access");
            try { // will result in java.lang.IllegalAccessException
                fieldToChance.set(so, Integer.valueOf(1337));
                System.out.println("\tObject.status after change: " + so);
            } catch (final java.lang.IllegalAccessException e) {
                System.out.println("\tAs expected: IllegalAccessException");
            }
        }

        {
            System.out.println("\nGood manual read and write access");
            final boolean isFieldOriginallyAccessible = fieldToChance.isAccessible();
            try { // will result in java.lang.IllegalAccessException
                if (!isFieldOriginallyAccessible) fieldToChance.setAccessible(true);
                System.out.println("\tReading Object.status field via Reflection: " + fieldToChance.getInt(so));
                fieldToChance.set(so, Integer.valueOf(4321));
                System.out.println("\tObject.status after change: " + so);
            } catch (final java.lang.IllegalAccessException e) {
                e.printStackTrace();
            } finally {
                if (!isFieldOriginallyAccessible) fieldToChance.setAccessible(false);
            }
        }

        {
            System.out.println("\nGood automated read and write access");
            try (JcFieldAccess fa = new JcFieldAccess(fieldToChance)) { // will result in java.lang.IllegalAccessException
                System.out.println("\tReading Object.status field via Reflection: " + fieldToChance.getInt(so));
                fieldToChance.set(so, Integer.valueOf(123));
                System.out.println("\tObject.status after change: " + so);
            } catch (final java.lang.IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    }

}

对于反射,当我想访问字段时,我使用我的自制 class 可以更轻松地访问该字段,然后将其恢复正常(上面的最后一个示例使用这个):

package jc.lib.lang.reflect;

import java.io.Closeable;
import java.lang.reflect.AccessibleObject;

public class JcFieldAccess implements Closeable {

    private final AccessibleObject  mField;

    private final boolean           mIsAccessible;

    public JcFieldAccess(final AccessibleObject pField) {
        mField = pField;

        mIsAccessible = mField.isAccessible();
        if (!mIsAccessible) mField.setAccessible(true);
    }
    
    @Override public void close() {
        if (mIsAccessible) return;
        if (mField != null) mField.setAccessible(false);
    }
    
}

这个实用程序 class 的技巧是,当在 try-resource 块中使用时,无论块是否失败,它的close()方法都会自动调用。 这与在finally块中调用close()或在本例中为setAccessible(false)调用相同,但需要进行一些额外的检查。

让 class 为:

class HandleErrorOrders {
    private OrderDAO orderDAO;

    HandleErrorOrders(final OrderDAO orderDAO) {
        this.orderDAO = orderDAO;
    }

    public void errorOrders() {
        List<Orders> orders = OrderDAO.getErrorOrders(); //select only fields with         status 'error'
        orders.forEach(order -> order.setStatus(OrderStatus.NEW);
        //some logging etc.
    }
}

您需要使用assert方法检查结束 state。 要测试,请编写如下内容:

class HandleErrorOrdersTest {

    @Mock
    private OrderDAO orderDAO;

    @InjectMocks
    private HandleErrorOrders handleErrorOrders;

    @Test
    void testErrorOrders() {
        Order order1 = mock(Order.class);
        Order order2 = mock(Order.class);
        
        when(orderDAO.getErrorOrders()).thenReturn(List.of(order1, order2));
        
        ErrorChecker errorChecker = new ErrorChecker(orderDAO);
        errorChecker.errorOrders();

        //asset checks
        Assert.assertEquals(OrderStatus.NEW, order1.getStatus());
        Assert.assertEquals(OrderStatus.NEW, order2.getStatus());

        //verification checks
        Mockito.verify(orderDAO).getErrorOrders();
    }
}

暂无
暂无

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

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