单元测试->集成测试->系统测试->验收测试
单元测试
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。一个单元可能是单个程序、类、对象、方法等。
测试阶段:编码后或编码前(测试驱动开发TDD)
测试对象:最小模块(Java-类)
测试内容:模块接口测试、局部数据结构、路径测试、错误处理测试、边界测试
模块接口测试:对通过被测模块的数据流进行测试。包括参数表、调用子模块的参数、全程数据、文件输入/输出操作等
局部数据结构:数据类型说明、初始化、缺省值、上溢下溢的地址等
路径测试:对每一条独立执行路径测试,保证每条语句至少执行一次
错误处理测试:检查模块的错误处理功能是否包含有错误或缺陷。是否拒绝不合理的输入、输出的报错信息是否难以理解、是否对错误定位有误、 是否出错原因有误、报错代码处理执行之前,是否已引起系统报错
测试人员:开发人员
测试方法:白盒测试(基本路径测试、语句覆盖、条件覆盖、判定覆盖、判定条件覆盖、条件组合覆盖、路径覆盖等)
单测目的:
提高代码质量。之前在做测试时候,在项目启动后,通过Swagger或Postman访问接口进行测试,修改代码之后每次都要重新启动,耗时较长
方便重构。重构之后如果不确定代码的正确性,可以走一遍单元测试做参考
开发阶段排除Bug的例子:调度编辑功能,在ApplicationService层实现逻辑是参数校验、调度删除、调度新增;其中,在新增时候也有做校验,且校验失败时候直接抛出异常;导致我在新增正常调度成功后,编辑错误数据,删除成功了,但重新添加时候失败;详细测试数据见Spring Boot项目单元测试及集成测试的应用单元的2.2 Application-Service层 示例
集成测试
集成测试是在单元测试的基础上,将所有已通过单元测试的模块按照概要设计的要求组装为子系统或系统,并进行测试的过程。目标是把通过了单元测试的模块拿来,构造一个在设计中所描述的程序结构,应当避免一次性的集成(除非软件规模很小),而采用增量集成。
测试阶段:单元测试通过后
测试对象:模块间的接口
测试内容:模块之间数据传输、模块之间功能冲突、模块组装功能正确性、全局数据结构、单模块缺陷对系统的影响
测试人员:开发人员
测试方法:白盒测试+黑盒测试
系统测试
测试阶段:集成测试通过后
测试对象:整个系统
测试内容:功能、界面、可靠性、易用性、性能、兼容性、安全性等
测试人员:测试人员
测试方法:黑盒测试
验收测试
验收测试是部署软件之前的最后一个测试操作。它是技术测试的最后一个阶段,也称为交付测试。验收测试的目的是确保软件准备就绪,按照项目合同、任务书、双方约定的验收依据文档,向软件购买都展示该软件系统满足原始需求。
测试阶段:系统测试通过后
测试对象:整个系统
测试内容:同系统测试
测试人员:需求方或最终用户
测试方法:黑盒测试
测试阶段对比图如下:
集成测试 | 单元测试 | 系统测试 | 验收测试 | |
---|---|---|---|---|
测试阶段 | 单元测试通过后 | 编码后或编码前 | 集成测试通过后 | 系统测试通过后 |
测试对象 | 模块间的接口 | 最小模块 | 整个系统 | 整个系统 |
测试内容 | 模块之间数据传输、模块之间功能冲突、 模块组装功能正确性、全局数据结构、单模块缺陷对系统的影响 | 模块接口测试、局部数据结构、路径测试、错误处理测试、边界测试 | 功能、界面、可靠性、易用性、性能、兼容性、安全性等 | 同系统测试 |
测试人员 | 开发人员 | 开发人员 | 测试人员 | 需求方或最终用户 |
测试方法 | 白盒测试+黑盒测试 | 白盒测试 | 黑盒测试 | 黑盒测试 |
Junit
手册:JUnit API
常用注解:
@BeforeClass: 在所有测试方法执行前执行一次,一般在其中写上整体初始化的代码
public class Example { @BeforeClass public static void onlyOnce() { ... } @Test public void one() { ... } @Test public void two() { ... } }
@Before:在每个方法测试前执行,一般用来初始化方法
public class Example { List empty; @Before public void initialize() { empty= new ArrayList(); } @Test public void size() { ... } @Test public void remove() { ... } }
@AfterClass:在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码
public class Example { private static DatabaseConnection database; @BeforeClass public static void login() { database= ...; } @Test public void something() { ... } @Test public void somethingElse() { ... } @AfterClass public static void logout() { database.logout(); } }
@After:在每个测试方法执行后,在方法执行完成后要做的事情
public class Example { File output; @Before public void createOutputFile() { output= new File(...); } @Test public void something() { ... } @After public void deleteOutputFile() { output.delete(); } }
@Test:编写一般测试用例。支持两个可选参数,用于异常测试和限制测试执行时间
public class Example { @Test public void method() { org.junit.Assert.assertTrue( new ArrayList().isEmpty() ); } }
@Test(expected = Exception.class) :测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败
@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() { new ArrayList
@Test(timeout = m):测试方法花费的时间超过指定的时间m毫秒后,将测试失败
@Test(timeout=100) public void infinity() { while(true); }
@Ignore("not ready yet"):执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类
@Ignore public class IgnoreMe { @Test public void test1() { ... } @Test public void test2() { ... } }
Mockito
Mock测试即在测试过程中,对于一些不容易构造的,与本次单元测试关系不大但又有上下文依赖关系的对象,用一个虚拟的对象(Mock对象)来模拟,以便单元测试能够进行。Mockito是Java单元测试的mock开源框架。
手册:mockito-core 3.11.2 javadoc (org.mockito)
常用方法:
mock对象:
@BeforeEach void init() { scheduler = mock(Scheduler.class); scheduleService = new ScheduleServiceImpl(scheduler); }
参数匹配:
anyInt()、anyLong()、anyFloat()、anyDouble(),匹配任意xx类型数据
@Test void testMoc(){ ArrayList test = mock(ArrayList.class); when(test.get(anyInt())).thenReturn("mock argumentMatcher"); System.out.println(test.get(0)); }
设置对象调用的预期返回值:
when(condition).thenReturn(value1); 当满足when中条件时候,返回value1;当不满足时候,返回null
when(condition).thenReturn(value1).thenReturn(value2); 当满足when中条件时候,第一次返回value1,第n(n>1且∈N*)次返回value2
when(condition).thenReturn(value1)...thenReturn(valuen); 当满足when中条件时候,第一次返回value1,第二次返回value2...第n(n>1且∈N*)次返回valuen
doReturn().when().someMethod(); 与thenReturn()的区别是不会调用真实方法
示例如下:
当满足when中条件时候,返回value1:
@Test void mockitoMethodWhenThen(){ ArrayList mockList = mock(ArrayList.class); //参数匹配(when中条件不满足时候,renturn null) when(mockList.get(anyInt())).thenReturn("mock argumentMatcher"); System.out.println(mockList.get(0)); }
当不满足条件时候,返回null:
@Test void mockitoMethodWhenThen(){ ArrayList mockList = mock(ArrayList.class); //参数匹配(when中条件不满足时候,renturn null) when(mockList.get(1)).thenReturn("mock argumentMatcher"); System.out.println(mockList.get(0)); }
当满足when中条件时候,第一次返回value1,第n次(n>1且∈N*)返回value2:
@Test void mockitoMethodWhenThen(){ ArrayList mockList = mock(ArrayList.class); //参数匹配(when中条件不满足时候,renturn null) when(mockList.get(anyInt())).thenReturn("First trigger").thenReturn("Second trigger").thenReturn("Third trigger"); System.out.println(mockList.get(0)); System.out.println(mockList.get(1)); System.out.println(mockList.get(2)); System.out.println(mockList.get(2)); System.out.println(mockList.get(3)); }
@Test void mockitoMethodDoReturn() { ArrayList mockList = mock(ArrayList.class); mockList.add(1); doReturn(mockList.add(2)).when(mockList).add(3); mockList.add(3); assertThat(mockList.get(2)).isEqualTo(2); }
API如下所示:
/** * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called. ** Simply put: "When the x method is called then return y". * *
* Examples: * *
* * For stubbing void methods with throwables see: {@link Mockito#doThrow(Throwable...)} ** when(mock.someMethod()).thenReturn(10); * * //you can use flexible argument matchers, e.g: * when(mock.someMethod(anyString())).thenReturn(10); * * //setting exception to be thrown: * when(mock.someMethod("some arg")).thenThrow(new RuntimeException()); * * //you can set different behavior for consecutive method calls. * //Last stubbing (e.g: thenReturn("foo")) determines the behavior of further consecutive calls. * when(mock.someMethod("some arg")) * .thenThrow(new RuntimeException()) * .thenReturn("foo"); * * //Alternative, shorter version for consecutive stubbing: * when(mock.someMethod("some arg")) * .thenReturn("one", "two"); * //is the same as: * when(mock.someMethod("some arg")) * .thenReturn("one") * .thenReturn("two"); * * //shorter version for consecutive method calls throwing exceptions: * when(mock.someMethod("some arg")) * .thenThrow(new RuntimeException(), new NullPointerException(); * *
* Stubbing can be overridden: for example common stubbing can go to fixture * setup but the test methods can override it. * Please note that overridding stubbing is a potential code smell that points out too much stubbing. *
* Once stubbed, the method will always return stubbed value regardless * of how many times it is called. *
* Last stubbing is more important - when you stubbed the same method with * the same arguments many times. *
* Although it is possible to verify a stubbed invocation, usually it's just redundant. * Let's say you've stubbed
foo.bar()
. * If your code cares whatfoo.bar()
returns then something else breaks(often before evenverify()
gets executed). * If your code doesn't care whatget(0)
returns then it should not be stubbed. * ** See examples in javadoc for {@link Mockito} class * @param methodCall method to be stubbed * @return OngoingStubbing object used to stub fluently. * Do not create a reference to this returned object. */ @CheckReturnValue public static
OngoingStubbing when(T methodCall) { return MOCKITO_CORE.when(methodCall); }
/** * Sets a return value to be returned when the method is called. E.g: ** * See examples in javadoc for {@link Mockito#when} * * @param value return value * * @return object that allows stubbing consecutive calls */ OngoingStubbing* when(mock.someMethod()).thenReturn(10); *
thenReturn(T value);
/** * Sets consecutive return values to be returned when the method is called. E.g: ** * Last return value in the sequence (in example: 3) determines the behavior of further consecutive calls. ** when(mock.someMethod()).thenReturn(1, 2, 3); *
* See examples in javadoc for {@link Mockito#when} * * @param value first return value * @param values next return values * * @return object that allows stubbing consecutive calls */ // Additional method helps users of JDK7+ to hide heap pollution / unchecked generics array // creation warnings (on call site) @SuppressWarnings({"unchecked", "varargs"}) OngoingStubbing
thenReturn(T value, T... values);
验证调用次数:
verify(T mock);
verify(T mock, VerificationMode mode);
@Test void mockitoMethodVerify(){ ArrayList mockList = mock(ArrayList.class); //验证方法是否被调用及执行次数 mockList.add(1); verify(mockList).add(1); mockList.add(1); mockList.add(2); verify(mockList,times(2)).add(1); verify(mockList,never()).add(3); verify(mockList,atLeastOnce()).add(1); verify(mockList,atLeast(2)).add(1); verify(mockList,atMostOnce()).add(2); verify(mockList,atMost(2)).add(1); }
@Test void mockitoMethodVerify(){ ArrayList mockList = mock(ArrayList.class); //验证方法是否被调用及执行次数 mockList.add(1); mockList.add(1); verify(mockList,atLeast(3)).add(1); }
API如下所示:
/** * Verifies certain behavior happened once. ** Alias to
verify(mock, times(1))
E.g: ** Above is equivalent to: ** verify(mock).someMethod("some arg"); *
** verify(mock, times(1)).someMethod("some arg"); *
* Arguments passed are compared using
equals()
method. * Read about {@link ArgumentCaptor} or {@link ArgumentMatcher} to find out other ways of matching / asserting arguments passed. ** Although it is possible to verify a stubbed invocation, usually it's just redundant. * Let's say you've stubbed
foo.bar()
. * If your code cares whatfoo.bar()
returns then something else breaks(often before evenverify()
gets executed). * If your code doesn't care whatfoo.bar()
returns then it should not be stubbed. * ** See examples in javadoc for {@link Mockito} class * * @param mock to be verified * @return mock object itself */ @CheckReturnValue public static
T verify(T mock) { return MOCKITO_CORE.verify(mock, times(1)); }
/** * Verifies certain behavior happened at least once / exact number of times / never. E.g: ** * times(1) is the default and can be omitted ** verify(mock, times(5)).someMethod("was called five times"); * * verify(mock, atLeast(2)).someMethod("was called at least two times"); * * //you can use flexible argument matchers, e.g: * verify(mock, atLeastOnce()).someMethod(anyString()); *
* Arguments passed are compared using
equals()
method. * Read about {@link ArgumentCaptor} or {@link ArgumentMatcher} to find out other ways of matching / asserting arguments passed. ** * @param mock to be verified * @param mode times(x), atLeastOnce() or never() * * @return mock object itself */ @CheckReturnValue public static
T verify(T mock, VerificationMode mode) { return MOCKITO_CORE.verify(mock, mode); }
/** * Allows verifying exact number of invocations. E.g: ** * See examples in javadoc for {@link Mockito} class * * @param wantedNumberOfInvocations wanted number of invocations * * @return verification mode */ @CheckReturnValue public static VerificationMode times(int wantedNumberOfInvocations) { return VerificationModeFactory.times(wantedNumberOfInvocations); }* verify(mock, times(2)).someMethod("some arg"); *
/** * Alias totimes(0)
, see {@link Mockito#times(int)} ** Verifies that interaction did not happen. E.g: *
* ** verify(mock, never()).someMethod(); *
* If you want to verify there were NO interactions with the mock * check out {@link Mockito#verifyZeroInteractions(Object...)} * or {@link Mockito#verifyNoMoreInteractions(Object...)} *
* See examples in javadoc for {@link Mockito} class * * @return verification mode */ @CheckReturnValue public static VerificationMode never() { return times(0); }
/** * Allows at-least-once verification. E.g: ** Alias to* verify(mock, atLeastOnce()).someMethod("some arg"); *
atLeast(1)
. ** See examples in javadoc for {@link Mockito} class * * @return verification mode */ @CheckReturnValue public static VerificationMode atLeastOnce() { return VerificationModeFactory.atLeastOnce(); }
/** * Allows at-least-x verification. E.g: ** * See examples in javadoc for {@link Mockito} class * * @param minNumberOfInvocations minimum number of invocations * * @return verification mode */ @CheckReturnValue public static VerificationMode atLeast(int minNumberOfInvocations) { return VerificationModeFactory.atLeast(minNumberOfInvocations); }* verify(mock, atLeast(3)).someMethod("some arg"); *
/** * Allows at-most-once verification. E.g: ** Alias to* verify(mock, atMostOnce()).someMethod("some arg"); *
atMost(1)
. ** See examples in javadoc for {@link Mockito} class * * @return verification mode */ @CheckReturnValue public static VerificationMode atMostOnce() { return VerificationModeFactory.atMostOnce(); }
/** * Allows at-most-x verification. E.g: ** * See examples in javadoc for {@link Mockito} class * * @param maxNumberOfInvocations max number of invocations * * @return verification mode */ @CheckReturnValue public static VerificationMode atMost(int maxNumberOfInvocations) { return VerificationModeFactory.atMost(maxNumberOfInvocations); }* verify(mock, atMost(3)).someMethod("some arg"); *
验证失败对应的异常:
异常抛出
when() ...thenThrow(); 当满足条件时候,抛出异常
doThrow()...when(); 满足when中条件时候,抛出异常
当when条件中函数返回值为void时候,不可用thenThrow(),用doThrow()
@Test void mockitoMethodException() { ArrayList mockList = mock(ArrayList.class); mockList.add(1); when(mockList.add(2)).thenThrow(new Exception("异常抛出!")); doThrow(new Exception("异常抛出!")).when(mockList).clear(); }
返回回调接口生成期望值
when(methodCall).thenAnswer(answer));
doAnswer(answer).when(methodCall).[method];
调用真实的方法
doCallRealMethod().when(mock).[method];
AssertJ
AssertJ: JAVA 流式断言器,常见的断言器一条断言语句只能对实际值断言一个校验点,而流式断言器,支持一条断言语句对实际值同时断言多个校验点。是用来验证输出和期望是否一致的一个工具。
手册:assertj-core 3.20.2 javadoc (org.assertj)
常用方法:
as(): 添加错误提示信息
assertThat("abc").as("校验abc").isEqualTo("abcd");
isEqualsTo():相等
contains():包含
assertThat("abc").as("校验abc").isEqualTo("abc").contains("d");
isNull() / isNotNull():为空判断
Object object = null; assertThat(object).isNotNull(); assertThat(object).isNull();
isIn() / isNotIn():范围判断
List list = new ArrayList(); assertThat(list).isIn(new ArrayList(), new HashMap()); assertThat(list).isNotIn(new ArrayList(), new HashMap());
hasSize():大小校验
List list = new ArrayList(); assertThat(list).hasSize(1);
startsWith() / endsWith()等等...可参考API
搭建测试环境
Junit:Java应用程序单元测试标准类库
Spring Test & Spring Boot Test:Spring Boot 应用程序功能集成化测试支持
AssertJ:一个轻量级断言类库
Hamcrest:一个对象匹配器类库,用Junit自带的即可
Mockito: 一个java Mock测试框架
JSONassert:一个用于JSON的断言库
JsonPath: 一个Json操作类库
手册:
Junit:JUnit API
AssertJ:assertj-core 3.20.2 javadoc (org.assertj)
Mockito:mockito-core 3.11.2 javadoc (org.mockito)
Spring Boot Test:Spring Boot Reference Documentation
集成测试demo
2.1 Endpoint层
@SpringBootTest(classes = ScheduleSetup.class) @AutoConfigureMockMvc class ScheduleEndpointTest { @Autowired private MockMvc mockMvc; private ScheduleService scheduleService; @BeforeEach void initScheduleService() { scheduleService = mock(ScheduleServiceImpl.class); } @Test void add_schedule() { try { System.out.println("scheduleService>>>" + scheduleService); //构造请求数据 MapstartPolicyParams = new HashMap<>(); ScheduleDTO scheduleDTO = ScheduleDTO.builder().name("单元测试-1").owner("lijialin").severity(Severity.HIGH) .description("hah").startPolicyType(StartPolicyType.ONCE).startPolicyParams(null) .startTime(System.currentTimeMillis()).paramData(null).createBy("lijialin") .build(); //模拟post请求 mockMvc.perform(MockMvcRequestBuilders.post("/v1/soar-common/schedules") .content(JsonUtil.toJson(scheduleDTO)) .contentType(MediaType.APPLICATION_JSON)) /*.andExpect(MockMvcResultMatchers.status().isOk())*/; assertThat(scheduleService.existsByName("单元测试-1")); } catch (Exception e) { e.printStackTrace(); } }
2.2 Application-Service层
class ScheduleApplicationServiceTest { private ScheduleApplicationService applicationService; private ScheduleService scheduleService; @BeforeEach void init() { applicationService = new ScheduleApplicationService(mock(ScheduleServiceImpl.class), mock(ScheduleRecordServiceImpl.class)); scheduleService = mock(ScheduleServiceImpl.class); } @Test void edit() { MapparamsMap = new HashMap<>(); paramsMap.put("week", 6); paramsMap.put("hour", 8); paramsMap.put("minute", 12); paramsMap.put("interval", 1); ScheduleDTO scheduleDTO = ScheduleDTO.builder().name("新增调度1") .createBy("lijialin") .severity(Severity.HIGH) .startTime(1646387494000L) .startPolicyType(StartPolicyType.WEEKLY) .endPolicyType(EndPolicyType.END_TIME) .endPolicyValue(1646964300000L) .startPolicyParams(paramsMap) .build(); applicationService.save(scheduleDTO); assertThat(scheduleService.existsByName("新增调度1")); ScheduleEditDTO scheduleEditDTO = ScheduleEditDTO.builder() .id("37f62f73-65fc-4c00-89ff-eff295f784bf") .name("编辑调度1") .createBy("lijialin") .severity(Severity.HIGH) .startTime(1646387494000L) .startPolicyType(StartPolicyType.WEEKLY) .endPolicyType(EndPolicyType.END_TIME) .endPolicyValue(1646784300000L) .startPolicyParams(paramsMap) .build(); applicationService.edit(scheduleEditDTO); assertThat(scheduleService.existsByName("编辑调度1")); } }
2.3 Domain-Service层
@Slf4j class ScheduleServiceImplTest { private ScheduleService scheduleService; private Scheduler scheduler; @BeforeEach void init() { scheduler = mock(Scheduler.class); scheduleService = new ScheduleServiceImpl(scheduler); } @Test void addSchedule() { MapcornParams = new HashMap<>(); cornParams.put("type", "ONCE"); String cornParamsStr = JsonUtil.toJson(cornParams); ScheduleObject scheduleObject = ScheduleObject.builder().uuid("72bc5a2d-e5ee-4e83-9866-32b0df54bdb2") .name("单元测试-6") .creator("lijialin") .owner("lijialin") .type("ONCE") .severity(3) .description("desc...") .startTime(System.currentTimeMillis()) .cornParams(cornParamsStr) .build(); scheduleService.addSchedule(scheduleObject); assertThat(scheduleService.existsByName("单元测试-6")); }