最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會(huì)員登陸 & 注冊

使用 Spring Boot 和 @WebMvcTest 測試 MVC Web Controller

2021-10-27 19:40 作者:信碼由韁  | 我要投稿

【注】本文譯自: Testing MVC Web Controllers with Spring Boot and @WebMvcTest - Reflectoring

在有關(guān)使用 Spring Boot 進(jìn)行測試的系列的第二部分中,我們將了解 Web 控制器。首先,我們將探索 Web 控制器的實(shí)際作用,這樣我們就可以構(gòu)建涵蓋其所有職責(zé)的測試。

然后,我們將找出如何在測試中涵蓋這些職責(zé)。只有涵蓋了這些職責(zé),我們才能確保我們的控制器在生產(chǎn)環(huán)境中按預(yù)期運(yùn)行。

? 代碼示例

本文附有?GitHub 上的工作代碼示例。

依賴

我們將使用 JUnit Jupiter (JUnit 5) 作為測試框架,使用 Mockito 進(jìn)行模擬,使用 AssertJ 來創(chuàng)建斷言,使用 Lombok 來減少樣板代碼:

dependencies {

? ? compile('org.springframework.boot:spring-boot-starter-web')

? ? compileOnly('org.projectlombok:lombok')

? ? testCompile('org.springframework.boot:spring-boot-starter-test')

? ? testCompile('org.junit.jupiter:junit-jupiter:5.4.0')

? ? testCompile('org.mockito:mockito-junit-jupiter:2.23.0')

}

AssertJ?和 Mockito 跟隨?spring-boot-starter-test 依賴自動(dòng)獲得。

Web 控制器的職責(zé)

讓我們從一個(gè)典型的 REST 控制器開始:


@RestController

@RequiredArgsConstructor

class RegisterRestController {

? ? private final RegisterUseCase registerUseCase;

? ? @PostMapping("/forums/{forumId}/register")

? ? UserResource register(@PathVariable("forumId") Long forumId, @Valid @RequestBody UserResource userResource,

? ? ? ? ? ? @RequestParam("sendWelcomeMail") boolean sendWelcomeMail) {

? ? ? ? User user = new User(userResource.getName(), userResource.getEmail());

? ? ? ? Long userId = registerUseCase.registerUser(user, sendWelcomeMail);

? ? ? ? return new UserResource(userId, user.getName(), user.getEmail());

? ? }

}

控制器方法用?@PostMapping 注解來定義它應(yīng)該偵聽的 URL、HTTP 方法和內(nèi)容類型。

它通過用 @PathVariable、@RequestBody 和?@RequestParam 注解的參數(shù)獲取輸入,這些參數(shù)會(huì)從傳入的 HTTP 請(qǐng)求中自動(dòng)填充。

參數(shù)可以使用 @Valid?進(jìn)行注解,以指示 Spring 應(yīng)該對(duì)它們?bean 驗(yàn)證。

然后控制器使用這些參數(shù),調(diào)用業(yè)務(wù)邏輯返回一個(gè)普通的 Java 對(duì)象,默認(rèn)情況下該對(duì)象會(huì)自動(dòng)映射到 JSON 并寫入 HTTP 響應(yīng)體。

這里有很多 spring 魔法??傊?,對(duì)于每個(gè)請(qǐng)求,控制器通常會(huì)執(zhí)行以下步驟:

我們應(yīng)該注意不要添加更多的職責(zé),比如執(zhí)行業(yè)務(wù)邏輯。否則,我們的控制器測試將變得臃腫且無法維護(hù)。

我們將如何編寫有意義的測試,涵蓋所有這些職責(zé)?

單元測試還是集成測試?

我們寫單元測試嗎?還是集成測試?到底有什么區(qū)別?讓我們討論這兩種方法并決定其中一種。

在單元測試中,我們將單獨(dú)測試控制器。這意味著我們將實(shí)例化一個(gè)控制器對(duì)象,模擬業(yè)務(wù)邏輯,然后調(diào)用控制器的方法并驗(yàn)證響應(yīng)。

這對(duì)我們有用嗎?讓我們檢查一下可以單獨(dú)的單元測試中涵蓋上面確定的 6 個(gè)職責(zé)中的哪一個(gè):

