可以看出,在这种情况下,测试将执行两次,值3和7各一次。除了通过XML配置文件指定测试数据之外,还可以通过DataProvider注释在类中提供测试数据。
  JUnit
  与TestNG类似,测试数据也可以外部化用于JUnit。以下是与上述相同MathChecker类的JUnit测试用例:
  @RunWith(Parameterized.class)
      public class MathCheckerTest {
       private int inputNumber;
       private Boolean expected;
       private MathChecker mathChecker;
       @Before
       public void setup(){
           mathChecker = new MathChecker();
       }
          // Inject via constructor
          public MathCheckerTest(int inputNumber, Boolean expected) {
              this.inputNumber = inputNumber;
              this.expected = expected;
          }
          @Parameterized.Parameters
          public static Collection<Object[]> getTestData() {
              return Arrays.asList(new Object[][]{
                      {1, true},
                      {2, false},
                      {3, true},
                      {4, false},
                      {5, true}
              });
          }
          @Test
          public void testisOdd() {
              System.out.println("Running test for:"+inputNumber);
              assertEquals(mathChecker.isOdd(inputNumber), expected);
          }
      }
  可以看出,要对其执行测试的测试数据由getTestData()方法指定。此方法可以轻松地修改为从外部文件读取数据,而不是硬编码数据。
  5.使用断言而不是Print语句
  许多新手开发人员习惯于在每行代码之后编写System.out.println语句来验证代码是否正确执行。这种做法常常扩展到单元测试,从而导致测试代码变得杂乱。除了混乱,这需要开发人员手动干预去验证控制台上打印的输出,以检查测试是否成功运行。更好的方法是使用自动指示测试结果的断言。
  下面的StringUti类是一个简单类,有一个连接两个输入字符串并返回结果的方法:
  public class StringUtil {
          public String concat(String a,String b) {
              return a + b;
          }
      }
  以下是上述方法的两个单元测试:
  @Test
      public void testStringUtil_Bad() {
           String result = stringUtil.concat("Hello ", "World");
           System.out.println("Result is "+result);
      }
      @Test
      public void testStringUtil_Good() {
           String result = stringUtil.concat("Hello ", "World");
           assertEquals("Hello World", result);
      }
  testStringUtil\_Bad将始终传递,因为它没有断言。开发人员需要手动地在控制台验证测试的输出。如果方法返回错误的结果并且不需要开发人员干预,则testStringUtil\_Good将失败。
  6.构建具有确定性结果的测试
  一些方法不具有确定性结果,即该方法的输出不是预先知道的,并且每一次都可以改变。例如,考虑以下代码,它有一个复杂的函数和一个计算执行复杂函数所需时间(以毫秒为单位)的方法:
  public class DemoLogic {
      private void veryComplexFunction(){
          //This is a complex function that has a lot of database access and is time consuming
          //To demo this method, I am going to add a Thread.sleep for a random number of milliseconds
          try {
              int time = (int) (Math.random()*100);
              Thread.sleep(time);
          } catch (InterruptedException e) {
              // TODO Auto-generated catch block
              e.printStackTrace();
          }
      }
      public long calculateTime(){
          long time = 0;
          long before = System.currentTimeMillis();
          veryComplexFunction();
          long after = System.currentTimeMillis();
          time = after - before;
          return time;
      }
      }
  在这种情况下,每次执行calculateTime方法时,它将返回一个不同的值。为该方法编写测试用例不会有任何用处,因为该方法的输出是可变的。因此,测试方法将不能验证任何特定执行的输出。
  7.除了正面情景外,还要测试负面情景和边缘情况
  通常,开发人员会花费大量的时间和精力编写测试用例,以确保应用程序按预期工作。然而,测试负面测试用例也很重要。负面测试用例指的是测试系统是否可以处理无效数据的测试用例。例如,考虑一个简单的函数,它能读取长度为8的字母数字值,由用户键入。除了字母数字值,应测试以下负面测试用例:
  用户指定非字母数字值,如特殊字符。
  用户指定空值。
  用户指定大于或小于8个字符的值。
  类似地,边界测试用例测试系统是否适用于极端值。例如,如果用户希望输入从1到100的数字值,则1和100是边界值,对这些值进行测试系统是非常重要的。