分类信息网站开发教程,建手机网站报价,维护一个网站需要多少钱,如何让自己做的网页有网站单元测试
是什么#xff1f;
单元测试#xff08;unit testing#xff09;#xff0c;是指对软件中的最小可测试单元进行检查和验证。至于“单元”的大小或范围#xff0c;并没有一个明确的标准#xff0c;“单元”可以是一个方法、类、功能模块或者子系统。 单元测试通…单元测试
是什么
单元测试unit testing是指对软件中的最小可测试单元进行检查和验证。至于“单元”的大小或范围并没有一个明确的标准“单元”可以是一个方法、类、功能模块或者子系统。 单元测试通常和白盒测试联系到一起如果单从概念上来讲两者是有区别的不过我们通常所说的“单元测试”和“白盒测试”都认为是和代码有关系的所以在某些语境下也通常认为这两者是同一个东西。还有一种理解方式单元测试和白盒测试就是对开发人员所编写的代码进行测试。
作用
1.帮助理解需求 单元测试应该反映使用案例把被测单元当成黑盒测试其外部行为。 2. 提高实现质量 单元测试不保证程序做正确的事但能帮助保证程序正确地做事从而提高实现质量。 3.测试成本低 相比集成测试、验收测试单元测试所依赖的外部环境少自动化程度高时间短节约了测试成本。 4.反馈速度快 单元测试提供快速反馈把bug消灭在开发阶段减少问题流到集成测试、验收测试和用户降低了软件质量控制的成本。 5.利于重构 由于有单元测试作为回归测试用例有助于预防在重构过程中引入bug。 6.文档作用 单元测试提供了被测单元的使用场景起到了使用文档的作用。 7.对设计的反馈 一个模块很难进行单元测试通常是不良设计的信号单元测试可以反过来指导设计出高内聚、低耦合的模块。
什么时候写单元测试
更多的时候是同时在实现代码和单元测试。因为这样既可以在实现的过程中随时执⾏单元测试来验证同时也避免分开写时要重复理解⼀遍设计需求⼤⼤提⾼了效率节约了时间。
这⾥特别要强调的⼀点是有时候写单元测试和不写单元测试会直接影响到代码的设计和实现。⽐如要写⼀个有很多条件分⽀处理的函数如果不考虑单测你很可能把这所有的逻辑写在⼀个函数⾥。但是如果考虑到单测实现的简洁你就会把各个分⽀各写成⼀个函数然后把分⽀逻辑另写成⼀个函数最终意外达到了优化代码的⽬的。所以评判代码或者设计好不好的⼀个准则是看它容不容易测试。
什么时候可以不写单元测试
在个⼈的⼯作实践中很少遇到可以不写单元测试的情况当然确实有过不⽤写的时候。下面是可能遇到的几种情况请自行掂量。 函数逻辑太复杂了历史上也从没有⼈为它写过单测代码的reviewer也没有要求我写。 代码的重要性不够都是自己写自己维护的即使代码有问题也不会有什么重要影响的。有些接⼝的⼤函数典型如Main函数… 写对应的单元测试⾮常的复杂甚⾄⽆法写。这时候很可能 需要修改设计必须让你的设计易于单元测试需要增强单元测试框架框架功能不够不能很好⽀持某种场景下的单测。实在想不起来还有什么情况…也许有些涉及到⽤户交互的UI单元
单元测试编写规范 好的单元测试必须遵守AIR原则。AAutomatic(自动化),IIndependent(独立性),RRepeatable(可重复) Automatic(自动化) 单元测试应该是全自动执行的并且非交互式的。测试用例通常是被定期执行的执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试。单元测试中不准使用System.out来进行人肉认证必须使用assert来验证。 Independent(独立性) 保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护单元测试用例之间绝不能互相调用也不能依赖执行的先后次序。 Repeatable(可重复) 单元测试是可以重复执行的不能受到外界环境的影响。单元测试通常会被放到持续集成中每次有代码check in时单元测试都会被执行。如果单测对外部环境有依赖容易导致持续继承机制的不可用。 对于单元测试要保证测试粒度足够小有助于精确定位问题单测粒度至多是类级别一般是方法级别。 核心业务、核心应用、核心模块的增量代码确保单元测试通过。 单元测试代码必须写在如下工程目录src/test/java不允许写在业务代码目录下。 每个单元测试应该有个好名字让⼈⼀看就知道是做什么测试如果名字不能说明问题也要加上完整的注释。⽐如 testSortNumbers_withDuplicated, 意味SortNumbers函数的单元测试来验证有重复数字的情况。
代码覆盖率
是什么
代码覆盖率是对整个测试过程中被执行的代码的衡量它能测量源代码中的哪些语句在测试中被执行哪些语句尚未被执行。
为什么要测量代码覆盖率
众所周知测试可以提高软件版本的质量和可预测性。但是你知道你的单元测试甚至是你的功能测试实际测试代码的效果如何吗是否还需要更多的测试
这些是代码覆盖率可以试图回答的问题。总之出于以下原因我们需要测量代码覆盖率
了解我们的测试用例对源代码的测试效果了解我们是否进行了足够的测试在软件的整个生命周期内保持测试质量
注代码覆盖率不是灵丹妙药覆盖率测量不能替代良好的代码审查和优秀的编程实践。
通常我们应该采用合理的覆盖目标力求在代码覆盖率在所有模块中实现均匀覆盖而不是只看最终数字的是否高到令人满意。
举例假设代码覆盖率只在某一些模块代码覆盖率很高但在一些关键模块并没有足够的测试用例覆盖那样虽然代码覆盖率很高但并不能说明产品质量就很高。
代码覆盖率的指标种类
代码覆盖率工具通常使用一个或多个标准来确定你的代码在被自动化测试后是否得到了执行常见的覆盖率报告中看到的指标包括
函数覆盖率定义的函数中有多少被调用语句覆盖率程序中的语句有多少被执行分支覆盖率针对 if…else、case 等分支语句看代码中设计的分支是否都被测试到了。针对 if条件1只要条件 1 取 true 和 false 都执行过则这个分支就完全覆盖了。条件覆盖率条件覆盖率可以看作是对分支覆盖率的补充。每一个分支条件表达式中所有条件的覆盖。行覆盖率有多少行的源代码被测试过
对比main方法优点
比代码中写main 方法测试的好处
可以书写一系列的 测试方法对项目所有的 接口或者方法进行单元测试。启动后自动化测试并判断执行结果, 不需要人为的干预只需要查看最后结果就知道整个项目的方法接口是否通畅。。每个单元测试用例相对独立 由Junit 启动自动调用。 不需要添加额外的调用语句。
而main 方法不一样。 对多个方法调用。 需要添加打印或者输出语句。 添加了新的测试方法。 需要在main方法添加方法调用。 不能形成整体的测试结果。 需要对打印或者输出结果进行人为的判断。
JUnit
JUnit 是一个 Java 编程语言的单元测试框架。JUnit 在测试驱动的开发方面有很重要的发展是起源于 JUnit 的一个统称为 xUnit 的单元测试框架之一。
注解
JUnit 提供以下注解来编写测试。
注解描述RunWith用于设置测试运行器。例如我们可以通过 RunWith(SpringJUnit4ClassRunner.class) 让测试运行于 Spring 测试环境。Before带注解的方法将在测试类中的每个测试方法之前运行。After带注解的方法将在测试类中的每个测试方法之后运行。BeforeClass带注解的方法将在测试类中的所有测试方法之前运行。 此方法必须是静态的。AfterClass带注解的方法将在测试类中的所有测试方法之后运行。 此方法必须是静态的。Test用于将方法标记为 junit 测试Ignore它用于禁用或忽略测试套件中的测试类或方法。Rule引用规则在一个class中所有的Test标注过的测试方法都会共享这个Rule
一个单元测试类执行顺序为
BeforeClass – Before – Test – After – AfterClass每一个测试方法的调用顺序为
Before – Test – After编写测试
在 JUnit 中测试方法带有Test注解。 为了运行该方法JUnit 首先构造一个新的类实例然后调用带注解的方法。 测试抛出的任何异常将由 JUnit 报告为失败。 如果未引发任何异常则假定测试成功。
public class Demo1 {BeforeClasspublic static void setup() {System.out.println(BeforeClass);}Beforepublic void setupThis() {System.out.println(Before);}Testpublic void method() {System.out.println(测试);}Afterpublic void tearThis() {System.out.println(After);}AfterClasspublic static void tear() {System.out.println(AfterClass);}
}public class Demo2 {Testpublic void test1() {System.out.println(Test);}IgnoreTestpublic void testIgnore() {System.out.println(Ignore);}
}断言方法
**断言(assertion)**是一种在程序中的一阶逻辑(如一个结果为真或假的逻辑判断式)目的为了表示与验证软件开发者预期的结果——当程序执行到断言的位置时对应的断言应该为真。若断言不为真时程序会中止执行并给出错误信息。 Junit 4 Assert Methods MethodDescriptionassertNull(java.lang.Object object)检查对象是否为空assertNotNull(java.lang.Object object)检查对象是否不为空assertEquals(long expected, long actual)检查long类型的值是否相等assertFalse(boolean condition)检查条件是否为假assertTrue(boolean condition)检查条件是否为真 assertNotSame(java.lang.Object unexpected, java.lang.Object actual) 检查两个对象引用是否不引用统一对象(即对象不等)
案例
public class Demo3 {Testpublic void testAssertNull() {String str null;assertNull(str);}Testpublic void testAssertNotNull() {String str hello Java!!;assertNotNull(str);}Testpublic void testAssertEqualsLong() {long long1 2;long long2 2;assertEquals(long1, long2);}Testpublic void testAssertTrue() {ListString list new ArrayList();assertTrue(list.isEmpty());}Testpublic void testAssertFalse() {ListString list new ArrayList();list.add(hello);assertFalse(list.isEmpty());}Testpublic void testAssertSame() {String str1 hello world!!;String str2 hello world!!;assertSame(str2, str1);}Testpublic void testAssertNotSame() {String str1 hello world!!;String str3 hello Java!!;assertNotSame(str1, str3);}
}期望异常测试
有两种方法实现
1. Test(expected…)
Test注解有一个可选的参数expected允许你设置一个Throwable的子类
2. ExpectedException
如果要使用JUnit框架中的ExpectedException类需要声明ExpectedException异常。
案例
public class Demo4 {Rulepublic ExpectedException thrown ExpectedException.none();public void division() {int i 5 / 0;}Test(expected ArithmeticException.class)public void test1() {division();}Test()public void test2() {thrown.expect(ArithmeticException.class);division();}}优先级测试
将测试方法构成测试回环的时候就需要确定测试方法执行顺序以此记录。
FixMethodOrder是控制Test方法执行顺序的注解她有三种选择
MethodSorters.JVM 按照JVM得到的顺序执行 MethodSorters.NAME_ASCENDING 按照方法名字顺序执行 MethodSorters.DEFAULT 按照默认顺序执行 以确定的但是不可预期的顺序执行hashcode大小
(MethodSorters.JVM)
public class Demo5 {Testpublic void test2() {System.out.println(test2);}Testpublic void test1() {System.out.println(test1);}Testpublic void test3() {System.out.println(test3);}}参数化测试
参数化测试主要解决一次性进行多个测试用例的测试。其主要思想是将多个测试用例按照{输入值输出值}输入值可以是多个的列表方式进行测试。
//(1)步骤一测试类指定特殊的运行器org.junit.runners.Parameterized
RunWith(Parameterized.class)
public class Demo6 {// (2)步骤二为测试类声明变量分别用于存放期望值和测试所用数据。private final int expected;private final int a;private final int b;public Demo6(int expected, int a, int b) {this.expected expected;this.a a;this.b b;}// (4)步骤四为测试类声明一个使用注解 org.junit.runners.Parameterized.Parameters 修饰的// 返回值为java.lang.Iterable 的公共静态方法并在此方法中初始化所有需要测试的参数对。Parameterspublic static IterableInteger[] getTestParamters() {return Arrays.asList(new Integer[][]{{2, 1, 1}, {3, 2, 1}, {4, 3, 1}});}// (5)步骤五编写测试方法使用定义的变量作为参数进行测试。 Testpublic void testAdd() {Demo calculator new Demo();System.out.println(输入参数 a and b 预期值 expected);assertEquals(expected, calculator.add(a, b));}
}超时测试
如果一个测试用例比起指定的毫秒数花费了更多的时间那么 Junit 将自动将它标记为失败。timeout 参数和 Test注释一起使用。现在让我们看看活动中的 test(timeout)。
public class Demo7 {Test(timeout 1000)public void testTimeout() throws InterruptedException {TimeUnit.SECONDS.sleep(2);System.out.println(Complete);}
}上面测试会失败在一秒后会抛出异常 org.junit.runners.model.TestTimedOutException: test timed out after 1000 milliseconds
Mock
为什么要使用 mock Mock 的意思是模拟它可以用来对系统、组件或类进行隔离。 在测试过程中我们通常关注测试对象本身的功能和行为而对测试对象涉及的一些依赖仅仅关注它们与测试对象之间的交互比如是否调用、何时调用、调用的参数、调用的次数和顺序以及返回的结果或发生的异常等并不关注这些被依赖对象如何执行这次调用的具体细节。 因此Mock 机制就是使用 Mock 对象替代真实的依赖对象并模拟真实场景来开展测试工作。 使用 Mock 对象完成依赖关系测试的示意图如下所示 可以看出在形式上Mock 是在测试代码中直接 Mock 类和定义 Mock 方法的行为通常测试代码和 Mock 代码放一起。因此测试代码的逻辑从测试用例的代码上能很容易地体现出来。
Mockito 中常用方法
Mockito的使用一般有以下几种组合
do/when包括doThrow(…).when(…)/doReturn(…).when(…)/doAnswer(…).when(…) 返回值为void时使用given/will包括given(…).willReturn(…)/given(…).willAnswer(…)when/then: 包括when(…).thenReturn(…)/when(…).thenAnswer(…)
Mock 方法
mock 方法来自 org.mockito.Mock它表示可以 mock 一个对象或者是接口。
public static T T mock(ClassT classToMock)classToMock待 mock 对象的 class 类。返回 mock 出来的类
实例使用 mock 方法 mock 一个类
Random random Mockito.mock(Random.class);对 Mock 出来的对象进行行为验证和结果断言
验证是校验待验证的对象是否发生过某些行为Mockito 中验证的方法是verify。 Testvoid addTest() {Random random Mockito.mock(Random.class);System.out.println(random.nextInt());Mockito.verify(random).nextInt();// Mockito.verify(random, Mockito.times(2)).nextInt();}使用 verify 验证
Verify 配合 time() 方法可以校验某些操作发生的次数。 Testvoid addTest() {Random random Mockito.mock(Random.class);System.out.println(random.nextInt());Mockito.verify(random, Mockito.times(2)).nextInt();}断言使用到的类是 Assert.
Random random Mockito.mock(Random.class, test);
Assert.assertEquals(100, random.nextInt());输出结果
org.opentest4j.AssertionFailedError:
Expected :100
Actual :0当使用 mock 对象时如果不对其行为进行定义则 mock 对象方法的返回值为返回类型的默认值。
给 Mock 对象打桩
桩或称桩代码是指用来代替关联代码或者未实现代码的代码。如果函数B用B1来代替那么B称为原函数B1称为桩函数。打桩就是编写或生成桩代码。
打桩可以理解为 mock 对象规定一行的行为使其按照我们的要求来执行具体的操作。在 Mockito 中常用的打桩方法为
方法含义when().thenReturn()Mock 对象在触发指定行为后返回指定值when().thenThrow()Mock 对象在触发指定行为后抛出指定异常when().doCallRealMethod()Mock 对象在触发指定行为后调用真实的方法
thenReturn() 代码示例 Testvoid addTestOngoingStubbing() {MockitoAnnotations.initMocks(this);Mockito.when(random.nextInt()).thenReturn(1); //打桩指定返回值System.out.println(random.nextInt());}输出1Mockito 中常用注解
可以代替 Mock 方法的 Mock 注解 Shorthand for mocks creation - Mock annotation Important! This needs to be somewhere in the base class or a test runner: 快速 mock 的方法使用 mock 注解。
mock 注解需要搭配 MockitoAnnotations.initMocks(testClass) 方法一起使用。 Mockprivate Random random;Testvoid addTestAnnotations() {//开启注解否则空指针MockitoAnnotations.initMocks(this);System.out.println(random.nextInt());Mockito.verify(random).nextInt();}Spy 方法与 Spy 注解
spy() 方法与 mock() 方法不同的是
被 spy 的对象会走真实的方法而 mock 对象不会spy() 方法的参数是对象实例mock 的参数是 class
示例spy 方法与 Mock 方法的对比 Testvoid addTestMockAndSpyDifferent() {Demo mock Mockito.mock(Demo.class);Assert.assertEquals(0, mock.add(1, 2));Demo spy Mockito.spy(new Demo());Assert.assertEquals(3, spy.add(1, 2));}输出结果
// 第一个 Assert 断言失败因为没有给 Demo 对象打桩因此返回默认值
java.lang.AssertionError: expected:3 but was:0
预期:3
实际:0使用 Spy 注解代码示例
Spyprivate Demo demo;Testvoid addTestAnnotations() {MockitoAnnotations.initMocks(this);int res demo.add(1, 2);Assert.assertEquals(3, res);// Assert.assertEquals(4,res);}Spring Boot 中使用 JUnit
Spring 框架提供了一个专门的测试模块spring-test用于应用程序的集成测试。 在 Spring Boot 中你可以通过spring-boot-starter-test启动器快速开启和使用它。
Spring Boot 测试
// 获取启动类加载配置确定装载 Spring 程序的装载方法它回去寻找 主配置启动类被 SpringBootApplication 注解的
SpringBootTest
// 让 JUnit 运行 Spring 的测试环境 获得 Spring 环境的上下文的支持
RunWith(SpringRunner.class)
public class Demo1 {Autowiredprivate UserService userService;Testpublic void getUser() {User user userService.getUser(1);Assert.assertEquals(bob,user.getName());}
}SpringBootTest - webEnvironment
MOCK加载 WebApplicationContext 并提供一个 Mock 的 Servlet 环境此时内置的 Servlet 容器并没有正式启动。RANDOM_PORT加载 EmbeddedWebApplicationContext 并提供一个真实的 Servlet 环境然后使用一个随机端口启动内置容器。DEFINED_PORT这个配置也是通过加载 EmbeddedWebApplicationContext 提供一个真实的 Servlet 环境但使用的是默认端口如果没有配置端口就使用 8080。NONE加载 ApplicationContext 但并不提供任何真实的 Servlet 环境。
在 Spring Boot 中SpringBootTest 注解主要用于测试基于自动配置的 ApplicationContext它允许我们设置测试上下文中的 Servlet 环境。
在多数场景下一个真实的 Servlet 环境对于测试而言过于重量级通过 MOCK 环境则可以缓解这种环境约束所带来的困扰
RunWith 注解与 SpringRunner
在上面的示例中我们还看到一个由 JUnit 框架提供的 RunWith 注解它用于设置测试运行器。例如我们可以通过 RunWith(SpringJUnit4ClassRunner.class) 让测试运行于 Spring 测试环境。
虽然这我们指定的是 SpringRunner.class实际上**SpringRunner 就是 SpringJUnit4ClassRunner 的简化它允许 JUnit 和 Spring TestContext 整合运行而 Spring TestContext 则提供了用于测试 Spring 应用程序的各项通用的支持功能。
Spring MVC 测试
当你想对 Spring MVC 控制器编写单元测试代码时可以使用WebMvcTest注解。它提供了自配置的 MockMvc可以不需要完整启动 HTTP 服务器就可以快速测试 MVC 控制器。
使用WebMvcTest注解时只有一部分的 Bean 能够被扫描得到它们分别是
ControllerControllerAdviceJsonComponentFilterWebMvcConfigurerHandlerMethodArgumentResolver其他常规的Component包括Service、Repository等Bean 则不会被加载到 Spring 测试环境上下文中。
注意
如果报错java.lang.IllegalStateException: Found multiple SpringBootConfiguration annotated classes...... 在类上添加注解ContextConfiguration(classes {测试启动类.class})加载配置类
RunWith(SpringRunner.class)
WebMvcTest(UserController.class)
Slf4j
public class Demo2 {Autowiredprivate MockMvc mvc;MockBeanprivate UserService userService;Beforepublic void setUp() {//打桩Mockito.when(userService.getUser(1)).thenReturn(new User(1, 张三));}Testpublic void getUser() throws Exception {MvcResult mvcResult mvc.perform(MockMvcRequestBuilders.get(/user/1))//构建请求 测试的相对地址.andExpect(status().isOk()) // 期待返回状态吗码200.andExpect(jsonPath($.name).value(IsEqual.equalTo(张三))) // 这里是期待返回值是 张三.andDo(print())//打印输出流.andReturn();//返回结果String content mvcResult.getResponse().getContentAsString(Charset.defaultCharset());log.info(返回结果{}, content);}
}Spring Boot Web 测试
当你想启动一个完整的 HTTP 服务器对 Spring Boot 的 Web 应用编写测试代码时可以使用SpringBootTest(webEnvironment WebEnvironment.RANDOM_PORT)注解开启一个随机的可用端口。Spring Boot 针对 REST 调用的测试提供了一个 TestRestTemplate 模板它可以解析链接服务器的相对地址。
RunWith(SpringRunner.class)
SpringBootTest(webEnvironment SpringBootTest.WebEnvironment.RANDOM_PORT)
Slf4j
public class Demo3 {Autowiredprivate TestRestTemplate restTemplate;Testpublic void getUser(){ResponseEntityUser result restTemplate.getForEntity(/user/2, User.class);User body result.getBody();Assert.assertThat(body, Matchers.notNullValue());log.info(User{}, body);}
}使用 DataJpaTest 注解测试数据访问组件
如要需要使用真实环境中的数据库进行测试需要替换掉默认规则使用AutoConfigureTestDatabase(replace Replace.NONE)注解
RunWith(SpringRunner.class)
DataJpaTest
AutoConfigureTestDatabase(replace AutoConfigureTestDatabase.Replace.NONE)
public class Demo4 {Autowiredprivate UserRepository userRepository;Testpublic void getUser(){User user userRepository.findById(2).orElse(null);assert user ! null;Assert.assertThat(tom, Matchers.is(user.getName()));}
}Spring Service层测试
RunWith(SpringRunner.class)
public class Demo6 {Autowiredprivate UserService userService;MockBeanprivate UserRepository userRepository;Testpublic void testGetUser() {User user1 new User(1, zs);Mockito.when(userRepository.findById(1)).thenReturn(Optional.of(user1));User user2 userService.getUser(1);Assert.assertEquals(user1.getId(), user2.getId());}TestConfigurationpublic static class prepareOrderService {Beanpublic UserService getGlobalExceptionMsgSendService() {return new UserService();}}}rElse(null); assert user ! null; Assert.assertThat(“tom”, Matchers.is(user.getName())); } } ## Spring Service层测试java
RunWith(SpringRunner.class)
public class Demo6 {Autowiredprivate UserService userService;MockBeanprivate UserRepository userRepository;Testpublic void testGetUser() {User user1 new User(1, zs);Mockito.when(userRepository.findById(1)).thenReturn(Optional.of(user1));User user2 userService.getUser(1);Assert.assertEquals(user1.getId(), user2.getId());}TestConfigurationpublic static class prepareOrderService {Beanpublic UserService getGlobalExceptionMsgSendService() {return new UserService();}}}