简单聊下什么是单元测试?

前言

那如何保证重构不出错呢?你需要熟练掌握各种设计原则、思想、模式,还需要对所重构的业务和代码有足够的了解。除了这些个人能力因素之外,最可落地执行、最有效的保证重构不出错的手段应该就是单元测试(Unit Testing)了。单元测是Java开发必备的技能,这也是属于Java基础知识之一了。

当重构完成之后,如果新的代码仍然能通过单元测试,那就说明代码原有逻辑的正确性未被破坏,原有的外部可见行为未变。

什么是单元测试?

单元测试由研发工程师自己来编写,用来测试自己写的代码的正确性。我们常常将它跟集成测试放到一块来对比。单元测试相对于集成测试(Integration Testing)来说,测试的粒度更小一些。集成测试的测试对象是整个系统或者某个功能模块,比如测试用户注册、登录功能是否正常,是一种端到端(end to end)的测试。而单元测试的测试对象是类或者函数,用来测试一个类和函数是否都按照预期的逻辑执行。这是代码层级的测试。

这么说比较理论,我举个例子来解释一下。

public class Text {

private String content;

public Text(String content) {

this.content = content;

}

/**

* 将字符串转化成数字,忽略字符串中的首尾空格;

* 如果字符串中包含除首尾空格之外的非数字字符,则返回 null。

*/

public Integer toNumber() {

if (content == null || content.isEmpty()) {

  return null;

}

//... 省略代码实现...

return null;

}

}
如果我们要测试 Text 类中的 toNumber () 函数的正确性,应该如何编写单元测试呢?

实际上,写单元测试本身不需要什么高深技术。它更多的是考验程序员思维的缜密程度,看能否设计出覆盖各种正常及异常情况的测试用例,来保证代码在任何预期或非预期的情况下都能正确运行。

为了保证测试的全面性,针对 toNumber () 函数,我们需要设计下面这样几个测试用例。

如果字符串只包含数字:“123”,toNumber () 函数输出对应的整数:123。

如果字符串是空或者 null,toNumber () 函数返回:null。

如果字符串包含首尾空格:“123”,“123 ”,“ 123 ”,toNumber () 返回对应的整数:123。

如果字符串包含多个首尾空格:“123”,toNumber () 返回对应的整数:123;

如果字符串包含非数字字符:“123a4”,“123 4”,toNumber () 返回 null;

当我们设计好测试用例之后,剩下的就是将其翻译成代码了。翻译成代码的过程非常简单,我把代码贴在下面了,你可以参考一下(注意,我们这里没有使用任何测试框架)。

public class Assert {

public static void assertEquals(Integer expectedValue, Integer actualValue) {

if (actualValue != expectedValue) {

  String message = String.format(

          "Test failed, expected: %d, actual: %d.", expectedValue, actualValue);

  System.out.println(message);

} else {

  System.out.println("Test succeeded.");

}

}

public static boolean assertNull(Integer actualValue) {

boolean isNull = actualValue == null;

if (isNull) {

  System.out.println("Test succeeded.");

} else {

  System.out.println("Test failed, the value is not null:" + actualValue);

}

return isNull;

}

}

public class TestCaseRunner {

public static void main(String[] args) {

System.out.println("Run testToNumber()");

new TextTest().testToNumber();

System.out.println("Run testToNumber_nullorEmpty()");

new TextTest().testToNumber_nullorEmpty();

System.out.println("Run testToNumber_containsLeadingAndTrailingSpaces()");

new TextTest().testToNumber_containsLeadingAndTrailingSpaces();

System.out.println("Run testToNumber_containsMultiLeadingAndTrailingSpaces()");

new TextTest().testToNumber_containsMultiLeadingAndTrailingSpaces();

System.out.println("Run testToNumber_containsInvalidCharaters()");

new TextTest().testToNumber_containsInvalidCharaters();

}

}

public class TextTest {

public void testToNumber() {

Text text = new Text("123");

Assert.assertEquals(123, text.toNumber());

}

public void testToNumber_nullorEmpty() {

Text text1 = new Text(null);

Assert.assertNull(text1.toNumber());

Text text2 = new Text("");

Assert.assertNull(text2.toNumber());

}

public void testToNumber_containsLeadingAndTrailingSpaces() {

Text text1 = new Text(" 123");

Assert.assertEquals(123, text1.toNumber());

Text text2 = new Text("123 ");

Assert.assertEquals(123, text2.toNumber());

Text text3 = new Text(" 123 ");

Assert.assertEquals(123, text3.toNumber());

}

public void testToNumber_containsMultiLeadingAndTrailingSpaces() {

Text text1 = new Text("  123");

Assert.assertEquals(123, text1.toNumber());

Text text2 = new Text("123  ");

Assert.assertEquals(123, text2.toNumber());

Text text3 = new Text("  123  ");

Assert.assertEquals(123, text3.toNumber());

}

public void testToNumber_containsInvalidCharaters() {

Text text1 = new Text("123a4");

Assert.assertNull(text1.toNumber());

Text text2 = new Text("123 4");

Assert.assertNull(text2.toNumber());

}

}

重点回顾

单元测试是代码层面的测试,由研发自己来编写,用于测试 “自己” 编写的代码的逻辑的正确性。单元测试顾名思义是测试一个 “单元”,有别于集成测试,这个 “单元” 一般是类或函数,而不是模块或者系统。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注

关注我们