一、让我们开启单元测试之旅

小:一个测试几行代码(15)

精准:一个测试之测一个场景

隔离:每个测试都可以独立、重复运行,无耦合

快:每个测试都应该是毫秒级别的

频繁:应该频繁的执行,没增加、修改、删除一个测试都要运行一遍

那什么样的是好的单元测试呢?

自动化

可重复的

彻底的

独立的

专业的

好的测试用例

=====================================================================

测试用例应该短小精悍且快准狠。这些是对测试用例的函数本身而言的,但在实际项目中出问题往往就是某些情况没有考虑到导致程序出错的,我们在自测的时候往往会测试正常数据的情况然而却忽略的了错误情况和边界值的测试,这些才是校验一个项目的健壮性的标准。所以好的测试用例必定是有全面的测试数据。那怎样获取全面的测试数据呢?

在这之前需要知道哪些是好的测试数据

  • 最优可能抓住错误的

  • 不是重复的,多余的

  • 一组相似测试用例中最有效的

  • 既不是太简单,也不是太复杂

那怎样获取好的测试数据呢?有等价类划分法、边界值法、路径分析法。

等价类划分法


等价类划分法是把所有可能的输入数据,划分成若干个子集,然后从每个子集中选取少数的具有代表性的数据作为测试用例。

该方法是一种重要的、常用的黑盒测试用例设计方法。

有效等价类:对程序的规范说明是合理的,有意义的输入数据构成的集合。

无效等价类:对程序的规范说明不是合理的或者无意义的输入数据构成的集合。

我们来看一个例子:计算两个点距离的函数

public double getDistance(double x1, double y1, double x2, double y2)

在这里插入图片描述

边界值法


边界值分析法是对输入或者输出的边界值进行测试的一组黑盒测试方法。

通常情况下,边界值分析法是作为等价类划分法的补充,这种情况下,其测试用例来自等价类的边界。

比如上面一个例子中取边界值做为测试用例。

路径分析法


基本路径测试是一种白盒测试方法,它在程序控制图的基础上,通过分析程序的流程,构造导出基本可执行路径集合,从而设计测试用例的方法。

设计出的测试用例要保证在测试程序中的每一个可执行语句至少执行一次。

我们来看一个例子

在这里插入图片描述

可能的路径为:

1-2-3-4-5

1-2-3-4-6

1-2-4-5

1-2-4-6

断言

=================================================================

我们这里说的断言只是Junit断言,java 本身也有断言的,但是貌似我们使用的很少以至于我们都忘记了它的存在。

Junit 断言说是断言,其实也就是一份方法,没有什么语法。我们测试用例中使用断言,也就是使用这些方法来进行验证是否达到我们的预期。

方法有很多,大家可以看看源码,我这里给出几个常见的。

| 函数名 | 描述 |

| — | — |

| assertEquals | 判断实际产生的值与期望值是否相等 |

| assertNull | 判断对象是否为null |

| assertNotNull | 判断对象是否为非null |

| assertSame | 判断实际产生的对象与期望对象是否为同一个对象 |

| assertNotSame | 判断实际产生的对象与期望对象是否为不同的对象 |

| assertTrue | 判断bool变量是否为真 |

| assertFalse | 判断bool变量是否为假 |

| Fail | 使测试立即失败 |

上面这样说好像没有什么效果,我们先来看其中一个断言方法的源代码。我们就看第一个assertEquals 吧

在这里插入图片描述

可以看到有很多assertEquals方法。这样的方法的重载在底层很常见。我们来看下三个参数类似是Object的这个吧。

public static void assertEquals(String message, Object expected, Object actual) {

if (!equalsRegardingNull(expected, actual)) {

if (expected instanceof String && actual instanceof String) {

String cleanMessage = message == null ? “” : message;

throw new ComparisonFailure(cleanMessage, (String)expected, (String)actual);

} else {

failNotEquals(message, expected, actual);

}

}

}

private static boolean equalsRegardingNull(Object expected, Object actual) {

if (expected == null) {

return actual == null;

} else {

return isEquals(expected, actual);

}

}

private static boolean isEquals(Object expected, Object actual) {

return expected.equals(actual);

}

private static void failNotEquals(String message, Object expected, Object actual) {

fail(format(message, expected, actual));

}

static String format(String message, Object expected, Object actual) {

String formatted = “”;

if (message != null && !message.equals(“”)) {

formatted = message + " ";

}

String expectedString = String.valueOf(expected);

String actualString = String.valueOf(actual);

return expectedString.equals(actualString) ? formatted + "expected: " + formatClassAndValue(expected, expectedString) + " but was: " + formatClassAndValue(actual, actualString) : formatted + “expected:<” + expectedString + “> but was:<” + actualString + “>”;

}

equalsRegardingNull() 函数就是判断两个值是否相等,底层还是相当于用的object.equals()。如果两个值相等就断言通过,如果不相等就判断expected和actual是否是string类型,如果是直接将message输出。如果不是就failNotEquals().failNotEquals方法的源码我也贴出来了,可以看也很简单,就是message、expected、actual转换成string格式输出出来,并执行fail()使得测试失败。

从上面看断言也就不过如此(Junit 断言)。我们会使用常用的方法就可以写好测试用例啦,至于其他的方法,我们用到的时候可以直接其源代码,毕竟也不会很复杂。

简单案例

===================================================================

目标代码及功能说明