與 Spring 的集成測試會(huì)啟動(dòng)一個(gè)包含我們需要的所有 bean 的 Spring 應(yīng)用程序上下文。這包括負(fù)責(zé)偵聽某些 URL、與 JSON 之間進(jìn)行序列化和反序列化以及將異常轉(zhuǎn)換為 HTTP 的框架 bean。這些 bean 將評(píng)估簡單單元測試會(huì)忽略的注釋??傊唵蔚膯卧獪y試不會(huì)覆蓋 HTTP 層。所以,我們需要在我們的測試中引入 Spring 來為我們做 HTTP 魔法。因此,我們正在構(gòu)建一個(gè)集成測試來測試我們的控制器代碼和 Spring 為 HTTP 支持提供的組件之間的集成。

那么,我們該怎么做呢?

使用?@WebMvcTest 驗(yàn)證控制器職責(zé)

Spring Boot 提供了?@WebMvcTest 注釋來啟動(dòng)一個(gè)應(yīng)用程序上下文,該上下文只包含測試 Web 控制器所需的 bean:

@ExtendWith(SpringExtension.class)

@WebMvcTest(controllers = RegisterRestController.class)

class RegisterRestControllerTest {

? ? @Autowired

? ? private MockMvc mockMvc;

? ? @Autowired

? ? private ObjectMapper objectMapper;

? ? @MockBean

? ? private RegisterUseCase registerUseCase;

? @Test

? void whenValidInput_thenReturns200() throws Exception {

? ? mockMvc.perform(...);

? }

}

@ExtendWith

本教程中的代碼示例使用?@ExtendWith?批注告訴 JUnit 5 啟用 Spring 支持。從 Spring Boot 2.1 開始,我們不再需要加載 SpringExtension,因?yàn)樗鳛樵⑨尠?Spring Boot 測試注解中,例如 @DataJpaTest、@WebMvcTest 和 @SpringBootTest。

我們現(xiàn)在可以?@Autowire 從應(yīng)用程序上下文中獲取我們需要的所有 bean。Spring Boot 自動(dòng)提供了像?ObjectMapper 這樣的 bean 來映射到 JSON 和一個(gè)?MockMvc 實(shí)例來模擬 HTTP 請(qǐng)求。

我們使用?@MockBean 來模擬業(yè)務(wù)邏輯,因?yàn)槲覀儾幌霚y試控制器和業(yè)務(wù)邏輯之間的集成,而是控制器和 HTTP 層之間的集成。@MockBean 自動(dòng)用 Mockito 模擬替換應(yīng)用程序上下文中相同類型的 bean。

您可以在我關(guān)于模擬的文章中閱讀有關(guān)?@MockBean 注解的更多信息。

使用帶或不帶?controllers 參數(shù)的 @WebMvcTest?

通過在上面的示例中將? controllers 參數(shù)設(shè)置為 RegisterRestController.class,我們告訴 Spring Boot 將為此測試創(chuàng)建的應(yīng)用程序上下文限制為給定的控制器 bean 和 Spring Web MVC 所需的一些框架 bean。我們可能需要的所有其他 bean 必須單獨(dú)包含或使用?@MockBean 模擬。

如果我們不使用?controllers 參數(shù),Spring Boot 將在應(yīng)用程序上下文中包含所有控制器。因此,我們需要包含或模擬掉任何控制器所依賴的所有 bean。這使得測試設(shè)置更加復(fù)雜,具有更多的依賴項(xiàng),但節(jié)省了運(yùn)行時(shí)間,因?yàn)樗锌刂破鳒y試都將重用相同的應(yīng)用程序上下文。

我傾向于將控制器測試限制在最窄的應(yīng)用程序上下文中,以使測試獨(dú)立于我在測試中甚至不需要的 bean,即使 Spring Boot 必須為每個(gè)單獨(dú)的測試創(chuàng)建一個(gè)新的應(yīng)用程序上下文。

讓我們來回顧一下每個(gè)職責(zé),看看我們?nèi)绾问褂?MockMvc 來驗(yàn)證每一個(gè)職責(zé),以便構(gòu)建我們力所能及的最好的集成測試。

1. 驗(yàn)證 HTTP 請(qǐng)求匹配

驗(yàn)證控制器是否偵聽某個(gè) HTTP 請(qǐng)求非常簡單。我們只需調(diào)用?MockMvc 的?perform() 方法并提供我們要測試的 URL:


