酒店网站建设哪家好,网站开发价格 北京,淘宝宝贝排名查询,三合一网站为鼓励单元测试#xff0c;特分门别类示例各种组件的测试代码并进行解说#xff0c;供开发人员参考。
本文中的测试均基于JUnit5。
单元测试实战#xff08;一#xff09;Controller 的测试
单元测试实战#xff08;二#xff09;Service 的测试
单元测试实战特分门别类示例各种组件的测试代码并进行解说供开发人员参考。
本文中的测试均基于JUnit5。
单元测试实战一Controller 的测试
单元测试实战二Service 的测试
单元测试实战三JPA 的测试
单元测试实战四MyBatis-Plus 的测试
单元测试实战五普通类的测试
单元测试实战六其它
概述
普通类或曰POJO的测试是最简单的一种情况大多数情况下只使用JUnit即可。万一有不易实例化的外部依赖也可以用Mockito的Mock来模拟。这类测试一般应脱离Spring上下文来进行。
需要的话在每个测试之前应清理/重置测试数据一般为方法参数或待测类实例断言应主要检查待测类的行为是否符合预期。
依赖
大多数普通类测试只依赖JUnit但作为一般实践我们通常也带上Spring Boot自己的测试工具集。
dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-test/artifactIdscopetest/scope
/dependency
dependencygroupIdorg.junit.jupiter/groupIdartifactIdjunit-jupiter-api/artifactIdscopetest/scope
/dependency
示例
以下是一个BigDecimal的包装类实现了一些BigDecimal的最佳实践该类是我们的待测试类
package com.aaa.sdk.utils;import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Objects;/*** BigDecimal的包装类封装了以下实践* li不允许null值使用工厂方法创建实例时会直接报错。/li* li避免double构造传参造成精度丢失。/li* li封装equals强制使用compareTo来比较。/li* li封装格式化输出补零对齐默认两位。/li* li封装加减乘除尤其除法强制使用BigDecimal的三参方法避免无限小数报错。/li* li直观的判断正数、负数、零的方法。/li* br/** 本类设计为不可变对象非读方法加减乘除、取反、四舍五入等都会产生新对象。** author ioriogami**/
public class DecimalWrapper implements ComparableDecimalWrapper {/*** 默认精度保留两位。*/public static final int DEFAULT_SCALE 2;private final BigDecimal decimal;private DecimalWrapper(BigDecimal decimal) {Objects.requireNonNull(decimal);this.decimal decimal;}// 以下工厂方法/*** 通过一个BigDecimal对象构造DecimalWrapper实例。* param decimal 本wrapper代表的BigDecimal对象*/public static DecimalWrapper of(BigDecimal decimal) {return new DecimalWrapper(decimal);}/*** 通过一个String实例构造DecimalWrapper实例。* param s 字符串数值*/public static DecimalWrapper of(String s) {return of(new BigDecimal(s));}/*** 通过一个double实例构造DecimalWrapper实例。* param d double数值*/public static DecimalWrapper of(double d) {return of(new BigDecimal(String.valueOf(d)));}/*** 通过一个float实例构造。* param f float数值*/public static DecimalWrapper of(float f) {return of(new BigDecimal(String.valueOf(f)));}// 以下一般接口/*** 获取底层的BigDecimal对象*/public BigDecimal toBigDecimal() {return decimal;}/*** 获取double值。*/public Double toDouble() {return decimal.doubleValue();}Overridepublic String toString() {return decimal.toPlainString();}Overridepublic int hashCode() {return decimal.hashCode();}Overridepublic boolean equals(Object obj) {if (obj instanceof DecimalWrapper) {DecimalWrapper other (DecimalWrapper) obj;return decimal.compareTo(other.decimal) 0;}return false;}Overridepublic int compareTo(DecimalWrapper that) {if (that null) return 1;return this.decimal.compareTo(that.decimal);}// 以下格式化输出/*** 四舍五入保留两位。* return 一个新的DecimalWrapper对象*/public DecimalWrapper round() {return round(DEFAULT_SCALE, RoundingMode.HALF_UP);}/*** 四舍五入保留i位。* param scale 精度即i保留几位。* return 一个新的DecimalWrapper对象*/public DecimalWrapper round(int scale) {return round(scale, RoundingMode.HALF_UP);}/*** m舍n入保留i位。** param scale 精度即i保留几位。* param mode 舍入策略m和n。* return 一个新的DecimalWrapper对象*/public DecimalWrapper round(int scale, RoundingMode mode) {return of(decimal.setScale(scale, mode));}/*** 获得四舍五入保留两位强制补0后的字符串。*/public String format() {return new DecimalFormat(0.00).format(decimal.setScale(DEFAULT_SCALE, RoundingMode.HALF_UP));}/*** 获得四舍五入保留i位强制补0后的字符串。* param scale 精度即i保留几位。*/public String format(int scale) {return format(scale, RoundingMode.HALF_UP);}/*** 获得m舍n入保留scale位强制补0后的字符串。不会影响本身的值。* param scale 精度即i保留几位。* param mode 舍入策略m和n若为null则默认四舍五入。* return 格式化后的字符串。*/public String format(int scale, RoundingMode mode) {if (scale 0) throw new IllegalArgumentException(精度必须大于0);if (mode null) mode RoundingMode.HALF_UP;StringBuilder buff new StringBuilder(0.);for (int i 0; i scale; i) {buff.append(0);}return new DecimalFormat(buff.toString()).format(decimal.setScale(scale, mode));}// 以下加减乘除、取反/*** 加法。*/public DecimalWrapper add(DecimalWrapper other) {if (other null) throw new IllegalArgumentException(操作数为null无法进行加法运算。);return of(decimal.add(other.decimal));}/*** 减法。*/public DecimalWrapper subtract(DecimalWrapper other) {if (other null) throw new IllegalArgumentException(操作数为null无法进行减法运算。);return of(decimal.subtract(other.decimal));}/*** 乘法。*/public DecimalWrapper multiply(DecimalWrapper other) {if (other null) throw new IllegalArgumentException(操作数为null无法进行乘法运算。);return of(decimal.multiply(other.decimal));}/*** 除法。*/public DecimalWrapper divide(DecimalWrapper other) { // 使用三参除法避免结果为无限小数时报错。return divide(other, DEFAULT_SCALE, RoundingMode.HALF_UP);}/*** 除法指定精度和舍入策略。*/public DecimalWrapper divide(DecimalWrapper other, int scale, RoundingMode mode) {if (other null) throw new IllegalArgumentException(操作数为null无法进行除法运算。);if (scale 0) throw new IllegalArgumentException(精度必须大于0);if (mode null) mode RoundingMode.HALF_UP;return of(decimal.divide(other.decimal, scale, mode));}/*** 取反。*/public DecimalWrapper negate() {return of(decimal.negate());}/*** 判断是否零值不管到底是0.0、0.00还是0.0000..*/public boolean isZero() {return decimal.signum() 0;}/*** 判断是否正数。*/public boolean isPositive() {return decimal.signum() 1;}/*** 判断是否正数。*/public boolean isNegative() {return decimal.signum() -1;}
}
以下是对DecimalWrapper进行测试的测试类
package com.aaa.sdk.utils;import java.math.BigDecimal;
import java.math.RoundingMode;import org.junit.jupiter.api.Test;import static org.junit.jupiter.api.Assertions.*;class DecimalWrapperTest {Testvoid testConstructBigDecimal() {DecimalWrapper d DecimalWrapper.of(new BigDecimal(1.11));assertEquals(1.11, d.format());}Testvoid testConstructDouble() {DecimalWrapper d DecimalWrapper.of(1.11);assertEquals(1.11, d.format());}Testvoid testConstructString() {DecimalWrapper d DecimalWrapper.of(1.11);assertEquals(1.11, d.format());}Testvoid testConstructFloat() {DecimalWrapper d DecimalWrapper.of(1.1f);assertEquals(1.10, d.format());}Testvoid testConstructNullParam() {try {DecimalWrapper.of((String) null);fail(should not get here!);} catch (NullPointerException npe) {}try {DecimalWrapper.of((Double) null);fail(Should not get here!);} catch (NullPointerException npe) {}try {DecimalWrapper.of((Float) null);fail(Should not get here!);} catch (NullPointerException npe) {}try {DecimalWrapper.of((BigDecimal) null);fail(Should not get here!);} catch (NullPointerException npe) {}}Testvoid testComparison() {DecimalWrapper d1 DecimalWrapper.of(1.1);DecimalWrapper d2 DecimalWrapper.of(1.2);DecimalWrapper d3 DecimalWrapper.of(1.0);assertTrue(d1.compareTo(d2) 0);assertTrue(d2.compareTo(d1) 0);assertTrue(d3.compareTo(d1) 0);assertTrue(d3.compareTo(d2) 0);DecimalWrapper d4 DecimalWrapper.of(1.00);assertTrue(d3.compareTo(d4) 0);DecimalWrapper d5 null;assertTrue(d3.compareTo(d5) 0);}Testvoid testToDecimal() {DecimalWrapper d1 DecimalWrapper.of(1.11);assertEquals(d1.toBigDecimal(), new BigDecimal(1.11));}Testvoid testToDouble() {DecimalWrapper d1 DecimalWrapper.of(1.11);assertEquals(d1.toDouble(), 1.11);}Testvoid testEquals() {DecimalWrapper d1 DecimalWrapper.of(1.12345);DecimalWrapper d2 DecimalWrapper.of(1.12345f);DecimalWrapper d3 DecimalWrapper.of(1.12345);DecimalWrapper d4 DecimalWrapper.of(new BigDecimal(1.12345));assertEquals(d1, d2);assertEquals(d2, d3);assertEquals(d3, d4);}Testvoid testRoundDefault() {DecimalWrapper d1 DecimalWrapper.of(1.12385);DecimalWrapper d2 DecimalWrapper.of(1.12385f);DecimalWrapper d3 DecimalWrapper.of(1.12385);DecimalWrapper d4 DecimalWrapper.of(new BigDecimal(1.12385));assertEquals(d1.round(), DecimalWrapper.of(1.12));assertEquals(d2.round(), DecimalWrapper.of(1.12));assertEquals(d3.round(), DecimalWrapper.of(1.12));assertEquals(d4.round(), DecimalWrapper.of(1.12));}Testvoid testRound() {DecimalWrapper d1 DecimalWrapper.of(1.12385);DecimalWrapper d2 DecimalWrapper.of(1.12385f);DecimalWrapper d3 DecimalWrapper.of(1.12385);DecimalWrapper d4 DecimalWrapper.of(new BigDecimal(1.12385));assertEquals(d1.round(3), DecimalWrapper.of(1.124));assertEquals(d2.round(3), DecimalWrapper.of(1.124));assertEquals(d3.round(3), DecimalWrapper.of(1.124));assertEquals(d4.round(3), DecimalWrapper.of(1.124));assertEquals(d4.round(3, RoundingMode.DOWN), DecimalWrapper.of(1.123));}Testvoid testFormat() {DecimalWrapper d1 DecimalWrapper.of(1.12385);assertEquals(d1.format(), 1.12);assertEquals(d1.format(3), 1.124);assertEquals(d1.format(3, RoundingMode.DOWN), 1.123);}Testvoid testAdd() {DecimalWrapper d1 DecimalWrapper.of(1.12385);DecimalWrapper d2 DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of(2.24770), d1.add(d2));DecimalWrapper d3 DecimalWrapper.of(0);assertEquals(DecimalWrapper.of(1.12385), d3.add(d2));assertTrue(d3.add(d3).isZero());}Testvoid testSubtract() {DecimalWrapper d1 DecimalWrapper.of(2.24770);DecimalWrapper d2 DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of(1.12385), d1.subtract(d2));DecimalWrapper d3 DecimalWrapper.of(0); // change to: 0.0assertEquals(DecimalWrapper.of(1.12385), d2.subtract(d3));assertTrue(d2.subtract(d2).isZero());assertTrue(d3.subtract(d3).isZero());}Testvoid testMultiply() {DecimalWrapper d1 DecimalWrapper.of(1.12385);DecimalWrapper d2 DecimalWrapper.of(1.12385);assertEquals(DecimalWrapper.of(1.2630388225), d1.multiply(d2));DecimalWrapper d3 DecimalWrapper.of(0.00);assertTrue(d2.multiply(d3).isZero());DecimalWrapper d4 DecimalWrapper.of(-1);assertEquals(DecimalWrapper.of(-1.12385), d1.multiply(d4));}Testvoid testDivide() {DecimalWrapper d1 DecimalWrapper.of(10.0);DecimalWrapper d2 DecimalWrapper.of(3);assertEquals(DecimalWrapper.of(3.33), d1.divide(d2));DecimalWrapper d3 DecimalWrapper.of(0);assertTrue(d3.multiply(d1).isZero());try {d1.divide(d3);fail(divide by zero, should not get here!);} catch (Exception e){}}Testvoid testNegate() {DecimalWrapper d1 DecimalWrapper.of(-1.12385);assertTrue(d1.isNegative());assertFalse(d1.isZero());assertFalse(d1.isPositive());DecimalWrapper d2 d1.negate();assertTrue(d2.isPositive());assertFalse(d2.isZero());assertFalse(d2.isNegative());assertTrue(d1.add(d2).isZero());}}
测试类说明
测试类的代码和被测类的代码都比较直观不需做过多说明。
在该类中我们为被测类的每个公共方法都创建了测试方法且只用到了JUnit的Test和Assertions如果需要事前准备和事后清理工作还可以加上BeforeEach、AfterEach、BeforeAll、AfterAll等方法。
第37行的testConstructNullParam是负面测试测试当传入null值是不允许的会抛出NullPointerException。
第101行的testRoundDefault、第113行的testRound是对同一个方法的不同情况精度的测试。
私有方法的测试
私有方法不需要测试。对私有方法的测试应通过调用它的公有方法的测试来进行。直接测试私有方法往往是一种代码“坏味道”。
虽然但是有时候确实想给私有方法写一个测试也不是做不到
package com.aaa.api.auth.filter;import com.aaa.sdk.utils.Utils;
import org.junit.jupiter.api.Test;import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;import static org.junit.jupiter.api.Assertions.*;
class AuthNFilterTest {Testvoid testExtractUsernameFromToken() throws InvocationTargetException, IllegalAccessException {String token eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...;AuthNFilter filter new AuthNFilter();Method m Utils.doGetMethod(AuthNFilter.class, extractUsernameFromToken, String.class);//long start System.currentTimeMillis();String user (String) m.invoke(filter, token);//System.out.println(User is user , used (System.currentTimeMillis() - start) ms);assertEquals(ioriogami, user);}
}
第15行我们new了一个待测试对象。
第16行我们用反射获取了AuthNFilter类的extractUsernameFromToken私有方法该方法接受一个String类型的参数然后在第18行对其进行调用。 如需对构造方法、私有方法、静态方法、final类和方法进行mock可以使用powermock提供的增强版MockitoPowerMockito。 总结
普通类的测试以JUnit简单测试为主一般不需要Spring上下文。
每一个public方法都有至少一个测试对不同的情况/分支建议有多个测试最好也有负面测试。当然一个完美的测试类应该测试每个方法的正面行为、负面行为、边缘情况并应尽可能覆盖所有分支。但在有限的时间内我们应识别最重要的方面进行测试。
单元测试针对单个类原则上测试类与被测类一对一。当然也可以针对一组紧密相关的类/接口编写单元测试比如pagewindow是一个实现无界分页的小模块由几个接口和类组成它的单元测试就是针对这一组类的。 Mock的原理是采用字节码生成的方式对要mock的类进行sub-class然后生成这个子类的对象以此达到对其行为进行订制的目的。显然这种方式会受到Java继承机制的限制静态类/方法、final类/方法、构造器、私有方法等都不能继承因此就难以订制。 Mockito在2.1.0中加入了对final类/方法的支持。见 https://www.cnblogs.com/yuluoxingkong/p/14813558.html。 powermock提供的增强版MockitoPowerMockito 则对这些方面提供了全面的支持。