在这里插入图片描述

这段代码在项目中的作用是对特殊字段的对应的值进行处理并返回。

如果字段是包含time,那将值改成日期格式返回。

如果字段是包含iphone,那将值截取后11位返回。

其他情况,直接返回。

public class DataHandle {

public static final String REGEX_MOBILE = “^((13[0-9])|(15[0-9])|(17[0-9])|(18[0-9])|(19[0-9])|(14[0-9]))\d{8}$”;

public String fieldDataHandle(String key,String value){

//如果是时间类型,将时间戳转成时间

if(key.toLowerCase().contains(“ipone”)){ //如果手机号长于11位,截取后11位

if(value.length()>11){

value=value.substring(value.length()-11);

}

if(!isMobile(value)){

return null;

}

}else if(key.toLowerCase().contains(“time”)){

value=timeStampToDate(value,“yyyy-MM-dd HH:mm:ss”);

}

return value;

}

private static String timeStampToDate(String time,String timeFormat) {

Long timeLong = Long.parseLong(time);

SimpleDateFormat sdf = new SimpleDateFormat(timeFormat);//要转换的时间格式

Date date;

try {

date = sdf.parse(sdf.format(timeLong));

return sdf.format(date);

} catch (Exception e) {

return null;

}

}

private static boolean isMobile(String mobile) {

return Pattern.matches(REGEX_MOBILE, mobile);

}

}

单元测试设计


等价类设计

| 等价类划分 | 有效等价类 | 无效等价类 |

| — | — | — |

| key | 包含time, 包含ipone,包含time和ipone | 不包含time 和ipone |

| value | 时间戳,手机号,带区号的手机号 | 不是时间戳,也不是手机号 |

我们根据这个来设计测试用例

| key | value | 预期值 |

| — | — | — |

| 字段包含time | 时间戳 | 返回日期格式的的字符串 |

| 字段包含time | 不是时间戳 | null |

| 字段包含ipone | 不是手机号 | null |

| 字段包含ipone | 是11位的手机号 | 返回11位手机号字符串 |

| 字段包含ipone | 是手机号,但位数大于11位 | 返回11位手机号字符串 |

| 字段包含time,ipone | 时间戳 | 返回日期格式的的字符串 |

| 字段包含time,ipone | 不是手机号,也不是时间戳 | null |

| 字段包含time,ipone | 手机号 | null |

| 字段不包含time 和ipone | 时间戳 | 时间戳字符串 |

| 字段不包含time 和ipone | 11位手机号 | 手机号字符串 |

| 字段不包含time 和ipone | 大于11位手机号 | 返回值字符串 |

| 字段不包含time 和ipone | 不是手机号,也不是时间戳 | 值对应字符串 |

编写测试用例

public class DataHandleTest {

DataHandle dataHandle = null;

@Before

public void setup()

{

dataHandle = new DataHandle();

}

@After

public void tearDown()

{

dataHandle = null;

}

@Test

public void testFieldDataHandle_包含time是时间戳_返回日期字符串(){

assertEquals(“2019-09-10 19:02:30”, dataHandle.fieldDataHandle(“atime”,“1568113350000”));

}

@Test

public void testFieldDataHandle_包含time不是时间戳_返回NULL(){

assertNull(dataHandle.fieldDataHandle(“atime”,“1568113350aaa”));

}

@Test

public void testFieldDataHandle_包含ipone不是手机号_返回NULL(){

assertNull(dataHandle.fieldDataHandle(“bipone”,“aaa”));

}

@Test

public void testFieldDataHandle_包含ipone是11位手机号_返回手机号字符串(){

assertEquals(“13265459362”,dataHandle.fieldDataHandle(“bipone”,“13265459362”));

}

@Test

public void testFieldDataHandle_包含ipone是大于11位手机号_返回手机号字符串(){

assertEquals(“13265459362”,dataHandle.fieldDataHandle(“bipone”,“+8613265459362”));

}

@Test

public void testFieldDataHandle_包含time和ipone是时间戳_返回NULL(){

assertNull(dataHandle.fieldDataHandle(“atimebipone”,“1568168656000”));

}

@Test

public void testFieldDataHandle_包含time和ipone是手机号_返回手机号字符串(){

assertEquals(“13265459362”,dataHandle.fieldDataHandle(“atimebipone”,“13265459362”));

}

@Test

public void testFieldDataHandle_包含time和ipone不是时间戳手机号_返回NULL(){

assertNull(dataHandle.fieldDataHandle(“atimebipone”,“aaabbb”));

}

@Test

public void testFieldDataHandle_不包含time和ipone是时间戳_返回时间戳字符串(){

img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

andle_包含time和ipone是手机号_返回手机号字符串(){

assertEquals(“13265459362”,dataHandle.fieldDataHandle(“atimebipone”,“13265459362”));

}

@Test

public void testFieldDataHandle_包含time和ipone不是时间戳手机号_返回NULL(){

assertNull(dataHandle.fieldDataHandle(“atimebipone”,“aaabbb”));

}

@Test

public void testFieldDataHandle_不包含time和ipone是时间戳_返回时间戳字符串(){

[外链图片转存中…(img-KFHu8ybd-1719247656470)]
[外链图片转存中…(img-gPFCGSG0-1719247656470)]
[外链图片转存中…(img-doTOkcBt-1719247656471)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上软件测试知识点,真正体系化!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值