mockMvc.perform(post("/forums/42/register")

? ? .contentType("application/json"))

? ? .andExpect(status().isOk());

除了驗(yàn)證控制器對(duì)特定 URL 的響應(yīng)之外,此測試還驗(yàn)證正確的 HTTP 方法(在我們的示例中為 POST)和正確的請(qǐng)求內(nèi)容類型。我們上面看到的控制器會(huì)拒絕任何具有不同 HTTP 方法或內(nèi)容類型的請(qǐng)求。

請(qǐng)注意,此測試仍然會(huì)失敗,因?yàn)槲覀兊目刂破餍枰恍┹斎雲(yún)?shù)。

更多匹配 HTTP 請(qǐng)求的選項(xiàng)可以在?MockHttpServletRequestBuilder?的 Javadoc 中找到。

2. 驗(yàn)證輸入序列化

為了驗(yàn)證輸入是否成功序列化為 Java 對(duì)象,我們必須在測試請(qǐng)求中提供它。輸入可以是請(qǐng)求正文的 JSON 內(nèi)容 ( @RequestBody)、URL 路徑中的變量 ( @PathVariable) 或 HTTP 請(qǐng)求參數(shù) (@RequestParam):


@Test

void whenValidInput_thenReturns200() throws Exception {

? UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net");

?

? ?mockMvc.perform(post("/forums/{forumId}/register", 42L)

? ? ? ? .contentType("application/json")

? ? ? ? .param("sendWelcomeMail", "true")

? ? ? ? .content(objectMapper.writeValueAsString(user)))

? ? ? ? .andExpect(status().isOk());

}

我們現(xiàn)在提供路徑變量 forumId、請(qǐng)求參數(shù)?sendWelcomeMail 和控制器期望的請(qǐng)求正文。請(qǐng)求正文是使用 Spring Boot 提供的?ObjectMapper 生成的,將?UserResource 對(duì)象序列化為 JSON 字符串。

如果測試結(jié)果為綠色,我們現(xiàn)在知道控制器的?register() 方法已將這些參數(shù)作為 Java 對(duì)象接收,并且它們已從 HTTP 請(qǐng)求中成功解析。

3. 驗(yàn)證輸入驗(yàn)證

假設(shè)?UserResource 使用? @NotNull 注釋來拒絕?null 值:

@Value

public class UserResource {

? ? @NotNull

? ? private final String name;

? ? @NotNull

? ? private final String email;


}

當(dāng)我們將?@Valid?注解添加到方法參數(shù)時(shí),Bean 驗(yàn)證會(huì)自動(dòng)觸發(fā),就像我們在控制器中使用?userResource 參數(shù)所做的那樣。因此,對(duì)于快樂路徑(即驗(yàn)證成功時(shí)),我們在上一節(jié)中創(chuàng)建的測試就足夠了。

如果我們想測試驗(yàn)證是否按預(yù)期失敗,我們需要添加一個(gè)測試用例,在該用例中我們將無效的?UserResource JSON 對(duì)象發(fā)送到控制器。然后我們期望控制器返回 HTTP 狀態(tài) 400(錯(cuò)誤請(qǐng)求):

@Test

void whenNullValue_thenReturns400() throws Exception {

? UserResource user = new UserResource(null, "zaphod@galaxy.net");

?

? mockMvc.perform(post("/forums/{forumId}/register", 42L)

? ? ? ...

? ? ? .content(objectMapper.writeValueAsString(user)))

? ? ? .andExpect(status().isBadRequest());

}

根據(jù)驗(yàn)證對(duì)應(yīng)用程序的重要性,我們可能會(huì)為每個(gè)可能的無效值添加這樣的測試用例。但是,這會(huì)很快增加很多測試用例,因此您應(yīng)該與您的團(tuán)隊(duì)討論您希望如何處理項(xiàng)目中的驗(yàn)證測試。

4. 驗(yàn)證業(yè)務(wù)邏輯調(diào)用

接下來,我們要驗(yàn)證業(yè)務(wù)邏輯是否按預(yù)期調(diào)用。在我們的例子中,業(yè)務(wù)邏輯由?RegisterUseCase 接口提供,并需要一個(gè)?User 對(duì)象和一個(gè)?boolean?值作為輸入:

interface RegisterUseCase {

? ? Long registerUser(User user, boolean sendWelcomeMail);

}

