编写一个简单的 JUnit 测试来介绍软件 QA class?

Writing a simple JUnit test for an intro to software QA class?

我的任务是编写一个小程序以及 JUnit 和覆盖率测试,我在理解如何编写 JUnit 方面遇到了一些问题。

我以前为其他 class 项目编写过 JUnit 测试,在更有知识的队友的帮助下。它们都是针对 class 的,我们可以实例化、使用然后检查(使用 assert...())。

我们被告知要编写的程序是一个简单的控制台应用程序,它接收一些值,计算一些东西,然后 returns。下面是我写的,它只是在某个范围内输入可变数量的数字,并计算这些值的最小值、最大值和平均值。

我遇到的问题是,根据我之前的编写方式,我不确定如何为此编写 JUnit。我不确定如何 "falsify" 用户在单元测试中向控制台输入。

我正在寻求有关如何为该软件编写 JUnit 测试的帮助。

package softqa_assignment2;

import java.io.Console;
import java.util.LinkedList;

public class SoftQA_Assignment2 
{
    public static void main(String[] args) 
    {
        Console cons = System.console();
        if (cons == null) 
        {
            System.err.println("System could not provide console, exiting");
            System.exit(1);
        }

        System.out.println("--------------------");
        System.out.println("Welcome, this program computes the minimum, maximum, and average of 10-20 numbers.");
        System.out.println("Input \"Q\" at anytime to quit.");
        System.out.println("--------------------");

        int numVals = 0;
        boolean gotNumVals = false;
        while(!gotNumVals)
        {
            String numValsStr = cons.readLine("Enter number of numbers (10-20): ");
            if(numValsStr.equals("Q"))
            {
                System.err.println("Encountered \"Q\", quiting program...");
                System.exit(1);
            }

            try
            {
                numVals = Integer.parseInt(numValsStr);
            }
            catch(NumberFormatException e)
            {
                System.out.println("Please input a integer value between 10 and 20.");
                continue;
            }

            if(numVals < 10 || numVals > 20)
            {
                System.out.println("Please input a integer value between 10 and 20.");
                continue;
            }
            else
                gotNumVals = true;
        }

        System.out.println("--------------------");
        int valsGot = 0;
        LinkedList<Double> vals = new LinkedList<>();
        while(valsGot < numVals)
        {
            String numValueStr = cons.readLine((valsGot + 1) + ": Enter a number: ");
            if(numValueStr.equals("Q"))
            {
                System.err.println("Encountered \"Q\", quiting program...");
                System.exit(1);
            }

            double numToAdd = 0;
            try
            {
                numToAdd = Double.parseDouble(numValueStr);
            }
            catch(NumberFormatException e)
            {
                System.out.println("Please input a proper value.");
                continue;
            }

            vals.add(numToAdd);
            valsGot++;
        }

        System.out.println("--------------------");
        System.out.println("Done getting numbers, computing output...");
        System.out.println("--------------------");

        double min = Double.POSITIVE_INFINITY;
        double max = Double.NEGATIVE_INFINITY;
        double avg = 0.0;
        for(int i = 0; i < vals.size(); i++)
        {
            avg += vals.get(i);
            if(vals.get(i) < min)
                min = vals.get(i);
            if(vals.get(i) > max)
                max = vals.get(i);
        }
        avg /= vals.size();

        System.out.println("Minimum: " + min + "\n Maximum: " + max + "\n Average: " + avg);
        System.out.println("--------------------");
    }
} 

你可以有一个布尔变量,假设条件 = false。

现在,如果程序能够正确 运行 而没有任何错误,那么在您计算出 min/max/average 之后,您可以将其设置为 true。然后用这个断言。这个断言可以在 try 和 catch 块之后的 finally 块中进行。如果出现任何错误,将调用 catch 并且不应将条件设置为 true。因此断言应该 return 否定。

如果您已经知道用户将输入的值,则宁可使用正确的 min/average 值进行断言。

您的第一步是 refactor 您的代码变成小的、可重用的、有目的的方法。例如,检查 main 方法末尾附近的这段代码:

double min = Double.POSITIVE_INFINITY;
double max = Double.NEGATIVE_INFINITY;
double avg = 0.0;
for(int i = 0; i < vals.size(); i++)
{
    avg += vals.get(i);
    if(vals.get(i) < min)
        min = vals.get(i);
    if(vals.get(i) > max)
        max = vals.get(i);
}
avg /= vals.size();

可以重构为:

Stats stats = getStatsFromVals(vals);

新方法定义为:

static Stats getStatsFromVals(List<Double> vals) {
  double min = Double.POSITIVE_INFINITY;
  double max = Double.NEGATIVE_INFINITY;
  double avg = 0.0;
  for(double val : vals) {
    avg += val;
    if(val < min)
      min = val;
    if(val > max)
      max = val;
  }
  avg /= vals.size();
  return new Stats(min, max, avg);
}

其中 Stats 将是您定义的 static nested class 作为一个值 class,它包含您正在计算的所有统计值。

现在你有了一个在逻辑上和物理上都是一个单元的方法,使其成为单元测试的主要候选者。

@Test
public static void testGetStats() {
  List<Double> testList = Arrays.asList("1.0, 2.0, 3.0");
  Stats stats = MainClass.getStatsFromVals(testList);
  assertEquals(1.0, stats.min);
  assertEquals(3.0, stats.max);      
  assertEquals(2.0, Math.rint(stats.avg)); // risky FP math
}

正如评论中提到的,关键是让每个方法都将其参数作为输入,这样您就可以将控制台输入与应用程序逻辑分开。