gpt4 book ai didi

testing - @SuppressStaticInitialization 用于部分模拟

转载 作者:行者123 更新时间:2023-11-28 21:18:17 25 4
gpt4 key购买 nike

我有一个奇怪的案例,我想在不触及其他功能的情况下测试“某些”功能……我很难选择合适的描述,我希望我将在下面展示的代码几乎是 self 描述的。

假设我有一个保留一些策略的类:

class TypeStrategy {

private static final CreateConsumer CREATE_CONSUMER = new CreateConsumer();
private static final ModifyConsumer MODIFY_CONSUMER = new ModifyConsumer();

private static final Map<Type, Consumer<ConsumerContext>> MAP = Map.of(
Type.CREATE, CREATE_CONSUMER,
Type.MODIFY, MODIFY_CONSUMER
);

public static void consume(Type type, ConsumerContext context) {
Optional.ofNullable(MAP.get(nodeActionType))
.orElseThrow(strategyMissing(type))
.accept(context);
}
}

这个想法很简单——有一些策略是为特定的类型注册的;方法 consume 将简单地尝试找到合适的注册类型并使用提供的 ConsumerContext 对其调用 consume

现在的问题是:我非常想测试我关心的所有策略是否都已注册,并且我可以对它们调用 accept - 这就是我想要测试的所有内容。

通常,我会在 TypeStrategy 上使用 @SuppressStaticInitializationFor 并使用 WhiteBox::setInternalState 只是将我需要的任何内容放入 CREATE_CONSUMERMODIFY_CONSUMER;但在这种情况下我不能,因为 MAP 也会被跳过,我真的不想那样,我只关心这两个策略 - 我需要 MAP 保持原样。

除了一些令人讨厌的重构之外,这确实让我到达了我想要的位置,我不知道如何实现这一目标。在最好的情况下,我希望 @SuppressStaticInitializationFor 将支持一些“部分”跳过,您可以在其中指定一些过滤器来确定您想要跳过的内容,但这不是一个选项,真的。

我还可以测试调用链上的“所有”其他内容——即测试 accept 应该做的所有事情,但是在这个测试中增加了近 70 行模拟,它变成了理解它真的想测试一个非常小的部分是一场噩梦。

最佳答案

从您的描述来看,黑盒测试似乎不是一种选择,因此也许我们可以通过模拟消费者的构造函数并验证他们的交互来依赖一些白盒测试。

您可以在下面找到从您的初始示例推断出的完整示例,包括 .orElseThrow(strategyMissing(type)) 的可能选项。

一个重要说明/免责声明:由于我们要保持 TypeStrategy 不变,这意味着 map 的静态初始化 block 将被执行。因此,我们需要特别注意消费者模拟实例。我们需要确保在初始模拟阶段添加到 map 中的相同模拟实例在所有测试中都可用,否则验证将失败。因此,我们不会为每个测试创建模拟,而是为所有测试创建一次模拟。虽然在单元测试中不建议这样做(测试应该是隔离和独​​立的),但我相信在这种特殊情况下,这是一个可以接受的不错的权衡。

import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import java.util.AbstractMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.*;
import static org.powermock.api.mockito.PowerMockito.whenNew;

// enable powermock magic
@RunWith(PowerMockRunner.class)
@PrepareForTest({MockitoTest.TypeStrategy.class})
public class MockitoTest {
private static CreateConsumer createConsumerMock;
private static ModifyConsumer modifyConsumerMock;

// static initializer in TypeStrategy => mock everything once in the beginning to avoid having new mocks for each test (otherwise "verify" will fail)
@BeforeClass
public static void setup() throws Exception {
// mock the constructors to return mocks which we can later check for interactions
createConsumerMock = mock(CreateConsumer.class);
modifyConsumerMock = mock(ModifyConsumer.class);
whenNew(CreateConsumer.class).withAnyArguments().thenReturn(createConsumerMock);
whenNew(ModifyConsumer.class).withAnyArguments().thenReturn(modifyConsumerMock);
}

@Test
public void shouldDelegateToCreateConsumer() {
checkSpecificInteraction(Type.CREATE, createConsumerMock);
}

@Test
public void shouldDelegateToModifyConsumer() {
checkSpecificInteraction(Type.MODIFY, modifyConsumerMock);
}

private void checkSpecificInteraction(Type type, Consumer<ConsumerContext> consumer) {
ConsumerContext expectedContext = new ConsumerContext();

// invoke the object under test
TypeStrategy.consume(type, expectedContext);

// check interactions
verify(consumer).accept(expectedContext);
}

@Test
public void shouldThrowExceptionForUnsupportedConsumer() {
ConsumerContext expectedContext = new ConsumerContext();

// unsupported type mock
Type unsupportedType = PowerMockito.mock(Type.class);
when(unsupportedType.toString()).thenReturn("Unexpected");

// powermock does not play well with "@Rule ExpectedException", use plain old try-catch
try {
// invoke the object under test
TypeStrategy.consume(unsupportedType, expectedContext);

// if no exception was thrown to this point, the test is failed
fail("Should have thrown exception for unsupported consumers");
} catch (Exception e) {
assertThat(e.getMessage(), is("Type [" + unsupportedType + "] not supported"));
}
}


/* production classes below */

public static class TypeStrategy {
private static final CreateConsumer CREATE_CONSUMER = new CreateConsumer();
private static final ModifyConsumer MODIFY_CONSUMER = new ModifyConsumer();

private static final Map<Type, Consumer<ConsumerContext>> MAP = Stream.of(
new AbstractMap.SimpleEntry<>(Type.CREATE, CREATE_CONSUMER),
new AbstractMap.SimpleEntry<>(Type.MODIFY, MODIFY_CONSUMER)
).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

public static void consume(Type type, ConsumerContext context) {
Optional.ofNullable(MAP.get(type))
.orElseThrow(strategyMissing(type))
.accept(context);
}

private static Supplier<IllegalArgumentException> strategyMissing(Type type) {
return () -> new IllegalArgumentException("Type [" + type + "] not supported");
}
}

public static class CreateConsumer implements Consumer<ConsumerContext> {
@Override
public void accept(ConsumerContext consumerContext) {
throw new UnsupportedOperationException("Not implemented");
}
}

public static class ModifyConsumer implements Consumer<ConsumerContext> {
@Override
public void accept(ConsumerContext consumerContext) {
throw new UnsupportedOperationException("Not implemented");
}
}

public enum Type {
MODIFY, CREATE
}

public static class ConsumerContext {
}
}

关于testing - @SuppressStaticInitialization 用于部分模拟,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55082177/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com