SpringBoot Test详解
目录
- spring-boot-starter-test
-
- 1、概述
- 2、常用注解
-
- 2.1、配置类型的注解
- 2.2、Mock类型的注解
- 2.3、自动配置类型的注解
- 2.4、启动测试类型的注解
- 2.5、相似注解的区别和联系
- 3、SpringBootTest和Junit的使用
-
- 3.1、单元测试
- 3.2、集成测试
- 4、MockMvc
-
- 4.1、简单示例
- 4.2、自动配置
- 4.3、使用方式
-
- 1、测试逻辑
- 2、MockMvcBuilder
- 3、MockMvcRequestBuilders
- 4、ResultActions
- 5、ResultMatchers
- 6、MvcResult
- 5、业务代码
- 6、分层测试
-
- 6.1、Dao层测试
- 6.2、Service层测试
- 6.3、Controller层测试
- 7、JSON接口测试
spring-boot-starter-test
1、概述
SpringBoot对单元测试的支持在于提供了一系列注解和工具的集成,它们是通过两个项目提供的:
- spring-boot-test项目:包含核心功能
- spring-boot-test-autoconfigure项目:支持自动配置
通常情况下,我们通过spring-boot-starter-test的Starter来引入SpringBoot的核心支持项目以及单元测试项目以及单元测试库。
spring-boot-starter-test包含的类库如下:
- JUnit:一个Java语言的单元测试框架
- Spring Test & Spring Boot Test:为SpringBoot应用提供集成测试和工具支持
- AssertJ::支持流式断言的Java测试框架
- Hamcrest:一个匹配器库
- Mockito:一个Java Mock框架
- JSONassert:一个针对JSON的断言库
- JsonPath:一个JSON XPath库
如果SpringBoot提供的基础类无法满足业务需求,我们也可以自行添加依赖。依赖注入的优点之一就是可以轻松使用单元测试。这种方式可以直接通过new来创建对象,而不需要涉及Spring。当然,也可以通过模拟对象来替换真实依赖。
如果需要集成测试,比如使用Spring的ApplicationContext,Spring同样能够提供无须部署应用程序或连接到其它基础环境的集成测试。而SpringBoot应用本身就是一个ApplicationContext,因此除了正常使用Spring上下文进行测试,无须执行其它操作。
Maven依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope>
</dependency>
2、常用注解
从功能上讲,Spring Boot Test中的注解主要分如下几类:
类别
示例
说明
配置类型
@TestConfiguration等
提供一些测试相关的配置入口
mock类型
@MockBean等
提供mock支持
启动测试类型
@SpringBootTest等
以Test结尾的注解,具有加载applicationContext的能力
自动配置类型
@AutoConfigureJdbc等
以AutoConfigure开头的注解,具有加载测试支持功能的能力
2.1、配置类型的注解
注解
作用
实践中的使用
@TestComponent
该注解为另一种@Component
,在语义上用来指定某个Bean是专门用于测试的
该注解适用与测试代码和正式混合在一起时,不加载被该注解描述的Bean,使用不多
@TestConfiguration
该注解是另一种@TestComponent
,它用于补充额外的Bean或覆盖已存在的Bean
在不修改正式代码的前提下,使配置更加灵活
@TypeExcludeFilters
用来排除@TestConfiguration
和@TestComponent
适用于测试代码和正式代码混合的场景,使用不多
@OverrideAutoConfiguration
可用于覆盖@EnableAutoCOnfiguration
,与ImportAutoConfiguration
结合使用,以限制所加载的自动配置类
在不修改正式代码的前提下,提供了修改配置自动配置类的能力
@PropertyMapping
定义@AutoConfigure
注解中用到的变量名称,例如在@AutoConfigureMockMvc
中定义名为spring.test.mockmvc.webclient.enabled
的变量
一般不使用
使用@SpringBootApplication启动测试或者生产代码,被@TestComponent描述的Bean会自动被排除掉。如果不是则需要向@SpringBootApplication添加TypeExcludeFilter。
2.2、Mock类型的注解
注解
作用
MockBean
用于Mock指定的class或被注解的属性
MockBeans
使@MockBean支持在同一类型或属性上多次出现
@SpyBean
用于spy指定的class或被注解的属性
@SpyBeans
使@SpyBeans支持在同一类型或属性上次多次出现
@MockBean和@SpyBean这两个注解,在mockito框架中本来已经存在,且功能基本相同。Spring Boot Test又定义一份重复的注解,目的在于使MockBean和SpyBean被ApplicationContext管理,从而方便使用。
MockBean和SpyBean功能非常相似,都能模拟方法的各种行为。不同之处在于MockBean是全新的对象,跟正式对象没有关系;而SpyBean与正式对象紧密联系,可以模拟正式对象的部分方法,没有被模拟的方法仍然可以运行正式代码。
2.3、自动配置类型的注解
注解
作用
@AutoConfigureJdbc
自动配置JDBC
@AutoConfigureCache
自动配置缓存
@AutoConfigureDataLdap
自动配置LDAP
@AutoConfigureJson
自动配置JSON
@AutoConfigureJsonTesters
自动配置JsonTester
@AutoConfigureDataJpa
自动配置JPA
@AutoConfigureTestEntityManager
自动配置TestEntityManager
@AutoConfigureRestDocs
自动配置Rest Docs
@AutoConfigureMockRestServiceServer
自动配置MockRestServiceServer
@AutoConfigureWebClient
自动配置WebClient
@AutoConfigureWebFlux
自动配置WebFlux
@AutoConfigureWebTestClient
自动配置WebTestClient
@AutoConfigureMockMvc
自动配置MockMvc
@AutoConfigureWebMvc
自动配置WebMvc
@AutoConfigureDataNeo4j
自动配置Neo4j
@AutoConfigureDataRedis
自动配置Redis
@AutoConfigureJooq
自动配置Jooq
@AutoCOnfigureTestDatabase
自动Test Database,可以使用内存数据库
这些注解可以搭配@Test使用,用于开启在@Test中未自动配置的功能。例如@SpringBootTest和@AutoConfigureMockMvc组合后,就可以注入org.springframework.test.web.servlet.MockMvc。
自动配置类型有两种使用方式:
- 在功能测试(即使用@SpringBootTest)时显示添加。
- 一般在切片测试中被隐式使用,例如@WebMvcTest注解时,隐式添加了@AutoConfigureCache、@AutoConfigureWebMvc和@AutoConfigureMockMvc。
2.4、启动测试类型的注解
所有的@*Test
注解都被@BootstrapWith
注解,它们可以启动ApplicationContext,是测试的入口,所有的测试类必须声明一个@*Test
注解。
注解
作用
@SpringBootTest
自动侦测并加载@SpringBootApplication或@SpringBootConfiguration中的配置,默认web环境为Mock,不见听任务端口
@DataRedisTest
测试对Redis操作,自动扫描被@RedisHash描述的类,并配置Spring Data Redis的库
@DataJpaTest
测试基于JPA的数据库操作,同时提供了TestEntityManager替代JPA的EntityManager
@DataJdbcTest
测试基于Spring Data JDBC的数据库操作
@JsonTest
测试JSON的序列化和反序列化
@WebMvcTest
测试Spring MVC中的Controllers
@WebFluxTest
测试Spring WebFlux中的Controllers
@RestClientTest
测试对REST客户端的操作
@DataLdapTest
测试对LDAP的操作
@DataMongoTest
测试对MongoDB的操作
@DataNeo4jTest
测试对Neo4j的操作
除了@SpringBootTest之外的注解都是用来进行切面测试的,他们会默认导入一些自动配置,点击查看官方文档。
一般情况,推荐使用@SpringBootTest而非其它切片测试的注解,简单有效。若某次改动仅涉及特定切片,可以考虑使用切片测试。SpringBootTest是这些注解中最常用的一个,其中包含的配置项如下:
- value:指定配置属性
- properties:指定配置属性,和value意义相同
- classes:指定配置类,等同于
@ContextConfiguration
中的class,若没有显示指定,将查找嵌套的@Configuration
类,然后返回到SpringBootConfiguration
搜索配置 - webEnviroment:指定web环境,可选值如下:
MOCK
:此值为默认值,该类型提供一个mock环境,此时内嵌的服务(servlet容器)并没有真正启动,也不会监听web端口RANDOM_PORT
:启动一个真实的web服务,监听一个随机端口DEFINED_PORT
:启动一个真实的web服务,监听一个定义好的端口(从配置中读取)NONE
:启动一个非web的ApplicationContext,既不提供mock环境,也不提供真实的web服务
2.5、相似注解的区别和联系
- @TestComment和@Comment:@TestComment是另一种@Component,在语义上用来指定某个Bean是专门用于测试的。使用@SpringBootApplication服务时,@TestComponent会被自动排除
- @TestConfiguration和@Configuration:@TestConfiguration是Spring Boot Boot Test提供的,@Configuration是Spring Framework提供的。@TestConfiguration实际上是也是一种@TestComponent,只是这个@TestComponent专门用来做配置用。@TestConfiguration和@Configuration不同,它不会阻止@SpringBootTest的查找机制,相当于是对既有配置的补充或覆盖。
- @SpringBootTest和@WebMvcTest(或@*Test):都可以启动Spring的ApplicationContext @SpringBootTest自动侦测并加载@SpringBootApplication或@SpringBootConfiguration中的配置,@WebMvcTest不侦测配置,只是默认加载一些自动配置。@SpringBootTest测试范围一般比@WebMvcTest大。
- @MockBean和@SpyBean:都能模拟方法的各种行为。不同之处在于MockBean是全新的对象,跟正式对象没有关系;而SpyBean与正式对象紧密联系,可以模拟正式对象的部分方法,没有被模拟的方法仍然可以运行正式代码。
3、SpringBootTest和Junit的使用
整体上,Spring Boot Test支持的测试种类,大致可以分为如下三类:
- 单元测试:一般面向方法,编写一般业务代码时,测试成本较大。涉及到的注解有@Test。
- 切片测试:一般面向于测试的边界功能,介于单元测试和功能测试之间。涉及到的注解有@WebMvcTest等。主要就是对于Controller的测试,分离了Service层,这里就涉及到Mock控制层所依赖的组件了。
- 功能测试:一般面向某个完整的业务功能,同时也可以使用切面测试中mock能力,推荐使用。涉及到的注解有@SpringBootTest等。
3.1、单元测试
默认无参数的@SpringBootTest 注解会加载一个Web Application Context并提供Mock Web Environment,但是不会启动内置的server。这点从日志中没有打印Tomcat started on port(s)可以佐证。
@SpringBootTest
public class AppTest {@AutowiredUserMapper userMapper;@Testpublic void test() {User user = new User();user.setName("tom");user.setAge(18);user.setHeight(1.88);Assertions.assertThat(userMapper.add(user)).isEqualTo(1);}
}
3.2、集成测试
//指定@SpringBootTest的Web Environment为RANDOM_PORT
//此时,将会加载ApplicationContext,并启动Server,Server监听在随机端口上。
//在测试类中通过@LocalServerPort获取该端口值
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class DemoTest {@LocalServerPortprivate Integer port;@Test@DisplayName("should access application")public void shouldAccessApplication() {Assertions.assertThat(port).isGreaterThan(1024);}
}
也可以通过指定@SpringBootTest的Web Environment为DEFINED_PORT 来指定server侦听应用程序配置的端口,默认为8080。不过这种指定端口的方式很少使用,因为如果本地同时启动应用时,会导致端口冲突。
4、MockMvc
MockMvc可以做到不启动项目工程就可以对结构进行测试。MockMvc实现了对HTTP请求的模拟,能够直接使用网络的形式,转换到Controller的调用,这样可以使得测试速度快、不依赖网络环境,同时提供了一套验证的工具,使得请求的验证同一而且方便。
4.1、简单示例
创建一个简单的TestController,提供一个方法,返回一个字符串:
@RestController
public class TestController {@RequestMapping("/mock")public String mock(String name) {return "Hello " + name + "!";}
}
单元测试:
@SpringBootTest
@AutoConfigureMockMvc
class TestControllerTest {@Autowiredprivate MockMvc mockMvc;@Testvoid mock() throws Exception {//mockMvc.perform执行一个请求mockMvc.perform(MockMvcRequestBuilders//构造请求.get("/mock")//设置返回值类型.accept(MediaType.APPLICATION_JSON)//添加请求参数.param("name", "tom"))//添加执行完成后的断言.andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().string("Hello tom!"))//添加一个结果处理器,此处打印整个响应结果信息.andDo(MockMvcResultHandlers.print());}
}
运行测试输出:
MockHttpServletRequest:HTTP Method = GETRequest URI = /mockParameters = {name=[tom]}Headers = [Accept:"application/json"]Body = nullSession Attrs = {}Handler:Type = pers.zhang.controller.TestControllerMethod = pers.zhang.controller.TestController#mock(String)Async:Async started = falseAsync result = nullResolved Exception:Type = nullModelAndView:View name = nullView = nullModel = nullFlashMap:Attributes = nullMockHttpServletResponse:Status = 200Error message = nullHeaders = [Content-Type:"application/json", Content-Length:"10"]Content type = application/jsonBody = Hello tom!Forwarded URL = nullRedirected URL = nullCookies = []
@AutoConfigureMockMvc注解提供了自动配置MockMvc的功能。@Autowired注入MockMvc对象。
MockMvc对象可以通过接口M哦查看Mv吃Builder的实现类获得。该接口提供一个唯一的build方法来构造MockMvc。主要有两个实现类:
StandaloneMockMvcBuilder
:独立安装DefaultMockMvcBuilder
:集成Web环境测试(并不会真正的web环境,而是通过相应的Mock API进行模拟测试,无须启动服务器)
MockMvcBuilders提供了对应的standaloneSetup和webAppContextSetup两种创建方法,在使用时直接调用即可,默认使用DefaultMOckMvcBuilder。
整个单元测试包含一下步骤:
- 准备测试环境
- 执行MockMvc请求
- 添加验证断言
- 添加结果处理器
- 得到MvcResult进行自定义断言/进行下一步的异步请求
- 卸载测试环境
4.2、自动配置
@AutoConfigureMockMvc提供了自动配置MockMvc的功能,源码如下:
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@ImportAutoConfiguration
@PropertyMapping("spring.test.mockmvc")
public @interface AutoConfigureMockMvc {//是否应向MockMvc注册来自应用程序上下文的filter,默认trueboolean addFilters() default true;//每次MockMvc调用后应如何打印MvcResult信息@PropertyMapping(skip = SkipPropertyMapping.ON_DEFAULT_VALUE)MockMvcPrint print() default MockMvcPrint.DEFAULT;//如果MvcResult仅在测试失败时才打印信息。默认true,则表示只在失败时打印boolean printOnlyOnFailure() default true;//当HtmlUnit在类路径上时,是否应该自动配置WebClient。默认为true@PropertyMapping("webclient.enabled")boolean webClientEnabled() default true;//当Selenium位于类路径上时,是否应自动配置WebDriver。默认为true@PropertyMapping("webdriver.enabled")boolean webDriverEnabled() default true;}
在AutoConfigureMockMvc的源码中,我们重点看它组合的@ImportAutoConfiguration
注解。该注解同样是SpringBoot自动配置项目提供的,其功能类似@EnableAutoConfiguration
,但又略有区别。@ImportAutoConfiguration同样用于导入自动配置类,不仅可以像@EnableAutoConfiguration那样排除指定的自动配置配置类,还可以指定使用哪些自动配置类,这是它们之间的重要区别之一。
另外,@ImportAutoConfiguration使用的排序规则与@EnableAutoConfiguration的相同,通常情况下,建议优先使用@EnableAutoConfiguration注解进行自动配置。但在单元测试中,则可考虑优先使用@ImportAutoCOnfiguration。源码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(ImportAutoConfigurationImportSelector.class)
public @interface ImportAutoConfiguration {//指定引入的自动配置类@AliasFor("classes")Class<?>[] value() default {};//指定引入的自动配置类。如果为空,则使用META-INF/spring.factories中注册的指定类//其中spring.factories中注册的key为被该注解的类的全限定名称@AliasFor("value")Class<?>[] classes() default {};//排除指定自动配置类Class<?>[] exclude() default {};}
通过value属性,提供了指定自动配置类的功能,可以通过细粒度控制,根据需要引入相应功能的自动配置。没有@EnableAutoConfiguration一次注入全局生效的特性,但是有了指定的灵活性。
更值得注意的是classes属性,它也是用来指定自动配置类的,但它的特殊之处在于,如果未进行指定,则会默认搜索项目META-INF/spring.factories文件中注册的类,但是它
搜索的注册类在spring.factories中的key是被@ImportAutoConfiguration注解的类的全限
定名称。显然,这里的key为org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc。以上功能也就解释了为什么在单元测试中更多的是使用@ImportAutoConfiguration注解来进行自动配置了。
在spring-boot-test-autoconfigure项目的spring.factories文件中的相关配置如下:
# AutoConfigureMockMvc auto-configuration imports
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc=
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcAutoConfiguration,
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebClientAutoConfiguration,
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcWebDriverAutoConfiguration,
org.springframework.boot.autoconfigure.security.oauth2.client.servlet.OAuth2ClientAutoConfiguration,
org.springframework.boot.autoconfigure.security.oauth2.resource.servlet.OAuth2ResourceServerAutoConfiguration,
org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration,
org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,
org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration,
org.springframework.boot.test.autoconfigure.web.servlet.MockMvcSecurityConfiguration
也就是说,当使用@ImportAutoConfiguration注解,并未指定classes属性值时,默认自动配置上述自动配置类。
使用@AutoConfigureMockMvc注解会导入MockMvcAutoConfiguration自动配置类,该类就是专门为MockMvc相关功能提供自动配置的。
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@AutoConfigureAfter(WebMvcAutoConfiguration.class)
@EnableConfigurationProperties({ ServerProperties.class, WebMvcProperties.class })
public class MockMvcAutoConfiguration {private final WebApplicationContext context;private final WebMvcProperties webMvcProperties;MockMvcAutoConfiguration(WebApplicationContext context, WebMvcProperties webMvcProperties) {this.context = context;this.webMvcProperties = webMvcProperties;}....}
注解部分说明,MockMvcAutoConfiguration需要在Web应用程序类型为Servlet,且在WebMvcAutoConfiguration自动配置之后进行自动配置。
另外,通过@EnableConfigurationProperties导入了ServerProperties和WebMvcProperties两个配置属性类,并通过构造方法设置为成员变量。
4.3、使用方式
1、测试逻辑
MockMvcBuilder
构造MockMvc的构造器mockMvc
调用perform
,执行一个RequestBuilder
请求,调用Controller
的业务处理逻辑perform
返回ResultActions
,返回操作结果,通过ResultActions
,提供了统一的验证方式- 使用
StatusResultMatchers
对请求结果进行验证 - 使用
ContentResultMatchers
对请求返回的内容进行验证
2、MockMvcBuilder
MockMvc
是spring测试下的一个非常好用的类,他们的初始化需要在setUp中进行。
MockMvcBuilder
是用来构造MockMvc
的构造器,其主要有两个实现:StandaloneMockMvcBuilder
和DefaultMockMvcBuilder
,前者继承了后者。
MockMvcBuilders.webAppContextSetup(WebApplicationContext context)
:指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvcMockMvcBuilders.standaloneSetup(Object... controllers)
:通过参数指定一组控制器,这样就不需要从上下文获取了,比如this.mockMvc = MockMvcBuilders.standaloneSetup(this.controller).build();
这些Builder还提供了其他api,可以自行百度
3、MockMvcRequestBuilders
从名字可以看出,RequestBuilder用来构建请求的,其提供了一个方法buildRequest(ServletContext servletContext)
用于构建MockHttpServletRequest;其主要有两个子类MockHttpServletRequestBuilder
和MockMultipartHttpServletRequestBuilder
(如文件上传使用),即用来Mock客户端请求需要的所有数据。
常用API:
MockHttpServletRequestBuilder get(String urlTemplate, Object... urlVariables)
:根据uri模板和uri变量值得到一个GET请求方式的RequestBuilder,如果在controller的方法中method选择的是RequestMethod.GET
,那在controllerTest中对应就要使用MockMvcRequestBuilders.get
post(String urlTemplate, Object... urlVariables)
:同get类似,但是是POST方法put(String urlTemplate, Object... urlVariables)
:同get类似,但是是PUT方法delete(String urlTemplate, Object... urlVariables)
:同get类似,但是是DELETE方法options(String urlTemplate, Object... urlVariables)
:同get类似,但是是OPTIONS方法
4、ResultActions
调用MockMvc.perform(RequestBuilder requestBuilder)
后将得到ResultActions
,对ResultActions有以下三种处理:
ResultActions.andExpect
:添加执行完成后的断言。添加ResultMatcher验证规则,验证控制器执行完成后结果是否正确ResultActions.andDo
:添加一个结果处理器,比如此处使用.andDo(MockMvcResultHandlers.print())
输出整个响应结果信息,可以在调试的时候使用ResultActions.andReturn
:表示执行完成后返回相应的结果
ResultHandler用于对处理的结果进行相应处理的,比如输出整个请求/响应等信息方便调试,Spring mvc测试框架提供了MockMvcResultHandlers
静态工厂方法,该工厂提供了ResultHandler print()
返回一个输出MvcResult详细信息到控制台的ResultHandler实现。
使用Content-type来指定不同格式的请求信息:
ALL = new MediaType("*", "*");
APPLICATION_ATOM_XML = new MediaType("application", "atom+xml");
APPLICATION_CBOR = new MediaType("application", "cbor");
APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded");
APPLICATION_JSON = new MediaType("application", "json");
APPLICATION_JSON_UTF8 = new MediaType("application", "json", StandardCharsets.UTF_8);
APPLICATION_NDJSON = new MediaType("application", "x-ndjson");
APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream");
APPLICATION_PDF = new MediaType("application", "pdf");
APPLICATION_PROBLEM_JSON = new MediaType("application", "problem+json");
APPLICATION_PROBLEM_JSON_UTF8 = new MediaType("application", "problem+json", StandardCharsets.UTF_8);
APPLICATION_PROBLEM_XML = new MediaType("application", "problem+xml");
APPLICATION_RSS_XML = new MediaType("application", "rss+xml");
APPLICATION_STREAM_JSON = new MediaType("application", "stream+json");
APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml");
APPLICATION_XML = new MediaType("application", "xml");
IMAGE_GIF = new MediaType("image", "gif");
IMAGE_JPEG = new MediaType("image", "jpeg");
IMAGE_PNG = new MediaType("image", "png");
MULTIPART_FORM_DATA = new MediaType("multipart", "form-data");
MULTIPART_MIXED = new MediaType("multipart", "mixed");
MULTIPART_RELATED = new MediaType("multipart", "related");
TEXT_EVENT_STREAM = new MediaType("text", "event-stream");
TEXT_HTML = new MediaType("text", "html");
TEXT_MARKDOWN = new MediaType("text", "markdown");
TEXT_PLAIN = new MediaType("text", "plain");
TEXT_XML = new MediaType("text", "xml");
5、ResultMatchers
ResultMatcher
用来匹配执行完请求后的结果验证,其就一个match(MvcResult result)
断言方法,如果匹配失败将抛出相应的异常,spring mvc测试框架提供了很多***ResultMatchers
来满足测试需求。
MockMvcResultMatchers
类提供了许多静态方法,提供了多种匹配器:
- request():返回
RequestResultMatchers
,访问与请求相关的断言asyncStarted
:断言异步处理开始asyncNotStarted
:断言异步不开始asyncResult
:断言使用给定匹配器进行异步处理的结果attribute
:用于断言请求属性值sessionAttribute
:用于断言Session会话属性值sessionAttributeDoesNotExist
:断言Session会话属性不存在
- handler():返回
HandlerResultMatchers
,对处理请求的处理程序的断言的访问handlerType
:断言处理请求的处理程序的类型methodCall
:断言用于处理请求的控制器方法methodName
:断言用于处理请求的控制器方法的名称method
:断言用于处理请求的控制器方法
- model():
ModelResultMatchers
,访问与模型相关的断言attribute
:断言一个模型属性值attributeExists
:断言一个模型属性存在attributeDoesNotExist
:断言一个模型属性不存在attributeErrorCount
:断言给定的模型属性有指定个数的错误attributeHasErrors
:断言给定的模型属性有错误attributeHasNoErrors
:断言给定的模型属性没有错误attributeHasFieldErrors
:断言给定的模型属性字段有错误attributeHasFieldErrorCode
:使用精确字符串匹配断言模型属性的字段错误代码errorCount
:断言模型中的错误总数hasErrors
:断言模型中有错误hasNoErrors
:断言模型中没有错误size
:断言模型属性的数量
- view():返回
ViewResultMatchers
,访问所选视图上的断言name
:断言视图名
- flash():返回
FlashAttributeResultMatchers
,访问flash属性断言attribute
:断言flash属性的值attributeExists
:断言给定的flash属性是否存在attributeCount
:断言flash属性的数量
- forwardedUrl(@Nullable String expectedUrl):断言请求被转发到给定的URL
- forwardedUrlTemplate(String urlTemplate, Object… uriVars):断言请求被转发到给定的URL模板
- forwardedUrlPattern(String urlPattern):断言请求被转发到给定的URL
- redirectedUrl(String expectedUrl):断言请求被重定向到给定的URL
- redirectedUrlTemplate(String urlTemplate, Object… uriVars):断言请求被重定向到给定的URL模板
- redirectedUrlPattern(String urlPattern):断言请求被重定向到给定的URL
- status():返回
StatusResultMatchers
,访问响应状态断言is
:断言响应状态码is1xxInformational
:断言响应状态码在1xx范围内is2xxSuccessful
:断言响应状态码在2xx范围内is3xxRedirection
:断言响应状态码在3xx范围内is4xxClientError
:断言响应状态码在4xx范围内is5xxServerError
:断言响应状态码在5xx范围内reason
:断言Servlet响应错误消息isContinue
:响应状态码是100isSwitchingProtocols
:响应状态码是101isProcessing
:响应状态码是102isCheckpoint
:响应状态码是103isOk
:响应状态码是200isCreated
:响应状态码是201isAccepted
:响应状态码是202isNonAuthoritativeInformation
:响应状态码是203isNoContent
:响应状态码是204isResetContent
:响应状态码是205isPartialContent
:响应状态码是206isMultiStatus
:响应状态码是207isAlreadyReported
:响应状态码是208isImUsed
:响应状态码是226isMultipleChoices
:响应状态码是300isMovedPermanently
:响应状态码是301isFound
:响应状态码是302isSeeOther
:响应状态码是303isNotModified
:响应状态码是304isUseProxy
:响应状态码是305isTemporaryRedirect
:响应状态码是307isPermanentRedirect
:响应状态码是308isBadRequest
:响应状态码是400isUnauthorized
:响应状态码是401isPaymentRequired
:响应状态码是402isForbidden
:响应状态码是403isNotFound
:响应状态码是404isMethodNotAllowed
:响应状态码是405isNotAcceptable
:响应状态码是406isProxyAuthenticationRequired
:响应状态码是407isRequestTimeout
:响应状态码是408isConflict
:响应状态码是409isGone
:响应状态码是410isLengthRequired
:响应状态码是411isPreconditionFailed
:响应状态码是412isPayloadTooLarge
:响应状态码是413isUriTooLong
:响应状态码是414isUnsupportedMediaType
:响应状态码是415isRequestedRangeNotSatisfiable
:响应状态码是416isExpectationFailed
:响应状态码是417isIAmATeapot
:响应状态码是418isInsufficientSpaceOnResource
:响应状态码是419isMethodFailure
:响应状态码是420isDestinationLocked
:响应状态码是421isUnprocessableEntity
:响应状态码是422isLocked
:响应状态码是423isFailedDependency
:响应状态码是424isTooEarly
:响应状态码是425isUpgradeRequired
:响应状态码是426isPreconditionRequired
:响应状态码是428isTooManyRequests
:响应状态码是429isRequestHeaderFieldsTooLarge
:响应状态码是431isUnavailableForLegalReasons
:响应状态码是451isInternalServerError
:响应状态码是500isNotImplemented
:响应状态码是501isBadGateway
:响应状态码是502isServiceUnavailable
:响应状态码是503isGatewayTimeout
:响应状态码是504isHttpVersionNotSupported
:响应状态码是505isVariantAlsoNegotiates
:响应状态码是506isInsufficientStorage
:响应状态码是507isLoopDetected
:响应状态码是508isBandwidthLimitExceeded
:响应状态码是509isNotExtended
:响应状态码是510isNetworkAuthenticationRequired
:响应状态码是511
- header():返回
HeaderResultMatchers
,访问响应头断言string
:断言响应头的主值stringValues
:断言响应头的值exists
:断言指定的响应头存在doesNotExist
:断言指定的响应头不存在longValue
:将指定响应头断言为longdateValue
:断言指定响应头解析为日期
- content():返回
ContentResultMatchers
,访问响应体断言contentType
:断言Content-Type,给定的内容类型必须完全匹配,包括类型、子类型和参数contentTypeCompatibleWith
:断言Content-Type与指定的类型兼容encoding
:断言响应的字符编码string
:断言响应体内容(作为字符串)bytes
:断言响应体内容(作为字节数组)xml
:断言响应体内容(作为Xml)source
:断言响应体内容(作为Source)json
:断言响应体内容(作为json)
- jsonPath(String expression, Object… args):返回
JsonPathResultMatchers
,使用JsonPath表达式访问响应体断言prefix
:断言JSON有效负载是否添加了给定的前缀value
:根据JsonPath断言结果值exists
:根据JsonPath断言在给定路径上存在非空值doesNotExist
:根据JsonPath断言在给定路径上不存在非空值isEmpty
:根据JsonPath断言给定路径中存在空值isNotEmpty
:根据JsonPath断言给定路径中不存在空值hasJsonPath
:根据JsonPath断言给定路径中存在一个值doesNotHaveJsonPath
:根据JsonPath断言给定路径中不存在一个值isString
:根据JsonPath断言结果是StringisBoolean
:根据JsonPath断言结果是BooleanisNumber
:根据JsonPath断言结果是NumberisArray
:根据JsonPath断言结果是ArrayisMap
:根据JsonPath断言结果是Map
- jsonPath(String expression, Matcher< super T> matcher):根据响应体计算给定的JsonPath表达式,并使用给定的Hamcrest Matcher断言结果值
- jsonPath(String expression, Matcher< super T> matcher, Class targetType):根据响应体计算给定的JsonPath表达式,并使用给定的Hamcrest Matcher断言结果值,在应用匹配器之前将结果值强制转换为给定的目标类型
- xpath(String expression, Object… args):返回
XpathResultMatchers
,使用XPath表达式访问响应体断言,以检查响应体的特定子集node
:计算XPath并断言使用给定的Hamcrest Matcher找到的Node内容nodeList
:计算XPath并断言与给定的Hamcrest Matcher找到的NodeList内容exists
:计算XPath并断言内容存在doesNotExist
:计算XPath并断言内容不存在nodeCount
:计算XPath并断言使用给定的Hamcrest Matcher找到的节点数string
:应用XPath并断言用给定的Hamcrest Matcher找到的String值number
:计算XPath并断言用给定的Hamcrest Matcher找到的Double值booleanValue
:计算XPath并断言找到的Boolean
- xpath(String expression, Map<String, String> namespaces, Object… args):使用XPath表达式访问响应体断言,以检查响应体的特定子集
- cookie():返回CookieResultMatchers,访问响应cookie断言
value
:使用给定的Hamcrest Matcher断言一个cookie值exists
:断言cookie存在doesNotExist
:断言cookie不存在maxAge
:使用Hamcrest Matcher断言cookie的maxAgepath
:用Hamcrest Matcher断言一个cookie的路径domain
:使用Hamcrest Matcher断言cookie的域comment
:用Hamcrest Matcher断言一个cookie的注释version
:用Hamcrest Matcher断言一个cookie的版本secure
:断言cookie是否必须通过安全协议发送httpOnly
:断言cookie是否只能是HTTP
6、MvcResult
即执行完控制器后得到的整个结果,并不仅仅是返回值,其包含了测试时需要的所有信息。
MvcResult有两个实现类:
- DefaultMvcResult:一个简单的默认实现
- PrintingMvcResult:待打印功能的实现
常用方法:
- getRequest:返回执行的请求
- getResponse:返回结果响应
- getHandler:返回已执行的处理程序
- getInterceptors:返回处理程序周围的拦截器
- getModelAndView:返回处理程序准备的ModelAndView
- getResolvedException:返回由处理程序引发并通过HandlerExceptionResolver成功解决的任何异常
- getFlashMap:返回在请求处理期间保存的FlashMap
- getAsyncResult:得到异步执行的结果
5、业务代码
实体类:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {private Long id;private String name;private Integer age;private Double height;
}
Dao层:
@Mapper
public interface UserMapper {List<User> list();Integer add(User user);Integer update(User user);Integer deleteById(Long id);User getById(Long id);
}
UserMapper.xml:
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" ><mapper namespace= "pers.zhang.mapper.UserMapper" ><insert id="add">INSERT INTO user (name, age, height)VALUES (#{name}, #{age}, #{height})</insert><update id="update">UPDATE user SET name = #{name}, age = #{age}, height = #{height}WHERE id = #{id}</update><delete id="deleteById">DELETE FROM userWHERE id = #{id}</delete><select id = "list" resultType="pers.zhang.entity.User">SELECT id, name, age, heightFROM user;</select><select id="getById" resultType="pers.zhang.entity.User">SELECT id, name, age, heightFROM userWHERE id = #{id}</select></mapper>
Service层:
public interface UserService {List<User> list();Integer add(User user);Integer update(User user);Integer deleteById(Long id);User getById(Long id);
}@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic List<User> list() {System.out.println("Call userMapper.list...");return userMapper.list();}@Overridepublic Integer add(User user) {System.out.println("Call userMapper.add...");return userMapper.add(user);}@Overridepublic Integer update(User user) {System.out.println("Call userMapper.update...");return userMapper.update(user);}@Overridepublic Integer deleteById(Long id) {System.out.println("Call userMapper.deleteById...");return userMapper.deleteById(id);}@Overridepublic User getById(Long id) {System.out.println("Call userMapper.getById...");return userMapper.getById(id);}
}
Controller层:
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;@GetMapping("/list")public List<User> list() {System.out.println("Call UserService.list");return userService.list();}@GetMapping("/info")public User getUserById(Long id) {System.out.println("Call UserService.getUserById");return userService.getById(id);}@PostMapping("/add")public Integer add(@RequestBody User user) {System.out.println("Call UserService.add");return userService.add(user);}@PostMapping("/update")public Integer update(@RequestBody User user) {System.out.println("Call UserService.update");return userService.update(user);}@PostMapping("/delete")public Integer delete(Long id) {System.out.println("Call UserService.delete");return userService.deleteById(id);}
}
6、分层测试
6.1、Dao层测试
在UserMapperTest测试类中可以直接使用@Autowired来装配UserMapper这个Bean。而且,@SpringBootTest注解会自动帮我们完成启动一个Spring容器ApplicationContext,然后连接数据库,执行一套完整的业务逻辑。
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import pers.zhang.entity.User;import java.util.List;import static org.junit.jupiter.api.Assertions.*;
import static org.assertj.core.api.Assertions.*;@SpringBootTest
class UserMapperTest {/*** 数据库user表内容如下:** id | name | age | height |* 1 tom 18 1.77* 2 jerry 22 1.83* 3 mike 24 1.79*/@Autowiredprivate UserMapper userMapper;@Testvoid list() {List<User> list = userMapper.list();assertThat(list.size()).isEqualTo(3);assertThat(list).extracting("id", "name", "age", "height").contains(tuple(1L, "tom", 18, 1.77),tuple(2L, "jerry", 22, 1.83),tuple(3L, "mike", 24, 1.79));}@Testvoid add() {User user = new User();user.setName("zhangsan");user.setAge(30);user.setHeight(1.66);Integer effectRows = userMapper.add(user);assertThat(effectRows).isEqualTo(1);}@Testvoid update() {User user = new User();user.setName("zhangsan");user.setAge(33);user.setHeight(1.88);user.setId(7L);Integer effectRows = userMapper.update(user);assertThat(effectRows).isEqualTo(1);}@Testvoid deleteById() {Integer effectRows = userMapper.deleteById(7L);assertThat(effectRows).isEqualTo(1);}@Testvoid getById() {User expect = new User();expect.setId(1L);expect.setName("tom");expect.setAge(18);expect.setHeight(1.77);User user = userMapper.getById(1L);assertThat(user).isEqualTo(expect);}
}
6.2、Service层测试
上面的测试代码是连接真实数据库来执行真实的Dao层数据库查询逻辑。而在实际开发中,有时候需要独立于数据库进行Service层逻辑的开发。这个时候就可以直接把数据库Dao层代码Mock掉。
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.*;
import pers.zhang.entity.User;
import pers.zhang.mapper.UserMapper;import java.util.ArrayList;
import java.util.List;import static org.assertj.core.api.Assertions.*;
import static org.mockito.Mockito.*;class UserServiceImplTest {//Mock掉Dao层@Mockprivate UserMapper userMapper;//把Mock掉的Dao层注入Service@InjectMocksprivate UserServiceImpl userService;@BeforeEachvoid setup() {//开启Mockito注解MockitoAnnotations.openMocks(this);}@Testvoid list() {List<User> users = new ArrayList<>();users.add(new User(10L, "zhangsan", 18, 1.77));users.add(new User(11L, "lisi", 22, 1.83));//打桩when(userMapper.list()).thenReturn(users);//调用List<User> list = userService.list();list.forEach(System.out::println);//验证verify(userMapper, times(1)).list();}@Testvoid add() {User user = new User(1L, "tom", 21, 1.80);//打桩when(userMapper.add(isA(User.class))).thenReturn(1);//调用Integer effectRows = userService.add(user);assertThat(effectRows).isEqualTo(1);//验证verify(userMapper, times(1)).add(user);}@Testvoid update() {User user = new User(2L, "tom", 21, 1.80);//打桩when(userMapper.update(argThat(u -> {return u != null && u.getId() != null;}))).thenReturn(1);//调用Integer effectRows = userService.update(user);assertThat(effectRows).isEqualTo(1);//验证verify(userMapper, times(1)).update(user);}@Testvoid deleteById() {//打桩when(userMapper.deleteById(anyLong())).thenReturn(1);//调用Integer effectRows = userService.deleteById(999L);assertThat(effectRows).isEqualTo(1);//验证verify(userMapper, times(1)).deleteById(999L);}@Testvoid getById() {User user = new User(1L, "xxx", 40, 1.92);//打桩when(userMapper.getById(1L)).thenReturn(user);//调用User actual = userService.getById(1L);assertThat(actual).isInstanceOf(User.class);//验证verify(userMapper, times(1)).getById(1L);}
}
输出:
Call userMapper.update...Call userMapper.getById...Call userMapper.add...Call userMapper.list...
User(id=10, name=zhangsan, age=18, height=1.77)
User(id=11, name=lisi, age=22, height=1.83)Call userMapper.deleteById...
6.3、Controller层测试
spring-boot-starter-test提供了MockMvc对Controller测试功能的强大支持。
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import pers.zhang.entity.User;
import pers.zhang.service.UserService;import java.util.Arrays;import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.*;class UserControllerTest {private MockMvc mockMvc;@Mockprivate UserService userService;@InjectMocksprivate UserController userController;@BeforeEachvoid setup() {//开启Mockito注解MockitoAnnotations.openMocks(this);//初始化MockMvc,将UserController注入其中mockMvc = MockMvcBuilders.standaloneSetup(userController).build();}@Testvoid list() throws Exception {//打桩when(userService.list()).thenReturn(Arrays.asList(new User(1L, "tom", 18, 1.77),new User(2L, "jerry", 22, 1.88)));//调用MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/list").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk()).andDo(print()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());//验证verify(userService, times(1)).list();}@Testvoid getUserById() throws Exception {//打桩User user = new User(1L, "tom", 18, 1.77);when(userService.getById(anyLong())).thenReturn(user);//调用MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.get("/user/info").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).param("id", "1")).andExpect(status().isOk()).andDo(print()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());//验证verify(userService, times(1)).getById(1L);}@Testvoid add() throws Exception {User user = new User();user.setName("jerry");user.setAge(22);user.setHeight(1.74);//打桩when(userService.add(isA(User.class))).thenReturn(1);//调用MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/add").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).content("{"name": "jerry", "age": 22, "height": 1.74}")).andExpect(status().isOk()).andDo(print()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());//验证verify(userService, times(1)).add(user);}@Testvoid update() throws Exception {User user = new User(1L, "tom", 18, 1.77);//打桩when(userService.update(isA(User.class))).thenReturn(1);//调用MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/update").accept(MediaType.APPLICATION_JSON).contentType(MediaType.APPLICATION_JSON).content("{"id": 1, "name": "tom", "age": 18, "height": 1.77}")).andExpect(status().isOk()).andDo(print()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());//验证verify(userService, times(1)).update(user);}@Testvoid delete() throws Exception {//打桩when(userService.deleteById(anyLong())).thenReturn(1);//调用MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/user/delete").accept(MediaType.APPLICATION_JSON).param("id", "1")).andExpect(status().isOk()).andDo(print()).andReturn();System.out.println(mvcResult.getResponse().getContentAsString());//验证verify(userService, times(1)).deleteById(1L);}
}
7、JSON接口测试
使用JsonPath可以像JavaScript语法一样方便地进行JSON数据返回的访问操作。
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.result.MockMvcResultHandlers;
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;@SpringBootTest
@AutoConfigureMockMvc
class JsonControllerTest {@Autowiredprivate MockMvc mockMvc;/*** 数据库user表内容如下:** id | name | age | height |* 1 tom 18 1.77* 2 jerry 22 1.83* 3 mike 24 1.79*/@Testvoid list() throws Exception {mockMvc.perform(MockMvcRequestBuilders.get("/user/list").accept(MediaType.APPLICATION_JSON))//响应码200.andExpect(MockMvcResultMatchers.status().isOk())//json数组长度为3.andExpect(MockMvcResultMatchers.jsonPath("$.length()", Matchers.equalTo(3)))//name包含指定值.andExpect(MockMvcResultMatchers.jsonPath("$..name", Matchers.contains("tom", "jerry", "mike"))).andDo(MockMvcResultHandlers.print());}@Testvoid getUserById() throws Exception {mockMvc.perform(MockMvcRequestBuilders.get("/user/info").accept(MediaType.APPLICATION_JSON).param("id", "1")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$.name", Matchers.equalTo("tom"))).andExpect(MockMvcResultMatchers.jsonPath("$.age", Matchers.equalTo(18))).andExpect(MockMvcResultMatchers.jsonPath("$.height", Matchers.equalTo(1.77))).andDo(MockMvcResultHandlers.print());}@Testvoid add() throws Exception {mockMvc.perform(MockMvcRequestBuilders.post("/user/add").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content("{"name": "zhangsan", "age": 40, "height": 1.76}")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.equalTo(1))).andDo(MockMvcResultHandlers.print());}@Testvoid update() throws Exception {mockMvc.perform(MockMvcRequestBuilders.post("/user/update").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).content("{"id": 9, "name": "lisi", "age": 44, "height": 1.76}")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.equalTo(1))).andDo(MockMvcResultHandlers.print());}@Testvoid delete() throws Exception {mockMvc.perform(MockMvcRequestBuilders.post("/user/delete").contentType(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON).param("id", "9")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.jsonPath("$", Matchers.equalTo(1))).andDo(MockMvcResultHandlers.print());}
}
相关文章:
SpringBoot Test详解
目录 spring-boot-starter-test 1、概述2、常用注解 2.1、配置类型的注解2.2、Mock类型的注解2.3、自动配置类型的注解2.4、启动测试类型的注解2.5、相似注解的区别和联系 3、SpringBootTest和Junit的使用 3.1、单元测试3.2、集成测试 4、MockMvc 4.1、简单示例4.2、自动配置4…...
CDefView::_GetPIDL函数分析之ListView_GetItem函数的参数item的item.mask 为LVIF_PARAM
CDefView::_GetPIDL函数分析之ListView_GetItem函数的参数item的item.mask 为LVIF_PARAM 第一部分: 1: kd> t SHELL32!CDefView::_GetPIDL: 001b:77308013 55 push ebp 1: kd> dv this 0x00000015 i 0n21 …...
Android Retrofit 框架注解定义与解析模块深度剖析(一)
一、引言 在现代 Android 和 Java 开发中,网络请求是不可或缺的一部分。Retrofit 作为 Square 公司开源的一款强大的类型安全的 HTTP 客户端,凭借其简洁易用的 API 和高效的性能,在开发者社区中广受欢迎。Retrofit 的核心特性之一便是通过注…...

项目上传到Gitee过程
在gitee上新建一个仓库 点击“克隆/下载”获取仓库地址 电脑上要装好git 在电脑本地文件夹右键“Git Bash Here” 依次执行如下命令 git init git remote add origin https://gitee.com/qlexcel/stm32-simple.git git pull origin master git add . git commit -m ‘init’…...

DeepSeek R1在医学领域的应用与技术分析(Discuss V1版)
DeepSeek R1作为一款高性能、低成本的国产开源大模型,正在深刻重塑医学软件工程的开发逻辑与应用场景。其技术特性,如混合专家架构(MoE)和参数高效微调(PEFT),与医疗行业的实际需求紧密结合,推动医疗AI从“技术驱动”向“场景驱动”转型。以下从具体业务领域需求出发,…...

数学之快速幂-数的幂次
题目描述 给定三个正整数 N,M,P,求 输入描述 第 1 行为一个整数 T,表示测试数据数量。 接下来的 T 行每行包含三个正整数 N,M,P。 输出描述 输出共 T 行,每行包含一个整数,表示答案。 输入输出样例 示例 1 输入 3 2 3 7 4…...
git subtree管理的仓库怎么删除子仓库
要删除通过 git subtree 管理的子仓库,可以按照以下步骤操作: 1. 确认子仓库路径 首先确认要删除的子仓库的路径,假设子仓库路径为 <subtree-path>。 2. 从主仓库中移除子仓库目录 使用 git rm 命令删除子仓库的目录: …...

学习资料电子版 免费下载的网盘网站(非常全!)
我分享一个私人收藏的电子书免费下载的网盘网站(学习资料为主): link3.cc/sbook123 所有资料都保存在网盘了,直接转存即可,非常的便利! 包括了少儿,小学,初中,中职&am…...

SpringMVC-全局异常处理
文章目录 1. 全局异常处理2. 项目异常处理方案2.1 异常分类2.2 异常解决方案2.3 异常解决方案具体实现 1. 全局异常处理 问题:当我们在SpingMVC代码中没有对异常进行处理时,三层架构的默认处理异常方案是将异常抛给上级调用者。也就是说Mapper层报错会将…...

基于Spring Boot的宠物健康顾问系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...

【Linux内核系列】:深入理解缓冲区
🔥 本文专栏:Linux 🌸作者主页:努力努力再努力wz ★★★ 本文前置知识: 文件系统以及相关系统调用接口 输入以及输出重定向 那么在此前的学习中,我们了解了文件的概念以及相关的系统调用接口,并…...
Python开发Scikit-learn面试题及参考答案
目录 如何用 SimpleImputer 处理数据集中的缺失值? 使用 StandardScaler 对数据进行标准化的原理是什么?与 MinMaxScaler 有何区别? 如何用 OneHotEncoder 对类别型特征进行编码? 解释特征选择中 SelectKBest 与 VarianceThreshold 的应用场景。 如何通过 PolynomialFe…...
~(取反)在算法竞赛中的常见用法和注意事项
在算法竞赛中,取反符号 ~ 主要用于按位取反操作,其功能是对整数的二进制表示逐位取反(0 变 1,1 变 0)。以下是 ~ 在算法竞赛中的常见用法和注意事项: 1. 按位取反的基本用法 ~ 对整数的二进制表示进行取反…...
C++ MySQL 常用接口(基于 MySQL Connector/C++)
C MySQL 常用接口(基于 MySQL Connector/C) 1. 数据库连接 接口: sql::mysql::MySQL_Driver *driver; sql::Connection *con;作用: 用于创建 MySQL 连接对象。 示例: driver sql::mysql::get_mysql_driver_insta…...

本地部署 OpenManus 保姆级教程(Windows 版)
一、环境搭建 我的电脑是Windows 10版本,其他的没尝试,如果大家系统和我的不一致,请自行判断,基本上没什么大的出入啊。 openManus的Git地址:https://github.com/mannaandpoem/OpenManus 根据官网的两种安装推荐方式如…...
【Pandas】pandas Series compare
# Pandas2.2 Series ## Computations descriptive stats |方法|描述| |-|:-------| |Series.compare(other[, align_axis, ...])|用于比较两个 Series| ### pandas.Series.compare pandas.Series.compare 方法用于比较两个 Series,并返回一个包含差异的 DataFram…...

基于DeepSeek的智慧医药系统(源码+部署教程)
运行环境 智慧医药系统运行环境如下: 前端: HTMLCSS后端:Java AIGCDeepseekIDE工具:IDEA技术栈:Springboot HTMLCSS MySQL 主要角色 智慧医药系统主要分为两个角色。 游客 尚未进行注册和登录。具备登录注册、…...
如何为服务设置合理的线程数
1. 首先,要确定最大线程数的限制因素。通常,线程数量受限于内存、CPU和操作系统限制。比如,每个线程都需要一定的栈内存,默认情况下Java线程的栈大小是1MB(64位系统可能更大),所以如果内存不足&…...

Unity--Cubism Live2D模型使用
了解LIVE2D在unity的使用--前提记录 了解各个组件的作用 Live2D Manuals & Tutorials 这些文件都是重要的控制动画参数的 Cubism Editor是编辑Live2D的工具,而导出的数据的类型,需要满足以上的条件 SDK中包含的Cubism的Importer会自动生成一个Pref…...
Vue.js 3 的设计思路:从声明式UI到高效渲染机制
目录 一、声明式UI与虚拟DOM的灵活性 二、渲染器:虚拟DOM到真实DOM的桥梁 三、组件的本质与实现 四、编译与运行时的协同优化 五、性能与可维护性的权衡 总结 Vue.js 3 作为新一代前端框架,其设计理念在声明式UI描述、虚拟DOM优化、组件化架构…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...

23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...

(转)什么是DockerCompose?它有什么作用?
一、什么是DockerCompose? DockerCompose可以基于Compose文件帮我们快速的部署分布式应用,而无需手动一个个创建和运行容器。 Compose文件是一个文本文件,通过指令定义集群中的每个容器如何运行。 DockerCompose就是把DockerFile转换成指令去运行。 …...

网络编程(UDP编程)
思维导图 UDP基础编程(单播) 1.流程图 服务器:短信的接收方 创建套接字 (socket)-----------------------------------------》有手机指定网络信息-----------------------------------------------》有号码绑定套接字 (bind)--------------…...
在web-view 加载的本地及远程HTML中调用uniapp的API及网页和vue页面是如何通讯的?
uni-app 中 Web-view 与 Vue 页面的通讯机制详解 一、Web-view 简介 Web-view 是 uni-app 提供的一个重要组件,用于在原生应用中加载 HTML 页面: 支持加载本地 HTML 文件支持加载远程 HTML 页面实现 Web 与原生的双向通讯可用于嵌入第三方网页或 H5 应…...

七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...