28 - 重构可靠性保障 - 单元测试

2021-08-29  本文已影响0人  舍是境界

很多程序员对重构这种做法还是非常认同的,面对项目中的烂代码,也想重构一下,但又担心重构之后出问题,出力不讨好。确实,如果你要重构的代码是别的同事开发的,你不是特别熟悉,在没有任何保障的情况下,重构引入 bug 的风险还是很大的。

那如何保证重构不出错呢?你需要熟练掌握各种设计原则、思想、模式,还需要对所重构的业务和代码有足够的了解。除了这些个人能力因素之外,最可落地执行、最有效的保证重构不出错的手段应该就是单元测试(Unit Testing)了。当重构完成之后,如果新的代码仍然能通过单元测试,那就说明代码原有逻辑的正确性未被破坏,原有的外部可见行为未变。

什么是单元测试?

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;
  }
}
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());
  }
}

为什么要写单元测试?

  1. 单元测试能有效地帮你发现代码中的 bug
  1. 写单元测试能帮你发现代码设计上的问题
  1. 单元测试是对集成测试的有力补充
  1. 写单元测试的过程本身就是代码重构的过程
  1. 阅读单元测试能帮助你快速熟悉代码
  1. 单元测试是 TDD 可落地执行的改进方案

如何编写单元测试?

import org.junit.Assert;
import org.junit.Test;
public class TextTest {
  @Test
  public void testToNumber() {
    Text text = new Text("123");
    Assert.assertEquals(new Integer(123), text.toNumber());
  }
  @Test
  public void testToNumber_nullorEmpty() {
    Text text1 = new Text(null);
    Assert.assertNull(text1.toNumber());
    Text text2 = new Text("");
    Assert.assertNull(text2.toNumber());
  }
  @Test
  public void testToNumber_containsLeadingAndTrailingSpaces() {
    Text text1 = new Text(" 123");
    Assert.assertEquals(new Integer(123), text1.toNumber());
    Text text2 = new Text("123 ");
    Assert.assertEquals(new Integer(123), text2.toNumber());
    Text text3 = new Text(" 123 ");
    Assert.assertEquals(new Integer(123), text3.toNumber());
  }
  @Test
  public void testToNumber_containsMultiLeadingAndTrailingSpaces() {
    Text text1 = new Text("  123");
    Assert.assertEquals(new Integer(123), text1.toNumber());
    Text text2 = new Text("123  ");
    Assert.assertEquals(new Integer(123), text2.toNumber());
    Text text3 = new Text("  123  ");
    Assert.assertEquals(new Integer(123), text3.toNumber());
  }
  @Test
  public void testToNumber_containsInvalidCharaters() {
    Text text1 = new Text("123a4");
    Assert.assertNull(text1.toNumber());
    Text text2 = new Text("123 4");
    Assert.assertNull(text2.toNumber());
  }
}
  1. 写单元测试真的是件很耗时的事情吗?
  1. 对单元测试的代码质量有什么要求吗?
  1. 单元测试只要覆盖率高就够了吗?
  1. 写单元测试需要了解代码的实现逻辑吗?
  1. 如何选择单元测试框架?

单元测试为何难落地执行?

小结

上一篇下一篇

猜你喜欢

热点阅读