测试是开发的一个非常重要的方面,可以在很大程度上决定一个应用程序的命运。良好的测试可以在早期捕获导致应用程序崩溃的问题,但较差的测试往往总是导致故障和停机。
  虽然有三种主要类型的软件测试:单元测试,功能测试和集成测试,但是在这篇博文中,我们将讨论开发人员级单元测试。在我深入讲述具体细节之前,让我们先来回顾一下这三种测试的详细内容。
  软件开发测试的类型
  单元测试用于测试各个代码组件,并确保代码按照预期的方式工作。单元测试由开发人员编写和执行。大多数情况下,使用JUnit或TestNG之类的测试框架。测试用例通常是在方法级别写入并通过自动化执行。
  集成测试检查系统是否作为一个整体而工作。集成测试也由开发人员完成,但不是测试单个组件,而是旨在跨组件测试。系统由许多单独的组件组成,如代码,数据库,Web服务器等。集成测试能够发现如组件布线,网络访问,数据库问题等问题。
  功能测试通过将给定输入的结果与规范进行比较来检查每个功能是否正确实现。通常,这不是在开发人员级别的。功能测试由单独的测试团队执行。测试用例基于规范编写,并且实际结果与预期结果进行比较。有若干工具可用于自动化的功能测试,如SeleniumQTP
  如前所述,单元测试可帮助开发人员确定代码是否正常工作。在这篇博文中,我将提供在Java中单元测试的有用提示。
  1.使用框架来用于单元测试
  Java提供了若干用于单元测试的框架。TestNG和JUnit是流行的测试框架。JUnit和TestNG的一些重要功能:
  ●易于设置和运行。
  ●支持注释。
  ●允许忽略或分组并一起执行某些测试。
  ●支持参数化测试,即通过在运行时指定不同的值来运行单元测试。
  ●通过与构建工具,如Ant,Maven和Gradle集成来支持自动化的测试执行。
  EasyMock是一个模拟框架,是单元测试框架,如JUnit和TestNG的补充。EasyMock本身不是一个完整的框架。它只是添加了创建模拟对象以便于测试的能力。例如,我们想要测试的一个方法可以调用从数据库获取数据的DAO类。在这种情况下,EasyMock可用于创建返回硬编码数据的MockDAO。这使我们能够轻松地测试我们意向的方法,而不必担心数据库访问。
  2.谨慎使用测试驱动开发!
  测试驱动开发(TDD)是一个软件开发过程,在这过程中,在开始任何编码之前,我们基于需求来编写测试。由于还没有编码,测试初会失败。然后写入小量的代码以通过测试。然后重构代码,直到被优化。
  目标是编写覆盖所有需求的测试,而不是一开始写代码,却可能甚至都不能满足需求。TDD是伟大的,因为它导致简单的模块化代码,且易于维护。总体开发速度加快,容易发现缺陷。此外,单元测试被创建作为TDD方法的副产品。
  然而,TDD可能不适合所有的情况。在设计复杂的项目中,专注于简单的设计以便于通过测试用例,而不提前思考可能会导致巨大的代码更改。此外,TDD方法难以用于与遗留系统,GUI应用程序或与数据库一起工作的应用程序交互的系统。另外,测试需要随着代码的改变而更新。
  因此,在决定采用TDD方法之前,应考虑上述因素,并应根据项目的性质采取措施。
  3.测量代码覆盖率
  代码覆盖率衡量(以百分比表示)了在运行单元测试时执行的代码量。通常,高覆盖率的代码包含未检测到的错误的几率要低,因为其更多的源代码在测试过程中被执行。测量代码覆盖率的一些佳做法包括:
  ●使用代码覆盖工具,如Clover,Corbetura,JaCoCo或Sonar。使用工具可以提高测试质量,因为这些工具可以指出未经测试的代码区域,让你能够开发开发额外的测试来覆盖这些领域。
  ●每当写入新功能时,立即写新的测试覆盖。
  ●确保有测试用例覆盖代码的所有分支,即if / else语句。
  高代码覆盖不能保证测试是完美的,所以要小心!
  下面的concat方法接受布尔值作为输入,并且仅当布尔值为true时附加传递两个字符串:
  public String concat(boolean append, String a,String b) {
          String result = null;
          If (append) {
              result = a + b;
                              }
          return result.toLowerCase();
  }
  以下是上述方法的测试用例:
 @Test
  public void testStringUtil() {
       String result = stringUtil.concat(true, "Hello ", "World");
       System.out.println("Result is "+result);
  }
  在这种情况下,执行测试的值为true。当测试执行时,它将通过。当代码覆盖率工具运行时,它将显示的代码覆盖率,因为concat方法中的所有代码都被执行。但是,如果测试执行的值为false,则将抛出NullPointerException。所以的代码覆盖率并不真正表明测试覆盖了所有场景,也不能说明测试良好。
  4.尽可能将测试数据外部化
  在JUnit4之前,测试用例要运行的数据必须硬编码到测试用例中。这导致了限制,为了使用不同的数据运行测试,测试用例代码必须修改。但是,JUnit4以及TestNG支持外部化测试数据,以便可以针对不同的数据集运行测试用例,而无需更改源代码。
  下面的MathChecker类有方法可以检查一个数字是否是奇数:
 public class MathChecker {
          public Boolean isOdd(int n) {
              if (n%2 != 0) {
                  return true;
              } else {
                  return false;
              }
          }
      }
  以下是MathChecker类的TestNG测试用例:
  public class MathCheckerTest {
          private MathChecker checker;
          @BeforeMethod
          public void beforeMethod() {
            checker = new MathChecker();
          }
          @Test
          @Parameters("num")
          public void isOdd(int num) {
            System.out.println("Running test for "+num);
            Boolean result = checker.isOdd(num);
            Assert.assertEquals(result, new Boolean(true));
          }
      }
  TestNG
  以下是testng.xml(用于TestNG的配置文件),它具有要为其执行测试的数据:
  <?xml version="1.0" encoding="UTF-8"?>
      <suite name="ParameterExampleSuite" parallel="false">
      <test name="MathCheckerTest">
      <classes>
        <parameter name="num" value="3"></parameter>
        <class name="com.stormpath.demo.MathCheckerTest"/>
      </classes>
       </test>
       <test name="MathCheckerTest1">
      <classes>
        <parameter name="num" value="7"></parameter>
        <class name="com.stormpath.demo.MathCheckerTest"/>
      </classes>
       </test>
      </suite>