版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
SpringBoot测试⽤例总结⼦详解前提:公司的SpringBoot项⽬,⼤概⼤部分功能完善之后,负责⼈让我去写测试⽤例,因为从来没有接触过,从⼀⽆所知到现在简单的编写测试⽤例,特此总结⼀下,以后公司万⼀继续让我写这个,看篇笔记就会了⼀:5分钟快速上⼿SpringBoot基础之MockMvc单元测试什么是Mock在⾯向对象的程序设计中,模拟对象(英语:mockobject)是以可控的⽅式模拟真实对象⾏为的假对象。在编程过程中,通常通过模拟⼀些输⼊数据,来验证程序是否达到预期结果。为什么使⽤Mock对象使⽤模拟对象,可以模拟复杂的、真实的对象⾏为。如果在单元测试中⽆法使⽤真实对象,可采⽤模拟对象进⾏替代。在以下情况可以采⽤模拟对象来替代真实对象:真实对象的⾏为是不确定的(例如,当前的时间或温度);真实对象很难搭建起来;真实对象的⾏为很难触发(例如,⽹络错误);真实对象速度很慢(例如,⼀个完整的数据库,在测试之前可能需要初始化);真实的对象是⽤户界⾯,或包括⽤户界⾯在内;真实的对象使⽤了回调机制;真实对象可能还不存在;真实对象可能包含不能⽤作测试(⽽不是为实际⼯作)的信息和⽅法。使⽤Mockito⼀般分三个步骤:1、模拟测试类所需的外部依赖;2、执⾏测试代码;3、判断执⾏结果是否达到预期;MockMvcMockMvc是由spring-test包提供,实现了对Http请求的模拟,能够直接使⽤⽹络的形式,转换到Controller的调⽤,使得测试速度快、不依赖⽹络环境。同时提供了⼀套验证的⼯具,结果的验证⼗分⽅便。接⼝MockMvcBuilder,提供⼀个唯⼀的build⽅法,⽤来构造MockMvc。主要有两个实现:StandaloneMockMvcBuilder和DefaultMockMvcBuilder,分别对应两种测试⽅式,即独⽴安装和集成Web环境测试(并不会集成真正的web环境,⽽是通过相应的MockAPI进⾏模拟测试,⽆须启动服务器)。MockMvcBuilders提供了对应的创建⽅法standaloneSetup⽅法和webAppContextSetup⽅法,在使⽤时直接调⽤即可。SpringBoot中使⽤第⼀步:jar包引⼊。创建SpringBoot项⽬中默认引⼊的spring-boot-starter-test间接引⼊了spring-test,因此⽆需再额外引⼊jar包。<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>第⼆步:创建HelloWorldController类,并提供hello⽅法作为待测试的业务接⼝。@RestControllerpublicclassHelloWorldController{@RequestMappingpublicStringhello(Stringname){return"Hello"+name+"!";}}第三步:编写测试类。实例化MockMvc有两种形式,⼀种是使⽤StandaloneMockMvcBuilder,另外⼀种是使⽤DefaultMockMvcBuilder。测试类及初始化MockMvc初始化://SpringBoot1.4版本之前⽤的是SpringJUnit4ClassRunner.class@RunWith(SpringRunner.class)//SpringBoot1.4版本之前⽤的是@SpringApplicationConfiguration(classes=Application.class)@SpringBootTest//测试环境使⽤,⽤来表⽰测试环境使⽤的ApplicationContext将是WebApplicationContext类型的@WebAppConfigurationpublicclassHelloWorldTest{privateMockMvcmockMvc;@AutowiredprivateWebApplicationContextwebApplicationContext;@Beforepublicvoidsetup(){//实例化⽅式⼀mockMvc=MockMvcBuilders.standaloneSetup(newHelloWorldController()).build();//实例化⽅式⼆//mockMvc=MockMvcBuilders.webAppContextSetup(webApplicationContext).build();}单元测试⽅法:@TestpublicvoidtestHello()throwsException{/**1mockMvc.perform、执⾏⼀个请求。*2MockMvcRequestBuilders.get("XXX")、构造⼀个请求。、*3ResultActions.param添加请求传值*4ResultActions.accept(MediaType.TEXT_HTML_VALUE))、设置返回类型、*5ResultActions.andExpect添加执⾏完成后的断⾔。*6ResultActions.andDo添加⼀个结果处理器,表⽰要对结果做点什么事情*⽐如此处使⽤MockMvcResultHandlers.print()输出整个响应结果信息。*7ResultActions.andReturn表⽰执⾏完成后返回相应的结果。、、*/mockMvc.perform(MockMvcRequestBuilders.get("/hello")//设置返回值类型为utf-8,否则默认为ISO-8859-1.accept(MediaType.APPLICATION_JSON_UTF8_VALUE).param("name","Tom")).andExpect(MockMvcResultMatchers.status().isOk()).andExpect(MockMvcResultMatchers.content().string("HelloTom!")).andDo(MockMvcResultHandlers.print());}测试结果打印:FlashMap:Attributes=nullMockHttpServletResponse:Status=200Errormessage=nullHeaders=[Content-Type:"application/json;charset=UTF-8",Content-Length:"10"]Contenttype=application/json;charset=UTF-8Body=HelloTom!ForwardedURL=nullRedirectedURL=nullCookies=[]2019-04-0221:34:27.954INFO6937---[utor'Thread-2]o.s.s.concurrent.ThreadPoolTaskExecutor:ShuttingdownExecutorService'applicationTaskExec整个过程如下:1.准备测试环境2.通过MockMvc执⾏请求3.添加验证断⾔4.添加结果处理器5.得到MvcResult进⾏⾃定义断⾔/进⾏下⼀步的异步请求6.卸载测试环境注意事项:如果使⽤DefaultMockMvcBuilder进⾏MockMvc实例化时需在SpringBoot启动类上添加组件扫描的package的指定,否则会出现404。如:@ComponentScan(basePackages="com.secbro2")相关APIRequestBuilder提供了⼀个⽅法buildRequest(ServletContextservletContext)⽤于构建MockHttpServletRequest;其主要有两个⼦类MockHttpServletRequestBuilder和MockMultipartHttpServletRequestBuilder(如⽂件上传使⽤)。MockMvcRequestBuilders提供get、post等多种⽅法⽤来实例化RequestBuilder。ResultActions,MockMvc.perform(RequestBuilderrequestBuilder)的返回值,提供三种能⼒:andExpect,添加断⾔判断结果是否达到预期;andDo,添加结果处理器,⽐如⽰例中的打印;andReturn,返回验证成功后的MvcResult,⽤于⾃定义验证/下⼀步的异步处理。⼆:⼀些常⽤的测试1.测试普通控制器mockMvc.perform(get("/user/{id}",1))//执⾏请求.andExpect(model().attributeExists("user"))//验证存储模型数据.andExpect(view().name("user/view"))//验证viewName.andExpect(forwardedUrl("/WEB-INF/jsp/user/view.jsp"))//验证视图渲染时forward到的jsp.andExpect(status().isOk())//验证状态码.andDo(print());//输出MvcResult到控制台2.得到MvcResult⾃定义验证MvcResultresult=mockMvc.perform(get("/user/{id}",1))//执⾏请求.andReturn();//返回MvcResultAssert.assertNotNull(result.getModelAndView().getModel().get("user"));//⾃定义断⾔3.验证请求参数绑定到模型数据及Flash属性mockMvc.perform(post("/user").param("name","zhang"))//执⾏传递参数的POST请求(也可以post("/user?name=zhang")).andExpect(handler().handlerType(UserController.class))//验证执⾏的控制器类型.andExpect(handler().methodName("create"))//验证执⾏的控制器⽅法名.andExpect(model().hasNoErrors())//验证页⾯没有错误.andExpect(flash().attributeExists("success"))//验证存在flash.andExpect(view().name("redirect:/user"));//验证视图属性4.⽂件上传byte[]bytes=newbyte[]{1,2};mockMvc.perform(fileUpload("/user/{id}/icon",1L).file("icon",bytes))//执⾏⽂件上传.andExpect(model().attribute("icon",bytes))//验证属性相等性.andExpect(view().name("success"));//验证视图5.JSON请求/响应验证StringrequestBody="{\"id\":1,\"name\":\"zhang\"}";mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON).content(requestBody).accept(MediaType.APPLICATION_JSON))//执⾏请求.andExpect(content().contentType(MediaType.APPLICATION_JSON))//验证响应contentType.andExpect(jsonPath("$.id").value(1));//使⽤Jsonpath验证JSON请参考/articles/JsonPath/StringerrorBody="{id:1,name:zhang}";MvcResultresult=mockMvc.perform(post("/user").contentType(MediaType.APPLICATION_JSON).content(errorBody).accept(MediaType.APPLICATION_JSON))//执⾏请求.andExpect(status().isBadRequest())//400错误请求.andReturn();Assert.assertTrue(HttpMessageNotReadableException.class.isAssignableFrom(result.getResolvedException().getClass()));//错误的请求内容体6.异步测试//CallableMvcResultresult=mockMvc.perform(get("/user/async1?id=1&name=zhang"))//执⾏请求.andExpect(request().asyncStarted()).andExpect(request().asyncResult(CoreMatchers.instanceOf(User.class)))//默认会等10秒超时.andReturn();mockMvc.perform(asyncDispatch(result)).andExpect(status().isOk()).andExpect(content().contentType(MediaType.APPLICATION_JSON)).andExpect(jsonPath("$.id").value(1));7.全局配置mockMvc=webAppContextSetup(wac).defaultRequest(get("/user/1").requestAttr("default",true))//默认请求如果其是Mergeable类型的,会⾃动合并的哦mockMvc.perform中的RequestBuilder.alwaysDo(print())//默认每次执⾏请求后都做的动作.alwaysExpect(request().attribute("default",true))//默认每次执⾏后进⾏验证的断⾔.build();mockMvc.perform(get("/user/1")).andExpect(model().attributeExists("user"));三:JUnit+Mockito单元测试之打桩when().thenReturn();什么是Mock测试Mock测试就是在测试过程中,对于某些不容易构造(如HttpServletRequest必须在Servlet容器中才能构造出来)或者不容易获取的对象(如JDBC中的ResultSet对象,JPA的CRUDRepository,需要执⾏数据库操作的),⽤⼀个虚拟的对象(Mock对象)来创建(覆盖⽅法返回)以便测试的测试⽅法。JUnit是⼀个单元测试框架。Mockito是⽤于数据模拟对象的框架。when().thenReturn();when(mockRepository.getMock(“x”)).thenReturn(“1024”);when(mockRepository.getMock("x")).thenReturn("1024");Stringmock=mockRepository.getMock("x");assertEquals("预期x=1024","1024",mock);when(xxxx).thenReturn(yyyy);是指定当执⾏了这个⽅法的时候,返回thenReturn的值,相当于是对模拟对象的配置过程,为某些条件给定⼀个预期的返回值。HttpServletRequestrequest=mock(HttpServletRequest.class);when(request.getParameter("csdn")).thenReturn("zhengkai");assertEquals("预期csdn=zhengkai",request.getParameter("csdn"),"zhengkai");Stub打桩Mockito中when().thenReturn();这种语法来定义对象⽅法和参数(输⼊),然后在thenReturn中指定结果(输出)。此过程称为Stub打桩。⼀旦这个⽅法被stub了,就会⼀直返回这个stub的值。Stub打桩需要注意的是:对于static和final⽅法,Mockito⽆法对其when(…).thenReturn(…)操作。当我们连续两次为同⼀个⽅法使⽤stub的时候,他只会只⽤最新的⼀次。迭代打桩打桩⽀持迭代风格的返回值,第⼀次调⽤i.next()将返回”Hello”,第⼆次的调⽤会返回”World”。//第⼀种⽅式,都是等价的when(i.next()).thenReturn("Hello").thenReturn("World");//第⼆种⽅式,都是等价的when(i.next()).thenReturn("Hello","World");//第三种⽅式,都是等价的when(i.next()).thenReturn("Hello");when(i.next()).thenReturn("World");void如何打桩没有返回值的void⽅法呢?不需要执⾏,只需要模拟跳过,写法上会有所不同,没返回值了调⽤thenReturn(xxx)肯定不⾏,取⽽代之的⽤doNothing().when().notify();doNothing().when(obj).notify();//或直接when(obj).notify();doNothing().when(obj).notify();//或直接when(obj).notify();抛出异常when(i.next()).thenThrow(newRuntimeException());doThrow(newRuntimeException()).when(i).remove();//void⽅法的//迭代风格doNothing().doThrow(newRuntimeException()).when(i).remove();//第⼀次调⽤remove⽅法什么都不做,第⼆次调⽤抛出RuntimeException异常。Any()anyString()匹配任何String参数,anyInt()匹配任何int参数,anySet()匹配任何Set,any()则意味着参数为任意值any(User.class)匹配任何User类。when(mockedList.get(anyInt())).thenReturn("element");System.out.println(mockedList.get(999));//elementelement此时打印是System.out.println(mockedList.get(777));//此时打印是四:⼩结⼀下相信⼤家⼼⾥都有个测试⽤例底⼦了,现在来介绍⼀下⾃⼰公司测试⽤例的过程(就是测controller,service等)。Step1:编写核⼼配置⽂件(test中的,仅供参考)#mybatis配置mybatis.type-aliases-package=com.shinefriends.sds.modelmybatis.mapper-locations=classpath:mybatis_mapper/*.xmlmapper.mappers=mon.Mappermapper.not-empty=falsemapper.identity=MYSQL#mysql驱动:h2spring.datasource.type=com.zaxxer.hikari.HikariDataSourcespring.datasource.driver-class-name=org.h2.Driverspring.datasource.platform=h2#全部数据初始化,包括数据库和内存数据spring.datasource.initialization-mode=alwaysspring.datasource.continue-on-error=true#h2内存数据库库名:testspring.datasource.url=jdbc:h2:mem:test;MODE=MySQL#初始化数据表spring.datasource.schema=classpath:init_table.sqlspring.datasource.data=classpath:init_data.sqlspring.datasource.username=spring.datasource.password=#打印SQL语句,Mapper所处的包.hawkingfoo.dao=debug#输出配置spring.h2.console.enabled=trueFILE_UPLOAD_ADDR=/Users/songjinyang/Desktop/app/security.private.key=/Users/songjinyang/Desktop/app/security/fast_price_private.keysecurity.public.key=/Users/songjinyang/Desktop/app/security/fast_price_public.keycsv.download.path=/Users/songjinyang/Desktop/app/Step2:初始化数据库中的表和数据(init_table.sql和init_date.sql)⼩问题:两个都是将建之前新建table的数据库语句复制过来,改⼀下,init_date.sql同理,但是我发现init_date.sql这⾥⾯的数据我⽤代码全都初始化过了,我觉得这⾥⾯什么都不⽤写,于是我将init_date.sql中所有初始化代码删除,但是报错了报错1:java.lang.IllegalStateException:FailedtoloadApplicationContext报错2:Causedby:java.lang.IllegalArgumentException:'script'mustnotbenullorempty解决:只要给init_date.sql随便放个语句就可以了,因为我的初始化在代码中实现了,于是我简单的放了⼀个查询,成功!⾮常nice,如下图Step3:编写Controller,Service,Mapper层代码(我这⾥直接上⼀部分代码了,因为很多解释上述已经说过了,这⾥只是最基本的测试简单控制器,helloWorld测试就没有介绍了【更简单,⽹上⼤部分都有】,其他的公司需要慢慢学再进⾏慢慢补充吧)Controller:packagecom.shinefriends.sds.controller;importcom.google.gson.Gson;importcom.google.gson.Gson;importcom.shinefriends.sds.model.MeetingRoom;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.test.context.junit4.SpringRunner;importorg.springframework.test.web.servlet.MockMvc;importorg.springframework.test.web.servlet.request.MockMvcRequestBuilders;importjavax.annotation.Resource;importjavax.ws.rs.core.MediaType;importstaticorg.springframework.test.web.servlet.result.MockMvcResultHandlers.print;importstaticorg.springframework.test.web.servlet.result.MockMvcResultMatchers.status;/***会议室Controller测试*@authoryuanzhisong*/@RunWith(SpringRunner.class)@SpringBootTest@AutoConfigureMockMvc@SuppressWarnings("checkstyle:magicnumber")publicclassMeetRoomControllerTest{/***MVCMock*/@ResourceprivateMockMvcmockMvc;/***Gson*/privateGsongson=newGson();/***删除接⼝测试*@throwsException*/@TestpublicvoiddeleteMeetingRoomTest()throwsException{//测试删除接⼝mockMvc.perform(MockMvcRequestBuilders.delete("/api/meetingRoom/1").contentType(MediaType.APPLICATION_JSON)).andExpect(status().isOk())//验证状态码.andDo(print());}/***新增接⼝测试*@throwsException*/@TestpublicvoidinsertMeetingRoomTest()throwsException{Stringcontent=gson.toJson(newMeetingRoom());//测试新增接⼝mockMvc.perform(MockMvcRequestBuilders.post("/api/meetingRoom/").contentType(MediaType.APPLICATION_JSON).content(content)).andExpect(status().isOk())//验证状态码.andDo(print());}/***查询接⼝测试*@throwsException*@throwsException*/@TestpublicvoidqueryMeetingRoomTest()throwsException{//测试新增接⼝mockMvc.perform(MockMvcRequestBuilders.get("/api/meetingRoom/").accept(MediaType.APPLICATION_JSON)).andExpect(status().isOk())//验证状态码.andDo(print());}/***修改接⼝测试*@throwsException*/@TestpublicvoidupdateMeetingRoomTest()throwsException{Stringcontent=gson.toJson(newMeetingRoom());//测试新增接⼝mockMvc.perform(MockMvcRequestBuilders.put("/api/meetingRoom/1").contentType(MediaType.APPLICATION_JSON).content(content)).andExpect(status().isOk())//验证状态码.andDo(print());}/***更新顺序测试*@throwsException*/@TestpublicvoidupdateMeetingRoomList()throwsException{Stringcontent=gson.toJson(newMeetingRoom());//测试新增接⼝mockMvc.perform(MockMvcRequestBuilders.put("/api/meetingRoom/").accept(MediaType.APPLICATION_JSON).content(content)).andExpect(status().isOk())//验证状态码.andDo(print());}}Service:packagecom.shinefriends.sds.service;importcom.shinefriends.sds.exception.CustomException;importcom.shinefriends.sds.model.MeetingRoom;importorg.junit.Assert;importorg.junit.Before;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.mockito.MockitoAnnotations;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.mock.mockito.MockBean;importorg.springframework.test.context.junit4.SpringRunner;importjava.util.ArrayList;importjava.util.Date;importjava.util.List;importstaticorg.mockito.Mockito.when;/***会议室Service测试*@authoryuanzhisong*/@RunWith(SpringRunner.class)@SpringBootTest@SpringBootTest@SuppressWarnings("checkstyle:magicnumber")publicclassMeetingRoomServiceTest{@MockBeanprivateMeetingRoomServicemeetingRoomService;privateList<MeetingRoom>meetingRooms;/***初始化数据*@returnList<MeetingRoom>*/privateList<MeetingRoom>initMeetingRoom(){List<MeetingRoom>list=newArrayList<>();MeetingRoommeetingRoom=newMeetingRoom();for(inti=0;i<3;i++){meetingRoom.setId(i);meetingRoom.setMeetingName(String.valueOf(i));meetingRoom.setIndexNo(i);meetingRoom.setCreateDate(newDate());meetingRoom.setCreateUserId(i);meetingRoom.setUpdateDate(newDate());meetingRoom.setUpdateUserId(i);list.add(meetingRoom);}returnlist;}/***测试数据初始化*/@BeforepublicvoidsetUp()throwsCustomException{MockitoAnnotations.initMocks(this);meetingRooms=initMeetingRoom();//查询接⼝测试when(meetingRoomService.selectMeetingRoom()).thenReturn(meetingRooms);//新增接⼝测试when(meetingRoomService.insertMeetingRoom(meetingRooms.get(0),1)).thenReturn("1");//删除接⼝测试when(meetingRoomService.deleteMeetingRoom(1)).thenReturn("1");//修改接⼝测试when(meetingRoomService.updateMeetingRoom(meetingRooms.get(0),1)).thenReturn("1");//更新顺序接⼝测试when(meetingRoomService.updateMeetingRoomList(meetingRooms,1)).thenReturn("1");}/***查询所有会议室*/@TestpublicvoidselectMeetingRoomTest(){List<MeetingRoom>list=meetingRoomService.selectMeetingRoom();Assert.assertEquals("查询会议室不正确",list.size(),meetingRooms.size());}/**/***删除会议室测试*/@TestpublicvoiddeleteMeetingRoomTest(){Strings=meetingRoomService.deleteMeetingRoom(1);Assert.assertEquals("删除会议室错误",String.valueOf(1),s);}/***修改会议室*/@TestpublicvoidupdateMeetingRoomTest()throwsCustomException{Strings=meetingRoomService.updateMeetingRoom(meetingRooms.get(0),1);Assert.assertEquals("修改会议室出现错误",String.valueOf(1),s);}/***新增会议室*/@TestpublicvoidinsertMeetingTest()throwsCustomException{Strings=meetingRoomService.insertMeetingRoom(meetingRooms.get(0),1);Assert.assertEquals("新增会议室出现错误",String.valueOf(1),s);}/***更新顺序接⼝测试*/@TestpublicvoidupdateMeetingRoomList(){Strings=meetingRoomService.updateMeetingRoomList(meetingRooms,1);Assert.assertEquals("更新顺序接⼝测试出现错误",String.valueOf(1),s);}}Mapper层:packagecom.shinefriends.sds.mapper;importcom.shinefriends.sds.model.MeetingRoom;importorg.junit.Assert;importorg.junit.Before;importorg.junit.Test;importorg.junit.runner.RunWith;importorg.mockito.MockitoAnnotations;importorg.springframework.boot.test.context.SpringBootTest;importorg.springframework.boot.test.mock.mockito.MockBean;importorg.springframework.test.context.junit4.SpringRunner;importjava.util.ArrayList;importjava.util.Date;importjava.util.List;importstaticorg.mockito.Mockito.when;@RunWith(SpringRunner.class)@SpringBootTest@SuppressWarnings("checkstyle:magicnumber")publicclassMeetingRoomMapperTest{@MockBeanprivateMeetingRoomMappermeetingRoomMapper;privateList<MeetingRoom>meetingRooms;/***初始化数据*@returnList<MeetingRoom>*/privateList<MeetingRoom>initMeetingR
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- Grid Coffee品牌介绍模版
- 二年级品德与社会下册 向人民英雄敬礼教案1 未来版
- 2024年高中生物 第2章 动物和人体生命活动的调节 第2节 通过激素的调节(Ⅱ)教案 新人教版必修3
- 2023七年级生物下册 第四单元 生物圈中的人 第11章 人体代谢废物的排出11.1 人体产生的代谢废物教案 (新版)北师大版
- 2024-2025学年高中化学 第1章 第3节 原子结构与元素性质 第1课时 电离能及其变化规律教案 鲁科版选修3
- 2024-2025学年高中语文 3 柳子厚墓志铭教案 语文版选修《唐宋八大家散文鉴赏》
- 告别母校 课件
- 亡羊补牢图片 课件
- 应急预案备案管理制度
- 第一单元(复习)-三年级语文上册单元复习(统编版)
- 体位引流课件
- 媒介伦理及规范案例教学演示文稿
- 混凝土有限公司安全管理工作责任追究制度
- 人教版三年级数学上册“倍的认识”作业设计
- 大数据可视化知到章节答案智慧树2023年浙江大学
- 学校教师招聘公告 中学招聘老师公告(四篇)
- 市政工程项目部管理制度及岗位职责
- 遥感技术及其应用(48张ppt)
- 第9章-庭院生态工程
- 《特殊儿童早期干预》教学大纲
- 初中化学鲁教版九年级下册化学与健康单元复习
评论
0/150
提交评论