【问题标题】:Unable to mock Service class in Spring MVC Controller tests无法在 Spring MVC 控制器测试中模拟服务类
【发布时间】:2013-04-16 17:45:25
【问题描述】:

我有一个 Spring 3.2 MVC 应用程序,并且正在使用 Spring MVC 测试框架来测试我的控制器操作上的 GET 和 POST 请求。我正在使用 Mockito 来模拟服务,但我发现这些模拟被忽略了,并且正在使用我的实际服务层(因此,数据库正在被命中)。

我的 Controller 测试中的代码:

package name.hines.steven.medical_claims_tracker.controllers;

import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
import name.hines.steven.medical_claims_tracker.domain.Policy;
import name.hines.steven.medical_claims_tracker.services.PolicyService;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration({ "classpath:/applicationContext.xml", "classpath:/tests_persistence-applicationContext.xml" })
public class PolicyControllerTest {

    @Mock
    PolicyService service;

    @Autowired
    private WebApplicationContext wac;

    private MockMvc mockMvc;

    @Before
    public void setup() {
        this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();

        // this must be called for the @Mock annotations above to be processed.
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void createOrUpdateFailsWhenInvalidDataPostedAndSendsUserBackToForm() throws Exception {
        // Post no parameters in this request to force errors
        mockMvc.perform(post("/policies/persist")).andExpect(status().isOk())
            .andExpect(model().attributeHasErrors("policy"))
            .andExpect(view().name("createOrUpdatePolicy"));
    }

    @Test
    public void createOrUpdateSuccessful() throws Exception {

        // Mock the service method to force a known response
        when(service.save(isA(Policy.class))).thenReturn(new Policy());

        mockMvc.perform(
                post("/policies/persist").param("companyName", "Company Name")
                .param("name", "Name").param("effectiveDate", "2001-01-01"))
                .andExpect(status().isMovedTemporarily()).andExpect(model().hasNoErrors())
                .andExpect(redirectedUrl("list"));
    }
}

您会注意到我有两个上下文配置文件;这是一个 hack,因为如果我无法阻止控制器测试到达实际的服务层,那么该服务层可能会将其存储库指向测试数据库。我还没有到无法再摆脱这种黑客攻击并且需要能够正确模拟我的服务层的地步。

为什么when(service.save(isA(Policy.class))).thenReturn(new Policy()); 没有启动并模拟 PolicyService 中的保存方法?我是否在某处遗漏了一些模拟配置?我需要在 Spring 配置中添加什么吗?到目前为止,我的研究仅限于谷歌搜索“spring mvc test mockito not working”,但这并没有给我太多的继续。

谢谢。


更新 1

你是对的@tom-verelst,我在测试中指的是PolicyService service; 行,所以MockMvc 中的服务当然会被Spring 注入。

我做了一些研究,发现a blog post 很好地解释了@InjectMocks 的用途。

然后我尝试用@InjectMocks 注释private MockMvc mockMvc,但仍然遇到同样的问题(即MockMvc 中的服务没有像我预期的那样被嘲笑)。我在调试过程中添加了堆栈跟踪,其中调用了PolicyServiceImpl 上的保存方法(与模拟服务中对保存方法的期望调用相反)。

Thread [main] (Suspended (breakpoint at line 29 in DomainEntityServiceImpl) PolicyServiceImpl(DomainEntityServiceImpl<T>).save(T) line: 29

NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597  
AopUtils.invokeJoinpointUsingReflection(Object, Method, Object[]) line: 317
ReflectiveMethodInvocation.invokeJoinpoint() line: 183  
ReflectiveMethodInvocation.proceed() line: 150  
TransactionInterceptor$1.proceedWithInvocation() line: 96
TransactionInterceptor(TransactionAspectSupport).invokeWithinTransaction(Method, Class, TransactionAspectSupport$InvocationCallback) line: 260  
TransactionInterceptor.invoke(MethodInvocation) line: 94
ReflectiveMethodInvocation.proceed() line: 172  
JdkDynamicAopProxy.invoke(Object, Method, Object[]) line: 204
$Proxy44.save(DomainEntity) line: not available 
PolicyController.createOrUpdate(Policy, BindingResult) line: 64
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597  
ServletInvocableHandlerMethod(InvocableHandlerMethod).invoke(Object...) line: 219
ServletInvocableHandlerMethod(InvocableHandlerMethod).invokeForRequest(NativeWebRequest, ModelAndViewContainer, Object...) line: 132    
ServletInvocableHandlerMethod.invokeAndHandle(ServletWebRequest, ModelAndViewContainer, Object...) line: 104    
RequestMappingHandlerAdapter.invokeHandleMethod(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 746   
RequestMappingHandlerAdapter.handleInternal(HttpServletRequest, HttpServletResponse, HandlerMethod) line: 687   
RequestMappingHandlerAdapter(AbstractHandlerMethodAdapter).handle(HttpServletRequest, HttpServletResponse, Object) line: 80 
TestDispatcherServlet(DispatcherServlet).doDispatch(HttpServletRequest, HttpServletResponse) line: 925  
TestDispatcherServlet(DispatcherServlet).doService(HttpServletRequest, HttpServletResponse) line: 856   
TestDispatcherServlet(FrameworkServlet).processRequest(HttpServletRequest, HttpServletResponse) line: 915   
TestDispatcherServlet(FrameworkServlet).doPost(HttpServletRequest, HttpServletResponse) line: 822
TestDispatcherServlet(HttpServlet).service(HttpServletRequest, HttpServletResponse) line: 727
TestDispatcherServlet(FrameworkServlet).service(HttpServletRequest, HttpServletResponse) line: 796
TestDispatcherServlet.service(HttpServletRequest, HttpServletResponse) line: 66
TestDispatcherServlet(HttpServlet).service(ServletRequest, ServletResponse) line: 820
MockFilterChain$ServletFilterProxy.doFilter(ServletRequest, ServletResponse, FilterChain) line: 168
MockFilterChain.doFilter(ServletRequest, ServletResponse) line: 136
MockMvc.perform(RequestBuilder) line: 134   
PolicyControllerTest.createOrUpdateSuccessful() line: 67
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]
NativeMethodAccessorImpl.invoke(Object, Object[]) line: 39
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 25
Method.invoke(Object, Object...) line: 597  
FrameworkMethod$1.runReflectiveCall() line: 44  
FrameworkMethod$1(ReflectiveCallable).run() line: 15    
FrameworkMethod.invokeExplosively(Object, Object...) line: 41
InvokeMethod.evaluate() line: 20    
RunBefores.evaluate() line: 28  
RunBeforeTestMethodCallbacks.evaluate() line: 74    
RunAfterTestMethodCallbacks.evaluate() line: 83 
SpringRepeat.evaluate() line: 72    
SpringJUnit4ClassRunner.runChild(FrameworkMethod, RunNotifier) line: 231
SpringJUnit4ClassRunner.runChild(Object, RunNotifier) line: 88
ParentRunner$3.run() line: 193  
ParentRunner$1.schedule(Runnable) line: 52  
SpringJUnit4ClassRunner(ParentRunner<T>).runChildren(RunNotifier) line: 191
ParentRunner<T>.access$000(ParentRunner, RunNotifier) line: 42
ParentRunner$2.evaluate() line: 184 
RunBeforeTestClassCallbacks.evaluate() line: 61 
RunAfterTestClassCallbacks.evaluate() line: 71  
SpringJUnit4ClassRunner(ParentRunner<T>).run(RunNotifier) line: 236
SpringJUnit4ClassRunner.run(RunNotifier) line: 174  
JUnit4TestMethodReference(JUnit4TestReference).run(TestExecution) line: 50
TestExecution.run(ITestReference[]) line: 38    
RemoteTestRunner.runTests(String[], String, TestExecution) line: 467
RemoteTestRunner.runTests(TestExecution) line: 683  
RemoteTestRunner.run() line: 390    
RemoteTestRunner.main(String[]) line: 197   

更多研究 (Mockito Injecting Null values into a Spring bean when using @Mock?) 建议将 @InjectMocks 应用于测试中的 PolicyController 成员变量,但正如第一个链接中的一个答案所指出的那样,这没有任何作用,因为 Spring 不知道关于它的任何东西。

【问题讨论】:

    标签: java spring spring-mvc mocking mockito


    【解决方案1】:

    如果在 SpringBoot 上使用 Springs 自己的 @MockBean。 - 正如码头所说:

    上下文中定义的任何现有的相同类型的单个 bean 将 被模拟取代。如果没有定义现有的 bean,则将创建一个新的 被添加。

     @RunWith(SpringRunner.class)
     public class ExampleTests {
    
         @MockBean
         private ExampleService service;
    

    【讨论】:

      【解决方案2】:

      还有另一个使用@WebMvcTest 的最新春季版本的解决方案。下面的例子。

      @RunWith(SpringRunner.class)
      @WebMvcTest(CategoryAPI.class)
      public class CategoryAPITest {
      
      @Autowired
      private MockMvc mvc;
      
      @MockBean
      CategoryAPIService categoryAPIService;
      
      @SpyBean
      Utility utility;
      
      PcmResponseBean responseBean;
      
      @Before
      public void before() {
          PcmResponseBean responseBean = new PcmResponseBean("123", "200", null, null);
          BDDMockito.given(categoryAPIService.saveCategory(anyString())).willReturn(responseBean);
      }
      
      @Test
      public void saveCategoryTest() throws Exception {
          String category = "{}";
          mvc.perform(post("/api/category/").content(category).contentType(MediaType.APPLICATION_JSON))
                  .andExpect(status().isOk()).andExpect(jsonPath("messageId", Matchers.is("123")))
                  .andExpect(jsonPath("status", Matchers.is("200")));
        }
      
      }
      

      这里我们只加载 CategoryAPI 类,它是一个 Spring 休息控制器类,其余都是模拟的。 Spring 有自己的注解版本,例如 @MockBean 和 @SpyBean,类似于 mockito @Mock 和 @Spy。

      【讨论】:

      • 这个解决方案实际上对我有用,而且看起来最干净。然而:我得到了一个ControllerAdvice,它又包含带有@ExceptionHandler-annotation 的方法。我如何告诉我的测试承认它? (我应该在一个单独的问题中问这个吗?)
      【解决方案3】:

      我更喜欢 Mockmvc 的独立服务

      为我提到的工作

      public class AccessControllerTest {
      
          private MockMvc mockMvc;
      
          @Mock
          private AccessControlService accessControlService;
      
          @InjectMocks
          private AccessController accessController;
      
          @Before
          public void setup() {
              MockitoAnnotations.initMocks(this);
              this.mockMvc =  MockMvcBuilders.standaloneSetup(accessController).build();
          }
      
          @Test
          public void validAccessControlRequest() throws Exception {
              Bundle bundle = new Bundle();
              bundle.setAuthorized(false);
              Mockito.when(accessControlService.retrievePatient(any(String.class)))
               .thenReturn(bundle);
      
              mockMvc.perform(get("/access/user?user=3")).andExpect(status().isOk());
      }
      

      【讨论】:

      • 或者可以使用MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build()获取MockMvc
      • 在查看 MockMvc 的工作行为时,它只是测试控制器代码,因为我们使用 Mockito.when 提供服务价值。我说的对吗?
      • 何时用于模拟依赖功能。 Mockito 用于对当前类执行操作和测试,而依赖函数将通过 Mockito.when 进行模拟
      • 谢谢你。我想知道为什么 mockMvc 对象从未被注入或被注入空值。所以切换到带有独立测试的单元测试,这种构建 mockmvc 的方式正是我想要的
      • 在上面的代码中,它从不测试是否返回了 Bundle 对象。仅检查状态 ok 并不能确认它使用的是模拟版本的 accessControlService。
      【解决方案4】:

      您正在为PolicyService 创建一个模拟,但据我所知,您并没有将其注入您的MockMvc。这意味着将调用 Spring 配置中定义的 PolicyService 而不是模拟。

      通过设置将PolicyService 的模拟注入到您的MockMvc 中,或者查看Springockito 以注入模拟。

      【讨论】:

      • 感谢关于 Springockito 的提示,Tom;我会了解的。但是,我认为@Before 中的MockitoAnnotations.initMocks(this); 为我注入了模拟。
      • 我在 Eclipse 中运行了测试并在该行代码之后设置了一个断点,并且可以确认 service 已设置为 PolicyService$$EnhancerByMockitoWithCGLIB$$a8cf961b。但是,我还在 Service 类的 save 方法中设置了一个断点,它也被命中了。就好像 Spring 在 Mockito 嘲笑它之后注入该服务一样。 Springockito 是覆盖此行为的唯一方法吗?
      • 我相信@Tom 是对的。您是否尝试过为您的 MockMvc 使用 @InjectMocks?这将告诉 Mockito 在哪里注入您使用 @Mock 注释创建的模拟。
      • 你在说哪一行代码,@stevenghines?您的单元测试或MockMvc 中的那个?单元测试中的服务属性肯定会引用PolicyService$$EnhancerByMockitoWithCGLIB$$...,但MockMvc 中的服务属性不会。如果您按照@J Andy 的建议使用 @InjectMocks,模拟将被注入您的 MockMvc
      • 请参阅我原始问题中的更新 1。所以,不幸的是@j-andy,你的建议在这种情况下对我不起作用,尽管感觉应该有。我的设置中的其他地方是否有问题使 Spring 忽略了我已将模拟专门注入 MockMvc 的事实?我的下一步是尝试通过特定于测试的应用程序配置 XML 使用依赖注入吗?这将是一种耻辱,因为我真的很喜欢在测试代码中注入 MockMvc 的优雅。
      【解决方案5】:

      Spring 文档11. Testing 中的11.3.6 Spring MVC 测试框架这部分有讨论,但不知何故不清楚。

      让我们继续文档中的示例进行解释。示例测试类如下所示

      @RunWith(SpringJUnit4ClassRunner.class)
      @WebAppConfiguration
      @ContextConfiguration("test-servlet-context.xml")
      public class AccountTests {
      
          @Autowired
          private WebApplicationContext wac;
      
          private MockMvc mockMvc;
      
          @Autowired
          private AccountService accountService;
      
          // ...
      
      }
      

      假设您有 org.example.AppController 作为控制器。在 test-servlet-context.xml 中,您需要有

      <bean class="org.example.AppController">
          <property name="accountService" ref="accountService" />
      </bean>
      
      <bean id="accountService" class="org.mockito.Mockito" factory-method="mock">
          <constructor-arg value="org.example.AccountService"/>
      </bean>
      

      文档缺少控制器的接线部分。如果您使用字段注入,则需要更改 accountService 的 setter 注入。另外,请注意,constructor-arg 的值(此处为 org.example.AccountService)是一个接口,而不是一个类。

      在 AccountTests 的设置方法中,您将拥有

      @Before
      public void setup() {
          this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
      
          // You may stub with return values here
          when(accountService.findById(1)).thenReturn(...);
      }
      

      测试方法可能看起来像

      @Test
      public void testAccountId(){
          this.mockMvc.perform(...)
          .andDo(print())
          .andExpect(...);  
      }
      

      andDo(print()) 好用,做“import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;”。

      【讨论】:

        【解决方案6】:

        感谢@J Andy 的思路,我意识到我在这方面走错了路。在更新 1 中,我试图将模拟服务注入 MockMvc,但后退一步后,我意识到正在测试的不是 MockMvc,而是我想要测试的 PolicyController。

        为了提供一些背景知识,我想避免在我的 Spring MVC 应用程序中对 @Controllers 进行传统的单元测试,因为我想测试仅通过在 Spring 本身中运行控制器提供的东西(例如对控制器的 RESTful 调用行动)。这可以通过使用Spring MVC Test framework 来实现,它允许您在 Spring 中运行测试。

        您会从我最初问题的代码中看到,我在 WebApplicationContext 中运行 Spring MVC 测试 (即this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build(); ) 而我应该一直在做的是独立运行。独立运行允许我直接注入我想要测试的控制器,因此可以控制如何将服务注入控制器(即强制使用模拟服务)。

        这在代码中更容易解释。所以对于以下控制器:

        import javax.validation.Valid;
        
        import name.hines.steven.medical_claims_tracker.domain.Benefit;
        import name.hines.steven.medical_claims_tracker.domain.Policy;
        import name.hines.steven.medical_claims_tracker.services.DomainEntityService;
        import name.hines.steven.medical_claims_tracker.services.PolicyService;
        
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Controller;
        import org.springframework.validation.BindingResult;
        import org.springframework.web.bind.annotation.ModelAttribute;
        import org.springframework.web.bind.annotation.PathVariable;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RequestMethod;
        import org.springframework.web.servlet.ModelAndView;
        
        @Controller
        @RequestMapping("/policies")
        public class PolicyController extends DomainEntityController<Policy> {
        
            @Autowired
            private PolicyService service;
        
            @RequestMapping(value = "persist", method = RequestMethod.POST)
            public String createOrUpdate(@Valid @ModelAttribute("policy") Policy policy, BindingResult result) {
                if (result.hasErrors()) {
                    return "createOrUpdatePolicyForm";
                }
                service.save(policy);
                return "redirect:list";
            }
        }
        

        我现在有以下测试类,其中成功模拟了服务并且我的测试数据库不再被命中:

        package name.hines.steven.medical_claims_tracker.controllers;
        
        import static org.mockito.Matchers.isA;
        import static org.mockito.Mockito.when;
        import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
        import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.view;
        import name.hines.steven.medical_claims_tracker.domain.Policy;
        import name.hines.steven.medical_claims_tracker.services.PolicyService;
        
        import org.junit.Before;
        import org.junit.Test;
        import org.junit.runner.RunWith;
        import org.mockito.InjectMocks;
        import org.mockito.Mock;
        import org.mockito.MockitoAnnotations;
        import org.springframework.test.context.ContextConfiguration;
        import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
        import org.springframework.test.web.servlet.MockMvc;
        import org.springframework.test.web.servlet.setup.MockMvcBuilders;
        
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration({ "classpath:/applicationContext.xml" })
        public class PolicyControllerTest {
        
            @Mock
            PolicyService policyService;
        
            @InjectMocks
            PolicyController controllerUnderTest;
        
            private MockMvc mockMvc;
        
            @Before
            public void setup() {
        
                // this must be called for the @Mock annotations above to be processed
                // and for the mock service to be injected into the controller under
                // test.
                MockitoAnnotations.initMocks(this);
        
                this.mockMvc = MockMvcBuilders.standaloneSetup(controllerUnderTest).build();
        
            }
        
            @Test
            public void createOrUpdateFailsWhenInvalidDataPostedAndSendsUserBackToForm() throws Exception {
                // POST no data to the form (i.e. an invalid POST)
                mockMvc.perform(post("/policies/persist")).andExpect(status().isOk())
                .andExpect(model().attributeHasErrors("policy"))
                .andExpect(view().name("createOrUpdatePolicy"));
            }
        
            @Test
            public void createOrUpdateSuccessful() throws Exception {
        
                when(policyService.save(isA(Policy.class))).thenReturn(new Policy());
        
                mockMvc.perform(
                        post("/policies/persist").param("companyName", "Company Name")
                        .param("name", "Name").param("effectiveDate", "2001-01-01"))
                        .andExpect(status().isMovedTemporarily()).andExpect(model().hasNoErrors())
                        .andExpect(redirectedUrl("list"));
            }
        }
        

        关于 Spring,我仍然在学习很多东西,因此欢迎任何可以改进我的解释的 cmets。 This blog post 对我提出这个解决方案很有帮助。

        【讨论】:

        • 太棒了,这节省了我一天和几个小时的挫败感。
        • 请注意,如果您使用任何 AOP 切入点,它们在独立运行时不会触发。至少这是我发现的:stackoverflow.com/questions/19690907/…
        • 我也这样做了,但是当我使用 spring security 时对我不起作用,你知道吗?有什么办法吗?
        • 但是我认为你真的是在保存到数据库中,我们怎样才能避免呢?
        • 正是我想要的。谢谢!
        【解决方案7】:

        这可能是 Spring 和 Mockito 尝试同时注入 bean 的问题。我能想到的避免这些问题的一种方法是使用 Spring ReflectionTestUtils 手动注入服务模拟。

        在这种情况下,您的 setup() 方法看起来像这样

        @Before
        public void setup() {
            this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
        
            // this must be called for the @Mock annotations above to be processed.
            MockitoAnnotations.initMocks(this);
        
            // TODO: Make sure to set the field name in UUT correctly
            ReflectionTestUtils.setField( mockMvc, "service", service );
        }
        

        附:您的命名约定有点偏离恕我直言,我假设 mockMvc 是您要测试的类(UUT)。我会改用以下名称

        @Mock PolicyService mockPolicyService;
        @InjectMocks Mvc mvc;
        

        【讨论】:

        • 我尝试按照您的建议进行操作,但出现以下异常:java.lang.IllegalArgumentException: Could not find field [service] of type [null] on target [org.springframework.test.web.servlet.MockMvc@2a9dfb04] 您认为正在测试的类是 mockMvc 对象,这让我退后一步看以另一种方式来实现我想做的事情(其结果可以在我自己对问题的回答中看到)。非常感谢您花时间和麻烦帮助我探索我的问题并最终引导我找到正确的解决方案。
        • @steve,我假设您的 setfield() 方法的参数不正确。并且 mockMvc 是否甚至包含服务,正如您所说的控制器是您的 UUT?无论如何,我看到你已经以原始方式解决了这个问题,这是一种更好的方法。
        • 感谢'ReflectionTestUtils.setField(mockMvc, "service", service);'这一行它对我帮助很大
        • 一旦我使用 MockMvcBuilders.webAppContextSetup api 休息状态总是 404
        • @TiagoMedici 您的评论非常不具体,问题和我的回答都相当陈旧。通过发布包含所有必需详细信息的新问题以及可能的工作代码示例,您可能会获得更好的结果。
        猜你喜欢
        • 2016-08-18
        • 1970-01-01
        • 1970-01-01
        • 2019-06-29
        • 1970-01-01
        • 1970-01-01
        • 2018-12-22
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多