我們希望控制器將傳入的?UserResource 對(duì)象轉(zhuǎn)換為?User 并將此對(duì)象傳遞給?registerUser() 方法。

為了驗(yàn)證這一點(diǎn),我們可以要求?RegisterUseCase 模擬,它已使用?@MockBean 注解注入到應(yīng)用程序上下文中:

@Test

void whenValidInput_thenMapsToBusinessModel() throws Exception {

? UserResource user = new UserResource("Zaphod", "zaphod@galaxy.net");

? mockMvc.perform(...);

? ArgumentCaptor<User> userCaptor = ArgumentCaptor.forClass(User.class);

? verify(registerUseCase, times(1)).registerUser(userCaptor.capture(), eq(true));

? assertThat(userCaptor.getValue().getName()).isEqualTo("Zaphod");

? assertThat(userCaptor.getValue().getEmail()).isEqualTo("zaphod@galaxy.net");

}

在執(zhí)行了對(duì)控制器的調(diào)用之后,我們使用?ArgumentCaptor 來捕獲傳遞給?RegisterUseCase.registerUser() 的?User 對(duì)象并斷言它包含預(yù)期值。

調(diào)用?verify 檢查 registerUser()?是否被調(diào)用過一次。

請(qǐng)注意,如果我們對(duì)?User 對(duì)象進(jìn)行大量斷言,我們可以?創(chuàng)建自己的自定義 Mockito 斷言方法?以獲得更好的可讀性。

5. 驗(yàn)證輸出序列化

調(diào)用業(yè)務(wù)邏輯后,我們希望控制器將結(jié)果映射到 JSON 字符串并將其包含在 HTTP 響應(yīng)中。在我們的例子中,我們希望 HTTP 響應(yīng)正文包含一個(gè)有效的 JSON 格式的?UserResource 對(duì)象:

@Test

void whenValidInput_thenReturnsUserResource() throws Exception {

? MvcResult mvcResult = mockMvc.perform(...)

? ? ? ...

? ? ? .andReturn();

? UserResource expectedResponseBody = ...;

? String actualResponseBody = mvcResult.getResponse().getContentAsString();

?

? assertThat(actualResponseBody).isEqualToIgnoringWhitespace(

? ? ? ? ? ? ? objectMapper.writeValueAsString(expectedResponseBody));

}

要對(duì)響應(yīng)主體進(jìn)行斷言,我們需要使用?andReturn() 方法將 HTTP 交互的結(jié)果存儲(chǔ)在?MvcResult 類型的變量中。

然后我們可以從響應(yīng)正文中讀取 JSON 字符串,并使用?isEqualToIgnoringWhitespace() 將其與預(yù)期的字符串進(jìn)行比較。我們可以使用 Spring Boot 提供的?ObjectMapper 從 Java 對(duì)象構(gòu)建預(yù)期的 JSON 字符串。

請(qǐng)注意,我們可以通過使用自定義的?ResultMatcher 使其更具可讀性,稍后對(duì)此加以描述。

6. 驗(yàn)證異常處理

通常,如果發(fā)生異常,控制器應(yīng)該返回某個(gè) HTTP 狀態(tài)。400 --- 如果請(qǐng)求有問題,500 --- 如果出現(xiàn)異常,等等。

默認(rèn)情況下,Spring 會(huì)處理大多數(shù)這些情況。但是,如果我們有自定義異常處理,我們想測試它。假設(shè)我們想要返回一個(gè)結(jié)構(gòu)化的 JSON 錯(cuò)誤響應(yīng),其中包含請(qǐng)求中每個(gè)無效字段的字段名稱和錯(cuò)誤消息。我們會(huì)像這樣創(chuàng)建一個(gè) @ControllerAdvice:

@ControllerAdvice

