http://www.petrikainulainen.net/programming/spring-framework/unit-testing-of-spring-mvc-controllers-configuration/

Writing unit tests for Spring MVC controllers has traditionally been both simple and problematic.

Although it is pretty simple to write unit tests which invoke controller methods, the problem is that those unit tests are not comprehensive enough.

For example, we cannot test controller mappings, validation and exception handling just by invoking the tested controller method.

Spring MVC Test solved this problem by giving us the possibility to invoke controller methods through theDispatcherServlet.

This is the first part of my tutorial which describes the unit testing of Spring MVC controllers and it describes how we can configure our unit tests.

Let’s get started.

Getting the Required Dependencies with Maven

We can get the required dependencies by declaring the following testing dependencies in our pom.xmlfile:

  • JUnit 4.11
  • Mockito Core 1.9.5
  • Spring Test 3.2.3.RELEASE

The relevant part of our pom.xml file looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <version>1.9.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>3.2.3.RELEASE</version>
    <scope>test</scope>
</dependency>

Note: If you have to use Spring Framework 3.1, you can write unit tests for your controllers by using spring-test-mvc. This project was included in the spring-test module when Spring Framework 3.2 was released.

Let’s move on and take a quick look at our example application.

The Anatomy of Our Example Application

The example application of this tutorial provides CRUD operations for todo entries. In order to understand the configuration of our test class, we must have some knowledge about the tested controller class.

At this point, we need to know the answers to these questions:

  • What dependencies does it have?
  • How is it instantiated?

We can get the answers to those questions by taking a look at the source code of the TodoControllerclass. The relevant part of the TodoController class looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
 
@Controller
public class TodoController {
 
    private final TodoService service;
 
    private final MessageSource messageSource;
 
    @Autowired
    public TodoController(MessageSource messageSource, TodoService service) {
        this.messageSource = messageSource;
        this.service = service;
    }
 
    //Other methods are omitted.
}

As we can see, our controller class has two dependencies: TodoService and MessageSource. Also, we can see that our controller class uses constructor injection.

At this point this is all there information we need. Next we will talk about our application context configuration.

Configuring the Application Context

Maintaining a separate application context configurations for our application and our tests is cumbersome. Also, It can lead into problems if we change something in the application context configuration of our application but forget to do the same change for our test context.

That is why the application context configuration of the example application has been divided in a such way that we can reuse parts of it in our tests.

Our application context configuration has been divided as follows:

  • The first application configuration class is called ExampleApplicationContext and it is the “main” configuration class of our application.
  • The second configuration class is responsible of configuring the web layer of our application. The name of this class is WebAppContext and it is the configuration class which we will use in our tests.
  • The third configuration class is called PersistenceContext and it contains the persistence configuration of our application.

Note: The example application has also a working application context configuration which uses XML configuration files. The XML configuration files which correspond with the Java configuration classes are:exampleApplicationContext.xml, exampleApplicationContext-web.xml and exampleApplicationContext-persistence.xml.

Let’s take a look at the application context configuration of our web layer and find out how we can configure our test context.

Configuring the Web Layer

The application context configuration of the web layer has the following responsibilities:

  1. It enables the annotation driven Spring MVC.
  2. It configures the location of static resources such as CSS files and Javascript files.
  3. It ensures that the static resources are served by the container’s default servlet.
  4. It ensures that the controller classes are found during component scan.
  5. It configures the ExceptionResolver bean.
  6. It configures the ViewResolver bean.

Let’s move on and take a look at the Java configuration class and the XML configuration file.

Java Configuration

If we use Java configuration, the source code of the WebAppContext class looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
 
