范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文
国学影视

Java单元测试浅析(JUnitMockito)

  作者:京东物流 秦彪1. 什么是单元测试
  (1)单元测试环节:
  测试过程按照阶段划分分为:单元测试、集成测试、系统测试、验收测试等。相关含义如下:
  1) 单元测试: 针对计算机程序模块进行输出正确性检验工作。
  2) 集成测试: 在单元测试基础上,整合各个模块组成子系统,进行集成测试。
  3) 系统测试: 将整个交付所涉及的协作内容都纳入其中考虑,包含计算机硬件、软件、接口、操作等等一系列作为一个整体,检验是否满足软件或需求说明。
  4) 验收测试: 在交付或者发布之前对所做的工作进行测试检验。
  单元测试是阶段性测试的首要环节,也是白盒测试的一种,该内容的编写与实践可以前置在研发完成,研发在编写业务代码的时候就需要生成对应代码的单元测试。单元测试的发起人是程序设计者,受益人也是编写程序的人,所以对于程序员,非常有必要形成自我约束力,完成基本的单元测试用例编写。
  (2)单元测试特征:
  由上可知,单元测试其实是针对软件中最小的测试单元来进行验证的。这里的单元就是指相关的功能子集,比如一个方法、一个类等。值得注意的是作为最低级别的测试活动,单元测试验证的对象仅限于当前测试内容,与程序其它部分内容相隔离,总结起来单元测试有以下特征:
  1) 主要功能是证明编写的代码内容与期望输出一致。
  2) 最小最低级的测试内容,由程序员自身发起,保证程序基本组件正常。
  3) 单元测试尽量不要区分类与方法,主张以过程性的方法为测试单位,简单实用高效为目标。
  4) 不要偏离主题,专注于测试一小块的代码,保证基础功能。
  5) 剥离与外部接口、存储之间的依赖,使单元测试可控。
  6) 任何时间任何顺序执行单元测试都需要是成功的。2. 为什么要单元测试
  (1)单元测试意义:
  程序代码都是由基本单元不断组合成复杂的系统,底层基本单元都无法保证输入输出正确性,层级递增时,问题就会不断放大,直到整个系统崩溃无法使用。所以单元测试的意义就在于保证基本功能是正常可用且稳定的。而对于接口、数据源等原因造成的不稳定因素,是外在原因,不在单元测试考虑范围之内。
  (2)使用main方法进行测试:@PostMapping(value="/save") public Map save(@RequestBody Student stu) {     studentService.save(stu);     Map params = new HashMap<>();     params.put("code",200);     params.put("message","保存成功");     return params; }
  假如要对上面的Controller进行测试,可以编写如下的代码示例,使用main方法进行测试的时候,先启动整个工程应用,然后编写main方法如下进行访问,在单步调试代码。public static void main(String[] args) {         HttpHeaders headers = new HttpHeaders();         headers.setContentType(MediaType.APPLICATION_JSON);         String json = "{"name":"张三","className":"三年级一班","age":"20","sex":"男"}";         HttpEntity httpEntity = new HttpEntity<>(json, headers);         String url = "http://localhost:9092/student/save";         MainMethodTest test = new MainMethodTest();         ResponseEntity responseEntity = test.getRestTemplate().postForEntity(url, httpEntity, Map.class);         System.out.println(responseEntity.getBody());     }
  (3)使用main方法进行测试的缺点:
  1) 通过编写大量的main方法针对每个内容做打印输出到控制台枯燥繁琐,不具备优雅性。
  2) 测试方法不能一起运行,结果需要程序员自己判断正确性。
  3) 统一且重复性工作应该交给工具去完成。3. 单元测试框架-JUnit3.1 JUnit简介
  JUnit官网:https://junit.org/。JUnit是一个用于编写可重复测试的简单框架。它是用于单元测试框架的xUnit体系结构的一个实例。
  JUnit的特点:
  (1) 针对于Java语言特定设计的单元测试框架,使用非常广泛。
  (2) 特定领域的标准测试框架。
  (3) 能够在多种IDE开发平台使用,包含Idea、Eclipse中进行集成。
  (4) 能够方便由Maven引入使用。
  (5) 可以方便的编写单元测试代码,查看测试结果等。
  JUnit的重要概念:
  名称
  功能作用
  Assert
  断言方法集合
  TestCase
  表示一个测试案例
  TestSuite
  包含一组TestCase,构成一组测试
  TestResult
  收集测试结果
  JUnit的一些注意事项及规范:
  (1) 测试方法必须使用@Test 修饰
  (2) 测试方法必须使用public void 进行修饰,不能带参数
  (3) 测试代码的包应该和被测试代码包结构保持一致
  (4) 测试单元中的每个方法必须可以独立测试,方法间不能有任何依赖
  (5) 测试类一般使用 Test作为类名的后缀
  (6) 测试方法使一般用test 作为方法名的前缀
  JUnit失败结果说明:
  (1) Failure:测试结果和预期结果不一致导致,表示测试不通过
  (2) error:由异常代码引起,它可以产生于测试代码本身的错误,也可以是被测代码的Bug3.2 JUnit内容
  (1) 断言的API
  断言方法
  断言描述
  assertNull(String message, Object object)
  检查对象是否为空,不为空报错
  assertNotNull(String message, Object object)
  检查对象是否不为空,为空报错
  assertEquals(String message, Object expected, Object actual)
  检查对象值是否相等,不相等报错
  assertTrue(String message, boolean condition)
  检查条件是否为真,不为真报错
  assertFalse(String message, boolean condition)
  检查条件是否为假,为真报错
  assertSame(String message, Object expected, Object actual)
  检查对象引用是否相等,不相等报错
  assertNotSame(String message, Object unexpected, Object actual)
  检查对象引用是否不等,相等报错
  assertArrayEquals(String message, Object[] expecteds, Object[] actuals)
  检查数组值是否相等,遍历比较,不相等报错
  assertArrayEquals(String message, Object[] expecteds, Object[] actuals)
  检查数组值是否相等,遍历比较,不相等报错
  assertThat(String reason, T actual, Matcher<? super T> matcher)
  检查对象是否满足给定规则,不满足报错
  (2) JUnit常用注解:
  1) @Test: 定义一个测试方法 @Test(excepted=xx.class): xx.class 表示异常类,表示测试的方法抛出此异常时,认为是正常的测试通过的 @Test(timeout = 毫秒数) :测试方法执行时间是否符合预期。
  2) @BeforeClass: 在所有的方法执行前被执行,static 方法全局只会执行一次,而且第一个运行。
  3) @AfterClass:在所有的方法执行之后进行执行,static 方法全局只会执行一次,最后一个运行。
  4) @Before:在每一个测试方法被运行前执行一次。
  5) @After:在每一个测试方法运行后被执行一次。
  6) @Ignore:所修饰的测试方法会被测试运行器忽略。
  7) @RunWith:可以更改测试执行器使用junit测试执行器。3.3 JUnit使用3.3.1 Controller层单元测试
  (1) Springboot中使用maven引入Junit非常简单, 使用如下依赖即可引入:     org.springframework.boot     spring-boot-starter-test     test 
  (2) 上面使用main方法案例可以使用如下的Junit代码完成:@RunWith(SpringRunner.class) @SpringBootTest(classes = MainApplication.class) public class StudentControllerTest { 	 	// 注入Spring容器     @Autowired     private WebApplicationContext applicationContext;     // 模拟Http请求     private MockMvc mockMvc;      @Before     public void setupMockMvc(){     	// 初始化MockMvc对象         mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();     }          /**      * 新增学生测试用例      * @throws Exception      */     @Test     public void addStudent() throws Exception{         String json="{"name":"张三","className":"三年级一班","age":"20","sex":"男"}";         mockMvc.perform(MockMvcRequestBuilders.post("/student/save")    //构造一个post请求                     // 发送端和接收端数据格式                     .contentType(MediaType.APPLICATION_JSON_UTF8)                     .accept(MediaType.APPLICATION_JSON_UTF8)                     .content(json.getBytes())             )            // 断言校验返回的code编码            .andExpect(MockMvcResultMatchers.status().isOk())            // 添加处理器打印返回结果            .andDo(MockMvcResultHandlers.print());     } }
  只需要在类或者指定方法上右键执行即可,可以直接充当postman工作访问指定url,且不需要写请求代码,这些都由工具自动完成。
  (3)案例中相关组件介绍
  本案例中构造mockMVC对象时,也可以使用如下方式:@Autowired private StudentController studentController; @Before public void setupMockMvc(){    // 初始化MockMvc对象    mockMvc = MockMvcBuilders.standaloneSetup(studentController).build(); }
  其中MockMVC是Spring测试框架提供的用于REST请求的工具,是对Http请求的模拟,无需启动整个模块就可以对Controller层进行调用,速度快且不依赖网络环境。
  使用MockMVC的基本步骤如下:
  1) mockMvc.perform执行请求
  2) MockMvcRequestBuilders.post或get构造请求
  3) MockHttpServletRequestBuilder.param或content添加请求参数
  4) MockMvcRequestBuilders.contentType添加请求类型
  5) MockMvcRequestBuilders.accept添加响应类型
  6) ResultActions.andExpect添加结果断言
  7) ResultActions.andDo添加返回结果后置处理
  8) ResultActions.andReturn执行完成后返回相应结果3.3.2 Service层单元测试
  可以编写如下代码对Service层查询方法进行单测:@RunWith(SpringRunner.class) @SpringBootTest public class StudentServiceTest {  	@Autowired     private StudentService studentService;      @Test     public void getOne() throws Exception {     	 Student stu = studentService.selectByKey(5);          Assert.assertThat(stu.getName(),CoreMatchers.is("张三"));     } }
  执行结果:
  3.3.3 Dao层单元测试
  可以编写如下代码对Dao层保存方法进行单测:@RunWith(SpringRunner.class) @SpringBootTest public class StudentDaoTest {  	@Autowired     private StudentMapper studentMapper;      @Test     @Rollback(value = true)     @Transactional     public void insertOne() throws Exception {     	 Student student = new Student();     	 student.setName("李四");     	 student.setMajor("计算机学院");     	 student.setAge(25);     	 student.setSex("男");     	 int count = studentMapper.insert(student);     	 Assert.assertEquals(1, count);     } }
  其中@Rollback(value = true) 可以执行单元测试之后回滚所新增的数据,保持数据库不产生脏数据。3.3.4 异常测试
  (1) 在service层定义一个异常情况:public void computeScore() {    int a = 10, b = 0; }
  (2) 在service的测试类中定义单元测试方法:@Test(expected = ArithmeticException.class)     public void computeScoreTest() {         studentService.computeScore();     }
  (3) 执行单元测试也会通过,原因是@Test注解中的定义了异常
  3.3.5 测试套件测多个类
  (1) 新建一个空的单元测试类
  (2) 利用注解@RunWith(Suite.class)和@SuiteClasses标明要一起单元测试的类@RunWith(Suite.class) @Suite.SuiteClasses({ StudentServiceTest.class, StudentDaoTest.class}) public class AllTest { }
  运行结果:
  3.3.6 idea中查看单元测试覆盖率
  (1) 单测覆盖率
  测试覆盖率是衡量测试过程工作本身的有效性,提升测试效率和减少程序bug,提升产品可靠性与稳定性的指标。
  统计单元测试覆盖率的意义:
  1) 可以洞察整个代码中的基础组件功能的所有盲点,发现相关问题。
  2) 提高代码质量,通常覆盖率低表示代码质量也不会太高,因为单测不通过本来就映射出考虑到各种情况不够充分。
  3) 从覆盖率的达标上可以提高代码的设计能力。
  (2) 在idea中查看单元测试覆盖率很简单,只需按照图中示例的图标运行,或者在单元测试方法或类上右键Run "xxx" with Coverage即可。执行结果是一个表格,列出了类、方法、行数、分支覆盖情况。
  (3) 在代码中会标识出覆盖情况,绿色的是已覆盖的,红色的是未覆盖的。
  (4) 如果想要导出单元测试的覆盖率结果,可以使用如下图所示的方式,勾选 Open generated HTML in browser
  导出结果:
  3.3.7 JUnit插件自动生成单测代码
  (1) 安装插件,重启idea生效
  (2) 配置插件
  (3) 使用插件
  在需要生成单测代码的类上右键generate...,如下图所示。
  生成结果:
  4. 单元测试工具-Mockito4.1 Mockito简介
  在单元测试过程中主张不要依赖特定的接口与数据来源,此时就涉及到对相关数据的模拟,比如Http和JDBC的返回结果等,可以使用虚拟对象即Mock对象进行模拟,使得单元测试不在耦合。
  Mock过程的使用前提:
  (1) 实际对象时很难被构造出来的
  (2) 实际对象的特定行为很难被触发
  (3) 实际对象可能当前还不存在,比如依赖的接口还没有开发完成等等。
  Mockito官网:https://site.mockito.org 。Mockito和JUnit一样是专门针对Java语言的mock数据框架,它与同类的EasyMock和jMock功能非常相似,但是该工具更加简单易用。
  Mockito的特点:
  (1) 可以模拟类不仅仅是接口
  (2) 通过注解方式简单易懂
  (3) 支持顺序验证
  (4) 具备参数匹配器4.2 Mockito使用
  maven引入spring-boot-starter-test会自动将mockito引入到工程中。4.2.1 使用案例
  (1) 在之前的代码中在定义一个BookService接口, 含义是借书接口,暂且不做实现public interface BookService {     Book orderBook(String name); }
  (2) 在之前的StudentService类中新增一个orderBook方法,含义是学生预定书籍方法,其中实现内容调用上述的BookService的orderBook方法。public Book orderBook(String name) {    return bookService.orderBook(name); }
  (3) 编写单元测试方法,测试StudentService的orderBook方法@Test public void orderBookTest() {     Book expectBook = new Book(1L, "钢铁是怎样炼成的", "书架A01");     Mockito.when(bookService.orderBook(any(String.class))).thenReturn(expectBook);     Book book = studentService.orderBook("");     System.out.println(book);     Assert.assertTrue("预定书籍不符", expectBook.equals(book)); }
  (4) 执行结果:
  (5) 结果解析
  上述内容并没有实现BookService接口的orderBook(String name)方法。但是使用mockito进行模拟数据之后,却通过了单元测试,原因就在于Mockito替换了本来要在StudentService的orderBook方法中获取的对象,此处就模拟了该对象很难获取或当前无法获取到,用模拟数据进行替代。
  4.2.2 相关语法
  常用API:
  上述案例中用到了mockito的when、any、theWhen等语法。接下来介绍下都有哪些常用的API:
  1) mock:模拟一个需要的对象
  2) when:一般配合thenXXX一起使用,表示当执行什么操作之后怎样。
  3) any: 返回一个特定对象的缺省值,上例中标识可以填写任何String类型的数据。
  4) theReturn: 在执行特定操作后返回指定结果。
  5) spy:创造一个监控对象。
  6) verify:验证特定的行为。
  7) doReturn:返回结果。
  8) doThrow:抛出特定异常。
  9) doAnswer:做一个自定义响应。
  10) times:操作执行次数。
  11) atLeastOnce:操作至少要执行一次。
  12) atLeast:操作至少执行指定的次数。
  13) atMost:操作至多执行指定的次数。
  14) atMostOnce:操作至多执行一次。
  15) doNothing:不做任何的处理。
  16) doReturn:返回一个结果。
  17) doThrow:抛出一个指定异常。
  18) doAnswer:指定一个特定操作。
  19) doCallRealMethod:用于监控对象返回一个真实结果。4.2.3 使用要点
  (1) 打桩
  Mockito中有Stub,所谓存根或者叫打桩的概念,上面案例中的Mockito.when(bookService.orderBook(any(String.class))).thenReturn(expectBook);就是打桩的含义,先定义好如果按照既定的方式调用了什么,结果就输出什么。然后在使用Book book = studentService.orderBook(""); 即按照指定存根输出指定结果。    @Test     public void verifyTest() {         List mockedList = mock(List.class);          mockedList.add("one");          verify(mockedList).add("one");			// 验证通过,因为前面定义了这个桩         verify(mockedList).add("two");			// 验证失败,因为前面没有定义了这个桩     }
  (2) 参数匹配
  上例StudentService的orderBook方法中的any(String.class) 即为参数匹配器,可以匹配任何此处定义的String类型的数据。
  (3) 次数验证    @Test     public void timesTest() {         List mockedList = mock(List.class);         when(mockedList.get(anyInt())).thenReturn(1000);         System.out.println(mockedList.get(1));         System.out.println(mockedList.get(1));         System.out.println(mockedList.get(1));         System.out.println(mockedList.get(2));          // 验证通过:get(1)被调用3次         verify(mockedList, times(3)).get(1);         // 验证通过:get(1)至少被调用1次         verify(mockedList, atLeastOnce()).get(1);         // 验证通过:get(1)至少被调用3次         verify(mockedList, atLeast(3)).get(1);     }
  (4) 顺序验证    @Test     public void orderBookTest1() { 	    String json = "{"id":12,"location":"书架A12","name":"三国演义"}"; 	    String json1 = "{"id":21,"location":"书架A21","name":"水浒传"}";         String json2 = "{"id":22,"location":"书架A22","name":"红楼梦"}";         String json3 = "{"id":23,"location":"书架A23","name":"西游记"}";         when(bookService.orderBook("")).thenReturn(JSON.parseObject(json, Book.class));         Book book = bookService.orderBook("");         Assert.assertTrue("预定书籍有误", "三国演义".equals(book.getName()));          when(bookService.orderBook("")).thenReturn(JSON.parseObject(json1, Book.class)).                 thenReturn(JSON.parseObject(json2, Book.class)).                 thenReturn(JSON.parseObject(json3, Book.class));         Book book1 = bookService.orderBook("");         Book book2 = bookService.orderBook("");         Book book3 = bookService.orderBook("");         Book book4 = bookService.orderBook("");         Book book5 = bookService.orderBook("");         // 全部验证通过,按顺序最后打桩打了3次,大于3次按照最后对象输出         Assert.assertTrue("预定书籍有误", "水浒传".equals(book1.getName()));         Assert.assertTrue("预定书籍有误", "红楼梦".equals(book2.getName()));         Assert.assertTrue("预定书籍有误", "西游记".equals(book3.getName()));         Assert.assertTrue("预定书籍有误", "西游记".equals(book4.getName()));         Assert.assertTrue("预定书籍有误", "西游记".equals(book5.getName())); }
  (5) 异常验证    @Test(expected = RuntimeException.class)     public void exceptionTest() {         List mockedList = mock(List.class);         doThrow(new RuntimeException()).when(mockedList).add(1);         // 验证通过         mockedList.add(1);     }

跨省驻防丨红色教育党日活动铁心跟党志不渝,蓝焰铁胆筑忠魂怒发冲冠,凭栏处,潇潇雨歇。抬望眼,仰天长啸,壮怀激烈。三十功名尘与土,八千里路云和月。莫等闲,白了少年头,空悲切!靖康耻,犹未雪。臣子恨,何时灭!驾长车,踏破贺兰山缺。壮志饥餐胡蒙古帝国真的很强吗?为何苟延残喘的南宋,还能抵抗半个世纪?文章字数4643字,预计阅读时长11分钟。如果您喜欢这篇文章,请点击右上方的关注。感谢您的支持和鼓励,希望能给您带来舒适的阅读体验。1279年,随着宋军在崖山之战中战败,南宋政权彻丰县和秦台是什么关系?随着经济的快速发展,丰县新建了秦台路和秦台小学。丰县和秦台是什么关系?司马迁史记记载秦始皇常曰东南有天子气于是因东游以厌之。秦始皇害怕江山不牢,被外人所篡夺,所以三次东巡,想灭掉天20世纪30年代末,为什么世界上的绥靖政策会破产?20世纪30年代后期,显然是人类历史上的多事之秋。中日两国出于各自的考虑,虽未宣战却早已在中国打得天翻地覆。世界各地也不太平,在欧洲,纳粹德国比日本更为疯狂,美英法退一步,德国进一王胜战贪腐事件,我国司法实践中有被判死刑先例,罪有应得十年树木,百年树人。浙江余姚市教育局长在中国行政级别中仅仅是一名正科级干部。其腐败影响到整个教育系统,中国教育到了急需花大力气整治的时候。王胜战会不会吃花生米?到目前为止,其还只是不起眼的葡萄牙,为何成为了新航路开辟的发起者?1394年3月4日,恩里克王子出生在波尔图,相对于欧洲其他同时代的王公贵族,他并不是最显眼的一个,但他未来的成就却是万人瞩目的。被称为航海者的恩里克王子,用着对于航海事业的满腔热忱情人节2月14日的情人节,在西方国家对于年轻人来说是个隆重且很有意义的节日。相信很多人也都有过过情人节。那么大家知道情人节的来历和意义吗?情人节的起源情人节的起源也是非常早的,在网上搜索阅读嘉庆武冈州志之笔记第30章康熙时武冈行政划分为9乡31里一,导读。清嘉庆版武冈州志卷三舆地图坊乡图考记载武冈原编二十八里,加猺里二为三十里,康熙三十五年紫阳增编一里共三十一里,分东西南北四路。东路四乡南路一乡西路二乡北路二乡可见,康熙三保加利亚的领土变迁千年西美昂帝国之梦终成泡影头条号天启元贞文留在伏尔加河的保加尔人被基辅罗斯征服并同化保加利亚人是南部斯拉夫人的一支,但是他们的起源并不是斯拉夫人,而是讲突厥语的保加尔人,亦称古保加利亚人。据保加利亚历史界主如果刘禅公开斥责诸葛亮会怎样?历史上的刘禅还真的这么做过别说,历史上的刘禅还真的这么做过意思是街亭失利后诸葛亮自贬3级,不到1年阿斗就下旨了,他说街亭是马谡的问题,一年来你屡建大功,如今社稷有难,再不出来为国效力就是不顾单位,在闹个人情唐玄宗为何要给爱妃堕胎?又为何抛弃嫡子,选一个娼女之子为太子在后人眼中,唐肃宗李亨恐怕是最不幸的皇帝之一。当太子期间,他接连被李林甫和杨国忠蹂躏,连自己的老婆都保不住。当了皇帝之后,又被老婆和宦官欺负,最后竟然被惊吓而死。但李亨其实又是一位
地球文明一直在循环往复?15万年前的工业痕迹,在青海出现综述人类在地球文明中起到举足轻重的作用,历史赋予我们以力量,但切莫得意忘形了,我们当真了解地球文明的全貌吗?位于青海的一座小小的山峰,就足以让足智多谋的我们犯了难,真是咄咄怪事!究陕蒙边界纠纷陕蒙边界纠纷作者郭峰来自老郭说史自古以来,中原王朝与北方草原王庭就没有明显边界。说北控大漠西抵流沙,是大概范围,不像现在,精确到寸,一寸河山一寸血,十万青年十万军。中原王朝强盛了,劫富济贫真男人!英超狼队VS纽卡斯尔联昨天文章里分享的日料出现了一点小问题,不过视频分享的利物浦思路没有问题,上半场便结束了比赛,今天继续给大家带来一场英超的精彩对决。狼队作为由中国复星集团全资控股的英超球队,相信老球滕哈格卡塞米罗见识了英超的比赛风格,C罗不一定会离开英超第4轮,曼联客场10击败南安普顿。在赛后的新闻发布会上,曼联主帅滕哈格谈到了卡塞米罗的首秀表现以及C罗的情况。滕哈格表示,很高兴看到卡塞米罗完成首秀,他见识到了英超是什么样的比2。2分高了,油腻到呕!救命,别再拍了中秋假期刚过,相信大家一定还沉浸在放假的喜悦中。趁着喜庆,乌鸦就跟大家聊点热闹的。一部豆瓣开分只有2。4的烂剧,凭借油腻和辣眼睛,狂炸微博热搜好几次的东八区的先生们太可怕了。豆瓣2越来越多的酒店设置钟点房,什么人喜欢开钟点房?4小时能干嘛呢经济在高速的发展,人们选择旅游,释放身心,毕竟压力越来越大,随之而来的困扰就会越来越多。如果不能找到平衡点,一旦崩溃的话,将会十分的难受。(此处已添加小程序,请到今日头条客户端查看梦回九寨九寨沟之行游记神奇的九寨,人间的天堂。也曾魂牵梦绕,也曾朝思暮想,只为想念梦里九寨。虽不能至,心向往之。终得机会,说走就走,奔赴山海,追逐热爱。远离喧嚣,寻一处静谧。去旅行,投入自由无羁的大自然中国古堡风云明末晋东南的历史之旅寻晋之美,总是绕不开晋东南。地上文物看山西,这里古建筑数量之多质量之高类型齐全保存原始,独步全国,尤以晋东南地区的古建筑分布集中,惊艳出彩寺观彩塑壁画,过往的晋东南旅行路线带我们领一定要去东灵山看看,那个寄托美好愿望的北京屋脊每次出游都不想离市界太近,盛夏已过,还没有看到灵山的草甸,终于大着胆子出发啦!导航江水河村江水河村940出发从城区到灵山是120公里,开学前的最后一个周末,从出家门,加油,上路都需甘肃人游甘肃乡村旅游乐享金秋推广发布活动举行甘肃人游甘肃乡村旅游乐享金秋推广发布活动举行每日甘肃网兰州讯(新甘肃甘肃日报记者施秀萍)8月26日,由省文旅厅主办的甘肃人游甘肃2022甘肃乡村旅游乐享金秋推广发布活动以线上线下结火药味十足!华为Mate50硬刚iPhone14,经销商都不想囤货本文来源时代财经作者王婷资料图图源pexels今年9月,华为Mate50系列与iPhone14系列将几乎同一时间抢占高端市场,这意味着苹果公司和华为迎来正面对战。8月25日,苹果公