spring-boot - @SpringBootTest vs @ContextConfiguration vs @Import 在 Spring Boot 单元测试中
问题描述
我正在开发一个 Spring Boot 项目。我正在编写一个Unit Test
基于TDD
它有点困难的代码。
@SpringBootTest
加载了所有 bean,这导致了更长的测试时间。
所以我使用了@SpringBootTest
's 类名称。
我正常完成了测试,但我不确定 using@ContextConfiguration
和 using之间的区别@Import
。
所有三个选项都运行正常。我想知道哪种选择是最好的。
@Service
public class CoffeeService {
private final CoffeeRepository coffeeRepository;
public CoffeeService(CoffeeRepository coffeeRepository) {
this.coffeeRepository = coffeeRepository;
}
public String getCoffee(String name){
return coffeeRepository.findByName(name);
}
}
public interface CoffeeRepository {
String findByName(String name);
}
@Repository
public class SimpleCoffeeRepository implements CoffeeRepository {
@Override
public String findByName(String name) {
return "mocha";
}
}
Option 1 (SpringBootTest Annotation) - OK
@RunWith(SpringRunner.class)
@SpringBootTest(classes = {CoffeeService.class, SimpleCoffeeRepository.class})
public class CoffeeServiceTest {
@Autowired
private CoffeeService coffeeService;
@Test
public void getCoffeeTest() {
String value = coffeeService.getCoffee("mocha");
assertEquals("mocha", value);
}
}
Option 2 (ContextConfiguration Annotation) - OK
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {SimpleCoffeeRepository.class, CoffeeService.class})
public class CoffeeServiceTest {
@Autowired
private CoffeeService coffeeService;
@Test
public void getCoffeeTest() {
String value = coffeeService.getCoffee("mocha");
assertEquals("mocha", value);
}
}
Option 3 (Import Annotation) - OK
@RunWith(SpringRunner.class)
@Import({SimpleCoffeeRepository.class, CoffeeService.class})
public class CoffeeServiceTest {
@Autowired
private CoffeeService coffeeService;
@Test
public void getCoffeeTest() {
String value = coffeeService.getCoffee("mocha");
assertEquals("mocha", value);
}
解决方案
如果您的意图是运行适当的单元测试,我认为所有 3 个选项都是不好的。单元测试必须非常快,你应该能够在一秒钟左右运行数百个(当然取决于硬件,但你明白了)。因此,一旦您说“我为每个测试开始 spring”-它就不再是单元测试了。每次测试都启动弹簧是一项非常昂贵的操作。
有趣的是,您的代码以CoffeeService
完全可测试的方式编写:只需使用 Mockito 之类的库来模拟存储库类,您就可以测试服务逻辑而无需任何弹簧。您不需要任何弹簧跑步者,任何弹簧注释。您还将看到这些测试运行得更快。
class MyServiceTest {
@Test
public void test_my_service_get_coffee_logic() {
// setup:
CoffeeRepository repo = Mockito.mock(CoffeeRepository.class);
Mockito.when(repo.findByName("mocha")).thenReturn("coffeeFound");
CoffeeService underTest = new CoffeeService(repo);
// when:
String actualCoffee = underTest.getCoffee("mocha");
// then:
assertEquals(actualCoffee, "coffeeFound");
}
}
现在关于弹簧测试库
您可以将其视为一种测试代码的方法,该代码需要与其他组件进行一些互连,并且将所有内容都模拟出来是有问题的。它是同一个 JVM 内的一种集成测试。您提出的所有方式都运行应用程序上下文,这实际上是一件非常复杂的事情,youtube 上有关于应用程序上下文启动期间真正发生的事情的完整会话 - 尽管超出了问题的范围,关键是执行上下文启动需要时间
@SpringBootTest
更进一步并尝试模仿 Spring Boot 框架添加的用于创建上下文的过程:根据包结构确定要扫描的内容,从预定义的位置加载外部配置,可选地运行自动配置启动器等等。
现在,可能加载应用程序中所有 bean 的应用程序上下文可能非常大,并且对于某些测试,它不是必需的。它通常取决于测试的目的是什么
例如,如果您测试休息控制器(您已正确放置所有注释),您可能不需要启动数据库连接。
您所展示的所有方式都过滤了应该运行的确切内容、要加载的 bean 以及相互注入的内容。
通常,这些限制适用于“层”而不是单个 bean(层 = 休息层、数据层等)。
第二种和第三种方法实际上是相同的,它们是“过滤”应用程序上下文的不同方法,只保留必要的 bean。
更新:
由于您已经完成了方法的性能比较:
单元测试 = 非常快的测试,其目的是验证您编写的代码(当然也可以是您的一位同事)所以如果您运行 Spring,它自动意味着相对较慢的测试。所以回答你的问题
使用@ContextConfiguration 是否可以是“单元测试”
不,它不能,它是一个在春季只运行一个类的集成测试。
通常,我们不会只使用 Spring Framework 运行一个类。如果只想测试一个类(一个单元)的代码,在spring容器中运行它有什么好处呢?是的,在某些情况下,它可以是几个类,但不是几十个或几百个。
如果您使用 spring 运行一个类,那么无论如何,您都必须模拟它的所有依赖项,mockito 也可以这样做......
现在关于您的问题
@ContextConfiguration 与 @SpringBootTest 技术差异。
@SpringBootTest
仅当您有 Spring Boot 应用程序时才相关。该框架在底层使用 Spring,但简而言之,它附带了许多预定义的配方/实践,用于编写应用程序的“基础架构”:
- 配置管理,
- 封装结构,
- 可插拔性
- 日志记录
- 数据库集成等
因此 Spring Boot 建立了定义良好的流程来处理上述所有项目,如果您想启动将模仿 Spring Boot 应用程序的测试,那么您使用@SpringBootTest
注解。否则(或者如果您只有弹簧驱动的应用程序而不是弹簧靴) - 根本不要使用它。
@ContextConfiguration
不过是完全不同的事情。它只是说你想在 Spring 驱动的应用程序中使用什么 bean(它也适用于 spring boot)
“单元测试”是使用@ContextConfiguration 的正确方法吗?或不?
正如我所说 - 所有与弹簧测试相关的东西都仅用于集成测试,所以不,这是在单元测试中使用的错误方法。对于单元测试,使用根本不使用 spring 的东西(比如 mockito for mocks 和没有 spring runner 的常规 junit 测试)。
推荐阅读
- arrays - 在 Powershell 中修改 ArrayList 也会修改原来的数组
- button - Mailchimp 按钮在 Gmail 中使用倾斜 CSS 渲染效果不佳
- django - 如何防止浏览器下载文件并自动更改其名称
- python - 如何从分类和数值数据中绘制堆积条形图
- discord.js - 如何使用 channelid 获取特定的不和谐机器人频道?
- r - parLapply - 如何解决错误“找不到函数“bindToEnv””?
- python - 以最少迭代次数分割字符串的优化方法
- python - PyTorch 和 Numpy 中的张量入口选择逻辑发散
- css - 如何向 Slider 添加线性渐变颜色?
- reactjs - 如何防止父组件内部内容溢出?