class ControllerExceptionHandler {

? ? @ResponseStatus(HttpStatus.BAD_REQUEST)

? ? @ExceptionHandler(MethodArgumentNotValidException.class)

? ? @ResponseBody

? ? ErrorResult handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {

? ? ? ? ErrorResult errorResult = new ErrorResult();

? ? ? ? for (FieldError fieldError : e.getBindingResult().getFieldErrors()) {

? ? ? ? ? ? errorResult.getFieldErrors()

? ? ? ? ? ? ? ? ? ? .add(new FieldValidationError(fieldError.getField(), fieldError.getDefaultMessage()));

? ? ? ? }

? ? ? ? return errorResult;

? ? }

? ? @Getter

? ? @NoArgsConstructor

? ? static class ErrorResult {

? ? ? ? private final List<FieldValidationError> fieldErrors = new ArrayList<>();

? ? ? ? ErrorResult(String field, String message) {

? ? ? ? ? ? this.fieldErrors.add(new FieldValidationError(field, message));

? ? ? ? }

? ? }

? ? @Getter

? ? @AllArgsConstructor

? ? static class FieldValidationError {

? ? ? ? private String field;

? ? ? ? private String message;

? ? }

}

如果 bean 驗(yàn)證失敗,Spring 將拋出 MethodArgumentNotValidException。我們通過將 Spring 的?FieldError 對(duì)象映射到我們自己的?ErrorResult 數(shù)據(jù)結(jié)構(gòu)來處理這個(gè)異常。在這種情況下,異常處理程序會(huì)導(dǎo)致所有控制器返回 HTTP 狀態(tài) 400,并將?ErrorResult 對(duì)象作為 JSON 字符串放入響應(yīng)正文中。

為了驗(yàn)證這確實(shí)發(fā)生了,我們擴(kuò)展了我們之前對(duì)失敗驗(yàn)證的測試:


@Test

void whenNullValue_thenReturns400AndErrorResult() throws Exception {

? UserResource user = new UserResource(null, "zaphod@galaxy.net");

? MvcResult mvcResult = mockMvc.perform(...)

? ? ? ? ? .contentType("application/json")

? ? ? ? ? .param("sendWelcomeMail", "true")

? ? ? ? ? .content(objectMapper.writeValueAsString(user)))

? ? ? ? ? .andExpect(status().isBadRequest())

? ? ? ? ? .andReturn();

? ErrorResult expectedErrorResponse = new ErrorResult("name", "must not be null");

? String actualResponseBody =

? ? ? mvcResult.getResponse().getContentAsString();

? String expectedResponseBody =

? ? ? objectMapper.writeValueAsString(expectedErrorResponse);

? assertThat(actualResponseBody)

? ? ? .isEqualToIgnoringWhitespace(expectedResponseBody);

}

同樣,我們從響應(yīng)正文中讀取 JSON 字符串,并將其與預(yù)期的 JSON 字符串進(jìn)行比較。此外,我們檢查響應(yīng)狀態(tài)是否為 400。

這也可以以可讀性更強(qiáng)的方式實(shí)現(xiàn),我們接下來將要學(xué)習(xí)。

創(chuàng)建自定義 ResultMatcher

某些斷言很難寫,更重要的是,很難閱讀。特別是當(dāng)我們想要將來自 HTTP 響應(yīng)的 JSON 字符串與預(yù)期值進(jìn)行比較時(shí),它需要大量代碼,正如我們在最后兩個(gè)示例中看到的那樣。

幸運(yùn)的是,我們可以創(chuàng)建自定義的 ResultMatcher,我們可以在?MockMvc 的流暢 API 中使用它們。讓我們看看如何做到這一點(diǎn)。

匹配 JSON 輸出

使用以下代碼來驗(yàn)證 HTTP 響應(yīng)正文是否包含某個(gè) Java 對(duì)象的 JSON 表示不是很好嗎?

@Test

void whenValidInput_thenReturnsUserResource_withFluentApi() throws Exception {

? UserResource user = ...;

? UserResource expected = ...;

? mockMvc.perform(...)

? ? ? ...

? ? ? .andExpect(responseBody().containsObjectAsJson(expected, UserResource.class));

}

不再需要手動(dòng)比較 JSON 字符串。它的可讀性要好得多。事實(shí)上,代碼是如此的一目了然,這里我無需解釋。

為了能夠使用上面的代碼,我們創(chuàng)建了一個(gè)自定義的 ResultMatcher:

public class ResponseBodyMatchers {

? ? private ObjectMapper objectMapper = new ObjectMapper();

? ? public <T> ResultMatcher containsObjectAsJson(Object expectedObject, Class<T> targetClass) {

? ? ? ? return mvcResult -> {

? ? ? ? ? ? String json = mvcResult.getResponse().getContentAsString();

? ? ? ? ? ? T actualObject = objectMapper.readValue(json, targetClass);

? ? ? ? ? ? assertThat(actualObject).isEqualToComparingFieldByField(expectedObject);

? ? ? ? };

? ? }

? ? static ResponseBodyMatchers responseBody() {

? ? ? ? return new ResponseBodyMatchers();

? ? }

}

