5.應用測試 5.1測試Spring MVC的控制器
5.1測試Spring MVC的控制器
在前面的章節中,我們建立了我們專案,但是我們應該怎樣知道它的執行是我們想要的結果。更重要的是,我們應用怎麼知道過了6個月或1年之後,這些應用還可以執行良好。這個問題的解決就是通過一系列的測試程式碼。
在第四章節中,我們自定義了starter,現在我們需要將這些程式碼移除。現在我們需要建立基礎測試去測試我們的應用,確保所有的控制器都會暴露出RESTful URLs的資訊。這人測試就是我們所知道的Unit Testing,我們要確保所以測試相關的bean要初始化並關聯在一起。這種型別的測試就是我們所涉及的Integration或Service Testing.
5.1.1程式碼實現
Spring Boot已經給了我們測試檔案,BookPubApplicationTests.java,在src/test/java/org/owen/bookpub資料夾下。內容如下:
@SpringBootTest class BookPubApplicationTests { @Test void contextLoads() { } } |
- 在build.gradle檔案中,我們新增spring-boot-starter-test的包依賴。
implementation 'org.springframework.boot:spring-boot-starter-test' |
- 接著,我們來擴充套件我們基礎模板BookPubApplicatonTests的類。
/* * Spring Boot 1.0使用註釋 * @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = BookPubApplication.class) @WebIntegrationTest("server.port:0") */ @RunWith(SpringRunner.class) @SpringBootTest(classes = BookPubApplication. //測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext型別的 @WebAppConfiguration public class BookPubApplicationTests { @Autowired private WebApplicationContext context; @Autowired private BookRepository repository; @Value("${local.server.port}") private int port; private MockMvc mockMvc; private TestRestTemplate restTemplate = new TestRestTemplate(); /** * 載入環境 */ @Before public void setupMockMvc() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } /** * 新增資料到資料庫 */ @Test public void contextLoads() { assertEquals(1, repository.count()); } /** * 這個呼叫首先是要開戶應用,然後通過getForObject的http地址訪問應用 * 問題:由於使用h2資料庫,所以開啟應用後就不能再建立表了。所以測試不會通過。 * 解決:需要判斷判斷H2的如果有表了就不需要建立表了。 * * 如果把應用關閉了,然後會連線不到應用,也是測試不通過 */ @Test public void webappBookIsbnApi() { Book book = restTemplate.getForObject("http://localhost:" + port + "/books/978-1-78528-415-1", Book.class); assertNotNull(book); assertEquals("Packt", book.getPublisher().getName()); } /** * 這個測試最後.andExpect(jsonPath("name").value("Packt"))這個測試不通過,因為是返回的資料型別是 * List的陣列,不為Json。 * @throws Exception */ @Test public void webappPublisherApi() throws Exception { mockMvc.perform(get("/publishers")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.parseMediaType("application/json"))) .andExpect(content().string(containsString("Packt"))) //.andExpect(jsonPath("name").value("Packt")) ; } } |
- 為了在執行時使用使用jsonPath(…),我們需要新增如下的依賴在我們build.gradle
testRuntime("com.jayway.jsonpath:json-path") |
- 然後執行測試類,選擇Gradle Test執行。在控制檯,你將會看到如下的資訊。
:compileJava :compileTestJava :testClasses :test 2020-04-13 21:40:44.694 INFO 25739 --- [ Thread-4] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebAp [email protected]: startup date [Mon Apr 13 21:40:36 CDT 2020]; root of context hierarchy 2020-04-13 21:40:44.704 INFO 25739 --- [ Thread-4] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2020-04-13 21:40:44.705 INFO 25739 --- [ Thread-4] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export 2020-04-13 21:40:44.780 INFO 25739 --- [ Thread-4]org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete BUILD SUCCESSFUL Total time: 24.635 secs |
- 通過build/reports/tests/index.html,你將會看到詳細資訊:
- 點選org.test.bookpb.BookPubApplicationTests會看到每個測試的情況和測試的時間長。
- 我們了可以點選Standard output的按鈕去檢視更多的執行日誌。
5.1.2程式碼說明
注意:以下說明是對Spring Boot1.x版本的。
首先,我們看到如下的註釋在BookPubApplication的類中:
- @RunWith(SpringJUnit4ClassRunner.class):這個是標準JUnit註釋以至於我們可以使用SpringJUnit4ClassRunner提供的Spring Test Context框架的標準JUnit測試。
- @SringApplicationConfiguration(classes = BookPubApplication.class):這是Spring Boot註釋,這個註釋決定如何去載入和配置Spring Allicaton Context到的有的測試中。這個註釋裡包含ContextConfiguration註釋,這個結構說明測試框架去使用Spring Boot的SpringApplicationContextLoader給應用。
- @WebIntegrationTest(“server.port:0”):這個註釋是說明Spring Boot是當前完全測試和要求完全初始化和啟動應用,畢竟是真實的處理。這個註釋經常與@SpringApplicationConfiguration一起用,為了完全測試。server.port:0告訴Spring Boot去啟動Tomcat時使用隨機的http埠,並且隨後會將值放入到我們宣告的@Value(“${local.server.port}”) private int port。當你測試在Jenkins或CI服務中,這個隨機的http埠是非常方便的,不然後,你有多個作業同步執行,你會的埠衝突。
接著,我們來看一下程式碼。因為是Spring Boot的測試,我們可以通過@Autowired自動裝配任何物件,或者使用@Value放入特殊的環境值。在我們的測試中,我們自動載入了WebApplicatonContext和BookRepository的物件,這些物件將會用在執行標準的[email protected]註釋的方法中。
在第一個測試方法contextLoads(),我們僅僅測試我們資料庫後BookRepository的向資料庫中插入資訊。
第二個測試方法,我們測試RESTful URL。我們使用TestRestTemplate和呼叫RESTful,並且使用的 隨機的埠。
我們用MockMvc去替換第二測試的方法。這是由Spring Test框架提供的,允許我們不需要客戶端為基礎的測試,即不需要需要我們啟動專案然後去測試。但是測試的結果是基於服務端測試的,不需要啟動專案,我們就可以與第二個方法相似的測試。
為了使用MockMvc,我伴著引入了@Autowired WebApplicationContext,並且使用MockMvcBuilders的工具去建立一個例項。我們將在啟動時執行,這樣就不需要每次測試都執行。
MockMvc提供給我們許多的能力去執行測試,這些都與web應用有關。這樣的設計與方法鏈有關,允許我們使用連線多種的測試。我們使用如下的例子:
- perform(get(…))方法是開啟web的應用。這我們的例子中,我們應用GET的請求,但是MockMvcRequestBuilders類提供的是表述的函式給每次的方法呼叫。
- andExpect(…)方法可以多次被使用,每次的請求都是一次評估。這樣的呼叫是實現了介面MockMvcMatchers的靜態灶。這些使用了大量不同的檢查,例如:響應狀態,內容型別,值儲蓄在session中,明確重發等。我們將會使用第三方的包json-path,為了去測試JSON的返回值,確保它包含了正確的元素。
5.1.3問題
接著,我們來看一下程式碼。因為是Spring Boot的測試,我們可以通過@Autowired自動裝配任何物件,或者使用@Value放入特殊的環境值。在我們的測試中,我們自動載入了WebApplicatonContext和BookRepository的物件,這些物件將會用在執行標準的[email protected]註釋的方法中。
在第一個測試方法contextLoads(),我們僅僅測試我們資料庫後BookRepository的向資料庫中插入資訊。
第二個測試方法,我們測試RESTful URL。我們使用TestRestTemplate和呼叫RESTful,並且使用的 隨機的埠。
- 在測試時會遇到Json列迴圈的問題,即報錯誤資訊為: Infinite recursion (StackOverflowError).這個是要在例項物件加上如下說明: 專案中ManyToOne 需要加上@JsonBackReference , ManyToMany或OneToMany 需要加上@JsonManagedReference
- 測試方法webappBookIsbnApi()會有問題,原因是:這個呼叫首先是要開戶應用,然後通過getForObject的http地址訪問應用問題:由於使用h2資料庫,所以開啟應用後就不能再建立表了。所以測試不會通過。
- 第三個測試中.andExpect(jsonPath("name").value("Packt")),是有問題,因為我們的操返回的不是JSON型別
5.1測試Spring MVC的控制器
在前面的章節中,我們建立了我們專案,但是我們應該怎樣知道它的執行是我們想要的結果。更重要的是,我們應用怎麼知道過了6個月或1年之後,這些應用還可以執行良好。這個問題的解決就是通過一系列的測試程式碼。
在第四章節中,我們自定義了starter,現在我們需要將這些程式碼移除。現在我們需要建立基礎測試去測試我們的應用,確保所有的控制器都會暴露出RESTful URLs的資訊。這人測試就是我們所知道的Unit Testing,我們要確保所以測試相關的bean要初始化並關聯在一起。這種型別的測試就是我們所涉及的Integration或Service Testing.
5.1.1程式碼實現
Spring Boot已經給了我們測試檔案,BookPubApplicationTests.java,在src/test/java/org/owen/bookpub資料夾下。內容如下:
@SpringBootTest
class BookPubApplicationTests {
@Test
void contextLoads() {
}
}
- 在build.gradle檔案中,我們新增spring-boot-starter-test的包依賴。
-
implementation 'org.springframework.boot:spring-boot-starter-test'
- 接著,我們來擴充套件我們基礎模板BookPubApplicatonTests的類。
-
/*
* Spring Boot 1.0使用註釋
* @RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = BookPubApplication.class)
@WebIntegrationTest("server.port:0")
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = BookPubApplication.class)
//測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext型別的
@WebAppConfiguration
public class BookPubApplicationTests
{
@Autowired
private WebApplicationContext context;
@Autowired
private BookRepository repository;
@Value("${local.server.port}")
private int port;
private MockMvc mockMvc;
private TestRestTemplate restTemplate = new TestRestTemplate();
/**
* 載入環境
*/
@Before
public void setupMockMvc()
{
mockMvc = MockMvcBuilders.webAppContextSetup(context).build();
}
/**
* 新增資料到資料庫
*/
@Test
public void contextLoads()
{
assertEquals(1, repository.count());
}
/**
* 這個呼叫首先是要開戶應用,然後通過getForObject的http地址訪問應用
* 問題:由於使用h2資料庫,所以開啟應用後就不能再建立表了。所以測試不會通過。
* 解決:需要判斷判斷H2的如果有表了就不需要建立表了。
*
* 如果把應用關閉了,然後會連線不到應用,也是測試不通過
*/
@Test
public void webappBookIsbnApi()
{
Book book = restTemplate.getForObject("http://localhost:" + port
+ "/books/978-1-78528-415-1", Book.class);
assertNotNull(book);
assertEquals("Packt", book.getPublisher().getName());
}
/**
* 這個測試最後.andExpect(jsonPath("name").value("Packt"))這個測試不通過,因為是返回的資料型別是
* List的陣列,不為Json。
* @throws Exception
*/
@Test
public void webappPublisherApi() throws Exception
{
mockMvc.perform(get("/publishers"))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.parseMediaType("application/json")))
.andExpect(content().string(containsString("Packt")))
//.andExpect(jsonPath("name").value("Packt"))
;
}
}
- 為了在執行時使用使用jsonPath(…),我們需要新增如下的依賴在我們build.gradle
-
testRuntime("com.jayway.jsonpath:json-path")
- 然後執行測試類,選擇Gradle Test執行。在控制檯,你將會看到如下的資訊。
-
:compileJava
:compileTestJava
:testClasses
:test
2020-04-13 21:40:44.694 INFO 25739 --- [ Thread-4]
ationConfigEmbeddedWebApplicationContext : Closing
org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebAp
[email protected]: startup date [Mon Apr 13 21:40:36 CDT 2020];
root of context hierarchy
2020-04-13 21:40:44.704 INFO 25739 --- [ Thread-4]
j.LocalContainerEntityManagerFactoryBean : Closing JPA
EntityManagerFactory for persistence unit 'default'
2020-04-13 21:40:44.705 INFO 25739 --- [ Thread-4]
org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl
schema export
2020-04-13 21:40:44.780 INFO 25739 --- [ Thread-4]org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export
complete
BUILD SUCCESSFUL
Total time: 24.635 secs
- 通過build/reports/tests/index.html,你將會看到詳細資訊:
- 點選org.test.bookpb.BookPubApplicationTests會看到每個測試的情況和測試的時間長。
- 我們了可以點選Standard output的按鈕去檢視更多的執行日誌。
-
5.1.2程式碼說明
注意:以下說明是對Spring Boot1.x版本的。
首先,我們看到如下的註釋在BookPubApplication的類中:
- @RunWith(SpringJUnit4ClassRunner.class):這個是標準JUnit註釋以至於我們可以使用SpringJUnit4ClassRunner提供的Spring Test Context框架的標準JUnit測試。
- @SringApplicationConfiguration(classes = BookPubApplication.class):這是Spring Boot註釋,這個註釋決定如何去載入和配置Spring Allicaton Context到的有的測試中。這個註釋裡包含ContextConfiguration註釋,這個結構說明測試框架去使用Spring Boot的SpringApplicationContextLoader給應用。
- @WebIntegrationTest(“server.port:0”):這個註釋是說明Spring Boot是當前完全測試和要求完全初始化和啟動應用,畢竟是真實的處理。這個註釋經常與@SpringApplicationConfiguration一起用,為了完全測試。server.port:0告訴Spring Boot去啟動Tomcat時使用隨機的http埠,並且隨後會將值放入到我們宣告的@Value(“${local.server.port}”) private int port。當你測試在Jenkins或CI服務中,這個隨機的http埠是非常方便的,不然後,你有多個作業同步執行,你會的埠衝突。
5.1測試Spring MVC的控制器
在前面的章節中,我們建立了我們專案,但是我們應該怎樣知道它的執行是我們想要的結果。更重要的是,我們應用怎麼知道過了6個月或1年之後,這些應用還可以執行良好。這個問題的解決就是通過一系列的測試程式碼。
在第四章節中,我們自定義了starter,現在我們需要將這些程式碼移除。現在我們需要建立基礎測試去測試我們的應用,確保所有的控制器都會暴露出RESTful URLs的資訊。這人測試就是我們所知道的Unit Testing,我們要確保所以測試相關的bean要初始化並關聯在一起。這種型別的測試就是我們所涉及的Integration或Service Testing.
5.1.1程式碼實現
Spring Boot已經給了我們測試檔案,BookPubApplicationTests.java,在src/test/java/org/owen/bookpub資料夾下。內容如下:
@SpringBootTest class BookPubApplicationTests { @Test void contextLoads() { } } |
在build.gradle檔案中,我們新增spring-boot-starter-test的包依賴。
implementation 'org.springframework.boot:spring-boot-starter-test' |
/* * Spring Boot 1.0使用註釋 * @RunWith(SpringJUnit4ClassRunner.class) @SpringApplicationConfiguration(classes = BookPubApplication.class) @WebIntegrationTest("server.port:0") */ @RunWith(SpringRunner.class) @SpringBootTest(classes = BookPubApplication.class) //測試環境使用,用來表示測試環境使用的ApplicationContext將是WebApplicationContext型別的 @WebAppConfiguration public class BookPubApplicationTests { @Autowired private WebApplicationContext context; @Autowired private BookRepository repository; @Value("${local.server.port}") private int port; private MockMvc mockMvc; private TestRestTemplate restTemplate = new TestRestTemplate(); /** * 載入環境 */ @Before public void setupMockMvc() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); } /** * 新增資料到資料庫 */ @Test public void contextLoads() { assertEquals(1, repository.count()); } /** * 這個呼叫首先是要開戶應用,然後通過getForObject的http地址訪問應用 * 問題:由於使用h2資料庫,所以開啟應用後就不能再建立表了。所以測試不會通過。 * 解決:需要判斷判斷H2的如果有表了就不需要建立表了。 * * 如果把應用關閉了,然後會連線不到應用,也是測試不通過 */ @Test public void webappBookIsbnApi() { Book book = restTemplate.getForObject("http://localhost:" + port + "/books/978-1-78528-415-1", Book.class); assertNotNull(book); assertEquals("Packt", book.getPublisher().getName()); } /** * 這個測試最後.andExpect(jsonPath("name").value("Packt"))這個測試不通過,因為是返回的資料型別是 * List的陣列,不為Json。 * @throws Exception */ @Test public void webappPublisherApi() throws Exception { mockMvc.perform(get("/publishers")) .andExpect(status().isOk()) .andExpect(content().contentType(MediaType.parseMediaType("application/json"))) .andExpect(content().string(containsString("Packt"))) //.andExpect(jsonPath("name").value("Packt")) ; } } |
testRuntime("com.jayway.jsonpath:json-path") |
:compileJava :compileTestJava :testClasses :test 2020-04-13 21:40:44.694 INFO 25739 --- [ Thread-4] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebAp [email protected]: startup date [Mon Apr 13 21:40:36 CDT 2020]; root of context hierarchy 2020-04-13 21:40:44.704 INFO 25739 --- [ Thread-4] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default' 2020-04-13 21:40:44.705 INFO 25739 --- [ Thread-4] org.hibernate.tool.hbm2ddl.SchemaExport : HHH000227: Running hbm2ddl schema export 2020-04-13 21:40:44.780 INFO 25739 --- [ Thread-4]org.hibernate.tool.hbm2ddl.SchemaExport : HHH000230: Schema export complete BUILD SUCCESSFUL Total time: 24.635 secs |
5.1.2程式碼說明
注意:以下說明是對Spring Boot1.x版本的。
首先,我們看到如下的註釋在BookPubApplication的類中:
接著,我們來看一下程式碼。因為是Spring Boot的測試,我們可以通過@Autowired自動裝配任何物件,或者使用@Value放入特殊的環境值。在我們的測試中,我們自動載入了WebApplicatonContext和BookRepository的物件,這些物件將會用在執行標準的[email protected]註釋的方法中。
在第一個測試方法contextLoads(),我們僅僅測試我們資料庫後BookRepository的向資料庫中插入資訊。
第二個測試方法,我們測試RESTful URL。我們使用TestRestTemplate和呼叫RESTful,並且使用的 隨機的埠。
我們用MockMvc去替換第二測試的方法。這是由Spring Test框架提供的,允許我們不需要客戶端為基礎的測試,即不需要需要我們啟動專案然後去測試。但是測試的結果是基於服務端測試的,不需要啟動專案,我們就可以與第二個方法相似的測試。
為了使用MockMvc,我伴著引入了@Autowired WebApplicationContext,並且使用MockMvcBuilders的工具去建立一個例項。我們將在啟動時執行,這樣就不需要每次測試都執行。
MockMvc提供給我們許多的能力去執行測試,這些都與web應用有關。這樣的設計與方法鏈有關,允許我們使用連線多種的測試。我們使用如下的例子:
5.1.3問題
們用MockMvc去替換第二測試的方法。這是由Spring Test框架提供的,允許我們不需要客戶端為基礎的測試,即不需要需要我們啟動專案然後去測試。但是測試的結果是基於服務端測試的,不需要啟動專案,我們就可以與第二個方法相似的測試。
為了使用MockMvc,我伴著引入了@Autowired WebApplicationContext,並且使用MockMvcBuilders的工具去建立一個例項。我們將在啟動時執行,這樣就不需要每次測試都執行。
MockMvc提供給我們許多的能力去執行測試,這些都與web應用有關。這樣的設計與方法鏈有關,允許我們使用連線多種的測試。我們使用如下的例子:
5.1.3問題
- 接著,我們來擴充套件我們基礎模板BookPubApplicatonTests的類。
- 為了在執行時使用使用jsonPath(…),我們需要新增如下的依賴在我們build.gradle
- 然後執行測試類,選擇Gradle Test執行。在控制檯,你將會看到如下的資訊。
- 通過build/reports/tests/index.html,你將會看到詳細資訊:
- 點選org.test.bookpb.BookPubApplicationTests會看到每個測試的情況和測試的時間長。
- 我們了可以點選Standard output的按鈕去檢視更多的執行日誌。
- @RunWith(SpringJUnit4ClassRunner.class):這個是標準JUnit註釋以至於我們可以使用SpringJUnit4ClassRunner提供的Spring Test Context框架的標準JUnit測試。
- @SringApplicationConfiguration(classes = BookPubApplication.class):這是Spring Boot註釋,這個註釋決定如何去載入和配置Spring Allicaton Context到的有的測試中。這個註釋裡包含ContextConfiguration註釋,這個結構說明測試框架去使用Spring Boot的SpringApplicationContextLoader給應用。
- @WebIntegrationTest(“server.port:0”):這個註釋是說明Spring Boot是當前完全測試和要求完全初始化和啟動應用,畢竟是真實的處理。這個註釋經常與@SpringApplicationConfiguration一起用,為了完全測試。server.port:0告訴Spring Boot去啟動Tomcat時使用隨機的http埠,並且隨後會將值放入到我們宣告的@Value(“${local.server.port}”) private int port。當你測試在Jenkins或CI服務中,這個隨機的http埠是非常方便的,不然後,你有多個作業同步執行,你會的埠衝突。
- perform(get(…))方法是開啟web的應用。這我們的例子中,我們應用GET的請求,但是MockMvcRequestBuilders類提供的是表述的函式給每次的方法呼叫。
- 在測試時會遇到Json列迴圈的問題,即報錯誤資訊為: Infinite recursion (StackOverflowError).這個是要在例項物件加上如下說明: 專案中ManyToOne 需要加上@JsonBackReference , ManyToMany或OneToMany 需要加上@JsonManagedReference
- 測試方法webappBookIsbnApi()會有問題,原因是:這個呼叫首先是要開戶應用,然後通過getForObject的http地址訪問應用問題:由於使用h2資料庫,所以開啟應用後就不能再建立表了。所以測試不會通過。
- 第三個測試中.andExpect(jsonPath("name").value("Packt")),是有問題,因為我們的操返回的不是JSON型別
- andExpect(…)方法可以多次被使用,每次的請求都是一次評估。這樣的呼叫是實現了介面MockMvcMatchers的靜態灶。這些使用了大量不同的檢查,例如:響應狀態,內容型別,值儲蓄在session中,明確重發等。我們將會使用第三方的包json-path,為了去測試JSON的返回值,確保它包含了正確的元素。
- perform(get(…))方法是開啟web的應用。這我們的例子中,我們應用GET的請求,但是MockMvcRequestBuilders類提供的是表述的函式給每次的方法呼叫。
- andExpect(…)方法可以多次被使用,每次的請求都是一次評估。這樣的呼叫是實現了介面MockMvcMatchers的靜態灶。這些使用了大量不同的檢查,例如:響應狀態,內容型別,值儲蓄在session中,明確重發等。我們將會使用第三方的包json-path,為了去測試JSON的返回值,確保它包含了正確的元素。
- 在測試時會遇到Json列迴圈的問題,即報錯誤資訊為: Infinite recursion (StackOverflowError).這個是要在例項物件加上如下說明: 專案中ManyToOne 需要加上@JsonBackReference , ManyToMany或OneToMany 需要加上@JsonManagedReference
- 測試方法webappBookIsbnApi()會有問題,原因是:這個呼叫首先是要開戶應用,然後通過getForObject的http地址訪問應用問題:由於使用h2資料庫,所以開啟應用後就不能再建立表了。所以測試不會通過。
- 第三個測試中.andExpect(jsonPath("name").value("Packt")),是有問題,因為我們的操返回的不是JSON型別