import java.util.Properties;
 
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = {
        "net.petrikainulainen.spring.testmvc.common.controller",
        "net.petrikainulainen.spring.testmvc.todo.controller"
})
public class WebAppContext extends WebMvcConfigurerAdapter {
 
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/static/**").addResourceLocations("/static/");
    }
 
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
 
    @Bean
    public SimpleMappingExceptionResolver exceptionResolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
 
        Properties exceptionMappings = new Properties();
 
        exceptionMappings.put("net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException", "error/404");
        exceptionMappings.put("java.lang.Exception", "error/error");
        exceptionMappings.put("java.lang.RuntimeException", "error/error");
 
        exceptionResolver.setExceptionMappings(exceptionMappings);
 
        Properties statusCodes = new Properties();
 
        statusCodes.put("error/404", "404");
        statusCodes.put("error/error", "500");
 
        exceptionResolver.setStatusCodes(statusCodes);
 
        return exceptionResolver;
    }
 
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
 
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
 
        return viewResolver;
    }
}

XML Configuration

If we use XML configuration, the content of the exampleApplicationContext-web.xml file looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">
 
    <mvc:annotation-driven/>
 
    <mvc:resources mapping="/static/**" location="/static/"/>
    <mvc:default-servlet-handler/>
 
    <context:component-scan base-package="net.petrikainulainen.spring.testmvc.common.controller"/>
    <context:component-scan base-package="net.petrikainulainen.spring.testmvc.todo.controller"/>
 
    <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <prop key="net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException">error/404</prop>
                <prop key="java.lang.Exception">error/error</prop>
                <prop key="java.lang.RuntimeException">error/error</prop>
            </props>
        </property>
        <property name="statusCodes">
            <props>
                <prop key="error/404">404</prop>
                <prop key="error/error">500</prop>
            </props>
        </property>
    </bean>
 
    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/"/>
        <property name="suffix" value=".jsp"/>
        <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    </bean>
</beans>

Configuring the Test Context

The configuration of our test context has two responsibilities:

  1. It configures a MessageSource bean which is used by our controller class (feedback messages) and Spring MVC (validation error messages). The reason why we need to do this is that theMessageSource bean is configured in the “main” configuration class (or file) of our application context configuration.
  2. It creates a TodoService mock which is injected to our controller class.

Let’s find out how we configure our test context by using Java configuration class and XML configuration file.

Java Configuration

If we configure our test context by using Java configuration, the source code of the TestContext class looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import org.mockito.Mockito;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
 
@Configuration
public class TestContext {
 
    @Bean
    public MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
 
        messageSource.setBasename("i18n/messages");
        messageSource.setUseCodeAsDefaultMessage(true);
 
        return messageSource;
    }
 
    @Bean
    public TodoService todoService() {
        return Mockito.mock(TodoService.class);
    }
}

XML Configuration

If we configure our test context by using an XML configuration, the content of the testContext.xml file looks as follow:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
 
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="i18n/messages"/>
        <property name="useCodeAsDefaultMessage" value="true"/>
    </bean>
 
    <bean id="todoService" name="todoService" class="org.mockito.Mockito" factory-method="mock">
        <constructor-arg value="net.petrikainulainen.spring.testmvc.todo.service.TodoService"/>
    </bean>
</beans>

Configuring The Test Class

We can configure our test class by using one of the following options:

  1. The Standalone configuration allows us to register one or more controllers (classes annotated with the@Controller annotation) and configure the Spring MVC infrastructure programatically. This approach is a viable option if our Spring MVC configuration is simple and straight-forward.
  2. The WebApplicationContext based configuration allows us the configure Spring MVC infrastructure by using a fully initialized WebApplicationContext. This approach is better if our Spring MVC configuration is so complicated that using standalone configuration does not make any sense.

Let’s move on and find out how we can configure our test class by using both configuration options.

Using Standalone Configuration

We can configure our test class by following these steps:

  1. Annotate the class with the @RunWith annotation and ensure that test is executed by using theMockitoJUnitRunner.
  2. Add a MockMvc field to the test class.
  3. Add a TodoService field to the test class and annotate the field with the @Mock annotation. This annotation marks the field as a mock. The field is initialized by the MockitoJUnitRunner.
  4. Add a private exceptionResolver() method to the class. This method creates a newSimpleMappingExceptionResolver object, configures it, and returns the created object.
  5. Add a private messageSource() method to the class. This method creates a newResourceBundleMessageSource object, configures it, and returns the created object.
  6. Add a private validator() method to the class. This method creates a new LocalValidatorFactoryBeanobject and returns the created object.
  7. Add a private viewResolver() method to the the class. This method creates a newInternalResourceViewResolver object, configures it, and returns the created object.
  8. Add a setUp() method to the test class and annotate the method with the @Before annotation. This ensures that the method is invoked before each test. This method creates a new MockMvc object by calling the standaloneSetup() method of the MockMvcBuilders class and configures the Spring MVC infrastructure programmatically.

The source code of our test class looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.MessageSource;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;
 
import java.util.Properties;
 
@RunWith(MockitoJUnitRunner.class)
public class StandaloneTodoControllerTest {
 
    private MockMvc mockMvc;
 
    @Mock
    private TodoService todoServiceMock;
 
    @Before
    public void setUp() {
        mockMvc = MockMvcBuilders.standaloneSetup(new TodoController(messageSource(), todoServiceMock))
                .setHandlerExceptionResolvers(exceptionResolver())
                .setValidator(validator())
                .setViewResolvers(viewResolver())
                .build();
    }
 
    private HandlerExceptionResolver exceptionResolver() {
        SimpleMappingExceptionResolver exceptionResolver = new SimpleMappingExceptionResolver();
 
        Properties exceptionMappings = new Properties();
 
        exceptionMappings.put("net.petrikainulainen.spring.testmvc.todo.exception.TodoNotFoundException", "error/404");
        exceptionMappings.put("java.lang.Exception", "error/error");
        exceptionMappings.put("java.lang.RuntimeException", "error/error");
 
        exceptionResolver.setExceptionMappings(exceptionMappings);
 
        Properties statusCodes = new Properties();
 
        statusCodes.put("error/404", "404");
        statusCodes.put("error/error", "500");
 
        exceptionResolver.setStatusCodes(statusCodes);
 
        return exceptionResolver;
    }
 
    private MessageSource messageSource() {
        ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource();
 
        messageSource.setBasename("i18n/messages");
        messageSource.setUseCodeAsDefaultMessage(true);
 
        return messageSource;
    }
 
    private LocalValidatorFactoryBean validator() {
        return new LocalValidatorFactoryBean();
    }
 
    private ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
 
        viewResolver.setViewClass(JstlView.class);
        viewResolver.setPrefix("/WEB-INF/jsp/");
        viewResolver.setSuffix(".jsp");
 
        return viewResolver;
    }
}

Using the standalone configuration has two problems:

  1. Our test class looks like a mess even though our Spring MVC configuration is rather simple. Naturally, we could clean it up by moving the creation of Spring MVC infrastructure components into a separate class. This is left as an exercise for the reader.
  2. We have to duplicate the configuration of Spring MVC infrastructure components. This means that if we change something in the application context configuration of our application, we must remember to do the same change to our tests as well.

Using WebApplicationContext Based Configuration

We can configure our test class by following these steps:

  1. Annotate the test class with the @RunWith annotation and ensure that the test is executed by using theSpringJUnit4ClassRunner.
  2. Annotate the class with the @ContextConfiguration annotation and ensure that the correct configuration classes (or XML configuration files) are used. If we want to use Java configuration, we have to set the configuration classes as the value of the classes attribute. On the other hand, if we prefer XML configuration, we have to set the configuration files as the value of the locations attribute.
  3. Annotate the class with the @WebAppConfiguration annotation. This annotation ensures that the application context which is loaded for our test is a WebApplicationContext.
  4. Add a MockMvc field to the test class.
  5. Add a TodoService field to the test class and annotate the field with the @Autowired annotation.
  6. Add a WebApplicationContext field to the test class and annotate the field with the @Autowiredannotation.
  7. Add a setUp() method to the test class and annotate the method with the @Before annotation. This ensures that the method is called before each test. This method has responsibilities: it resets the service mock before each test and create a new MockMvc object by calling thewebAppContextSetup() method of the MockMvcBuilders class.

The source code of our test class looks as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
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)
@ContextConfiguration(classes = {TestContext.class, WebAppContext.class})
//@ContextConfiguration(locations = {"classpath:testContext.xml", "classpath:exampleApplicationContext-web.xml"})
@WebAppConfiguration
public class WebApplicationContextTodoControllerTest {
 
    private MockMvc mockMvc;
 
    @Autowired
    private TodoService todoServiceMock;
 
    @Autowired
    private WebApplicationContext webApplicationContext;
 
    @Before
    public void setUp() {
        //We have to reset our mock between tests because the mock objects
        //are managed by the Spring container. If we would not reset them,
        //stubbing and verified behavior would "leak" from one test to another.
        Mockito.reset(todoServiceMock);
 
        mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
    }
}

The configuration of our test class looks a lot cleaner than the configuration which uses standalone configuration. However, the “downside” is that our test uses the full Spring MVC infrastructure. This might be an overkill if our test class really uses only a few components.

Summary

We have now configured our unit test class by using both the standalone setup and theWebApplicationContext based setup. This blog post has taught us two things:

  • We learned that it is important to divide the application context configuration in a such way that we can reuse parts of it in our tests.
  • We learned the difference between the standalone configuration and the WebApplicationContextbased configuration.

The next part of this tutorial describes how we can write unit tests for “normal” Spring MVC controllers.

P.S. The example application of this blog post is available at Github.

If you want to learn more about Spring MVC Test, you should read my Spring MVC Test tutorial.

Spring MVC Test -Controller的更多相关文章

  1. Spring mvc框架 controller间跳转 ,重定向 ,传参

     一.需求背景     1. 需求:spring MVC框架controller间跳转,需重定向.有几种情况:不带参数跳转,带参数拼接url形式跳转,带参数不拼接参数跳转,页面也能显示.   @Req ...

  2. spring mvc在Controller中获取ApplicationContext

    spring mvc在Controller中获取ApplicationContext web.xml中进行正常的beans.xml和spring-mvc.xml的配置: 需要在beans.xml中进行 ...

  3. Spring MVC 之@Controller@RequestMapping详解

    一:配置web.xml 1)问题:spring项目中有多个配置文件mvc.xml   dao.xml 2)解决:在web.xml中 <init-param> <param-name& ...

  4. 如何开始创建第一个基于Spring MVC的Controller

    万事开头难,良好的开端是成功的一半! 以下示例怎么开始创建我们的第一个Spring MVC控制器Controller 1.新建一个java类,命名为:MyFirstController,包含以下代码, ...

  5. 针对spring mvc的controller内存马-学习和实验

    1 基础 实际上java内存马的注入已经有很多方式了,这里在学习中动手研究并写了一款spring mvc应用的内存马.一般来说实现无文件落地的java内存马注入,通常是利用反序列化漏洞,所以动手写了一 ...

  6. 通过拦截器Interceptor实现Spring MVC中Controller接口访问信息的记录

    java web工程项目使用了Spring+Spring MVC+Hibernate的结构,在Controller中的方法都是用于处理前端的访问信息,Controller通过调用Service进行业务 ...

  7. spring mvc 的Controller类默认Scope是单例(singleton)的

    使用Spring MVC有一段时间了,之前一直使用Struts2,在struts2中action都是原型(prototype)的, 说是因为线程安全问题,对于Spring MVC中bean默认都是(s ...

  8. Spring MVC中Controller如何将数据返回给页面

    要实现Controller返回数据给页面,Spring MVC 提供了以下几种途径: ModelAndView:将视图和数据封装成ModelAndView对象,作为方法的返回值,数据最终会存到Http ...

  9. spring mvc 注解@Controller @RequestMapping @Resource的详细例子

    现在主流的Web MVC框架除了Struts这个主力 外,其次就是Spring MVC了,因此这也是作为一名程序员需要掌握的主流框架,框架选择多了,应对多变的需求和业务时,可实行的方案自然就多了.不过 ...

随机推荐

  1. CSS命名法

    一.Css命名法: 1.驼峰命名法:除第一个单词的首字母小写之外,其余的单词首字母均大写.如:#headBlock(2). 2.帕斯卡命名法:所有单词的首字母均大写.如:#HeadBlock(3). ...

  2. [Android Pro] Gradle tip #3-Task顺序

    reference to : http://blog.csdn.net/lzyzsd/article/details/46935405 原文链接 我注意到我在使用Gradle的时候遇到的大多数问题都是 ...

  3. mongochef如何链接有权限的mongodb3.x数据库

    废话不多说,直接上图: 1.打开mongochef 2.打开的界面是这样的: 3.点击connect,上图红色框中的按钮,不要点下拉三角 4.点击New Connection按钮 5.1:上图标注1, ...

  4. 关于 UICollectionViewCell 的一些陷阱

    如果直接使用 UICollectionViewCell 的自带属性 selected 来自定义一些样式,如: - (void)setSelected:(BOOL)selected { [super s ...

  5. SQLServer自定义函数简单演示

    CREATE FUNCTION [ schema_name. ] function_name ( [ { @parameter_name [ AS ][ type_schema_name. ] par ...

  6. NYOJ题目273字母小游戏

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAswAAAIZCAIAAAA9QP7RAAAgAElEQVR4nO3dPXKjzBqG4bMJ5VqIU2

  7. candence 笔记总结

    1.解决candece 启动后提示找不到licence文件的错误: candece的安装就不说了,按照破解步骤一步一步来就行了,但是装完后发现每次启动都会报错 "OrCAD Capture ...

  8. SQL分页存储过程——表名、返回的列、排序字段、排序类型、条件、页尺寸、页码

    ALTER PROCEDURE [dbo].[SP_LGY_ICU_PAGECUT] ), -- 表名 ) = '*', -- 需要返回的列 )='''', -- 排序的欄位名 , -- 設置排序類型 ...

  9. Shell编程基础教程5--文本过滤、正则表达式、相关命令

    5.文本过滤.正则表达式.相关命令    5.1.正则表达式(什么是正则表达式?正则表达式怎么进行匹配?常用命令)        简介:            一种用来描述文本模式的特殊语法      ...

  10. HDU5556 Land of Farms(二分图 2015 合肥区域赛)

    容易想到将问题转化为求图的独立数问题 ,但求一般图的独立集是一个NPC问题,需要一些转化. 状态压缩,枚举每个上古农场是否选择,然后将剩下的新农场根据i + j奇偶性分为x , y集. 结果为 max ...