靜態(tài)方法?responseBody() 用作我們流暢的 API 的入口點(diǎn)。它返回實(shí)際的 ResultMatcher,它從 HTTP 響應(yīng)正文解析 JSON,并將其與傳入的預(yù)期對(duì)象逐個(gè)字段進(jìn)行比較。

匹配預(yù)期的驗(yàn)證錯(cuò)誤

我們甚至可以更進(jìn)一步簡化我們的異常處理測試。我們用了?4 行代碼來驗(yàn)證 JSON 響應(yīng)是否包含某個(gè)錯(cuò)誤消息。我們可以改為一行:

@Test

void whenNullValue_thenReturns400AndErrorResult_withFluentApi() throws Exception {

? UserResource user = new UserResource(null, "zaphod@galaxy.net");

? mockMvc.perform(...)

? ? ? ...

? ? ? .content(objectMapper.writeValueAsString(user)))

? ? ? .andExpect(status().isBadRequest())

? ? ? .andExpect(responseBody().containsError("name", "must not be null"));

}

同樣,代碼是自解釋的。

為了啟用這個(gè)流暢的 API,我們必須從上面添加方法?containsErrorMessageForField() 到我們的?ResponseBodyMatchers 類:

public class ResponseBodyMatchers {

? ? private ObjectMapper objectMapper = new ObjectMapper();

? ? public ResultMatcher containsError(String expectedFieldName, String expectedMessage) {

? ? ? ? return mvcResult -> {

? ? ? ? ? ? String json = mvcResult.getResponse().getContentAsString();

? ? ? ? ? ? ErrorResult errorResult = objectMapper.readValue(json, ErrorResult.class);

? ? ? ? ? ? List<FieldValidationError> fieldErrors = errorResult.getFieldErrors().stream()

? ? ? ? ? ? ? ? ? ? .filter(fieldError -> fieldError.getField().equals(expectedFieldName))

? ? ? ? ? ? ? ? ? ? .filter(fieldError -> fieldError.getMessage().equals(expectedMessage)).collect(Collectors.toList());

? ? ? ? ? ? assertThat(fieldErrors).hasSize(1).withFailMessage(

? ? ? ? ? ? ? ? ? ? "expecting exactly 1 error message" + "with field name '%s' and message '%s'", expectedFieldName,

? ? ? ? ? ? ? ? ? ? expectedMessage);

? ? ? ? };

? ? }

? ? static ResponseBodyMatchers responseBody() {

? ? ? ? return new ResponseBodyMatchers();

? ? }

}

所有丑陋的代碼都隱藏在這個(gè)輔助類中,我們可以在集成測試中愉快地編寫干凈的斷言。

結(jié)論

Web 控制器有很多職責(zé)。如果我們想用有意義的測試覆蓋一個(gè) web 控制器,僅僅檢查它是否返回正確的 HTTP 狀態(tài)是不夠的。

通過 @WebMvcTest,Spring Boot 提供了我們構(gòu)建 Web 控制器測試所需的一切,但為了使測試有意義,我們需要記住涵蓋所有職責(zé)。否則,我們可能會(huì)在運(yùn)行時(shí)遇到丑陋的驚喜。

本文中的示例代碼可在?github 上找到。


使用 Spring Boot 和 @WebMvcTest 測試 MVC Web Controller的評(píng)論 (共 條)

分享到微博請(qǐng)遵守國家法律
凭祥市| 天津市| 历史| 衡南县| 中西区| 融水| 武城县| 偏关县| 合川市| 密云县| 绩溪县| 朝阳县| 巴楚县| 河源市| 合川市| 鸡泽县| 灵山县| 永清县| 拉孜县| 沧州市| 重庆市| 阿坝县| 章丘市| 新疆| 深州市| 上高县| 庆城县| 遂溪县| 利津县| 兴海县| 台北市| 佛教| 婺源县| 鹤庆县| 获嘉县| 尼勒克县| 大理市| 榆林市| 禄丰县| 东平县| 灌南县|