4

当需要测试这个程序时,应该使用哪种方式更好。
首先askUserPathAndWord()要求用户输入pathwhatFind。我们有两个线程:

  • 第一个线程扫描文件夹,如果找到可读文件put(),则将其放入队列中。
  • take()队列中的第二个线程s 并whatFind在此文件中查找。如果搜索成功,它将输出到控制台该文件的路径和单词的频率。

这是与多线程工作的集成依赖。哪个变体能够更好地测试这个程序 - EasyMock 的 Junit?我阅读了一些关于 EasyMock 的教程,但我不知道在哪些情况下使用它更好。

代码:

class FolderScan implements Runnable {

    private String path;
    private BlockingQueue<File> queue;
    private CountDownLatch latch;
    private File endOfWorkFile;

    FolderScan(String path, BlockingQueue<File> queue, CountDownLatch latch,
            File endOfWorkFile) {
        this.path = path;
        this.queue = queue;
        this.latch = latch;
        this.endOfWorkFile = endOfWorkFile;
    }

    public FolderScan() {
    }

    @Override
    public void run() {
        findFiles(path);
        queue.add(endOfWorkFile);
        latch.countDown();
    }

    private void findFiles(String path) {

        try {
            File root = new File(path);
            File[] list = root.listFiles();
            for (File currentFile : list) {
                String s = currentFile.getName().toLowerCase();
                if (currentFile.isDirectory()) {
                    findFiles(currentFile.getAbsolutePath());
                } else {
                    if (s.matches("^.*?\\.(txt|pdf|doc|docx|html|htm|xml|djvu|rar|rtf)$")) {
                        queue.put(currentFile);
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

public class FileScan implements Runnable {

    private String whatFind;
    private BlockingQueue<File> queue;
    private CountDownLatch latch;
    private File endOfWorkFile;

    public FileScan(String whatFind, BlockingQueue<File> queue,
            CountDownLatch latch, File endOfWorkFile) {
        this.whatFind = whatFind;
        this.queue = queue;
        this.latch = latch;
        this.endOfWorkFile = endOfWorkFile;
    }

    public FileScan() {
    }

    @Override
    public void run() {

        while (true) {
            try {
                File file;
                file = queue.take();

                if (file == endOfWorkFile) {
                    break;
                }

                scan(file);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        latch.countDown();
    }

    private void scan(File file) {
        Scanner scanner = null;
        int matches = 0;

        try {
            scanner = new Scanner(file);
        } catch (FileNotFoundException e) {
            System.out.println("File Not Found.");
            e.printStackTrace();
        }

        while (scanner.hasNext())
            if (scanner.next().equals(whatFind)) {
                matches++;
            }

        if (matches > 0) {
            String myStr = String.format(
                    "File: %s - and the number of matches " + "is: %d",
                    file.getAbsolutePath(), matches);
            System.out.println(myStr);
        }
    }

    public void askUserPathAndWord() {

        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(System.in));
        String path;
        String whatFind;
        BlockingQueue<File> queue = new LinkedBlockingQueue<File>();

        try {
            System.out.println("Please, enter a Path and Word"
                    + "(which you want to find):");
            System.out.println("Please enter a Path:");
            path = bufferedReader.readLine();
            System.out.println("Please enter a Word:");
            whatFind = bufferedReader.readLine();

            if (path != null && whatFind != null) {

                File endOfWorkFile = new File("GameOver.tmp");
                CountDownLatch latch = new CountDownLatch(2);

                FolderScan folderScan = new FolderScan(path, queue, latch,
                        endOfWorkFile);
                FileScan fileScan = new FileScan(whatFind, queue, latch,
                        endOfWorkFile);

                Executor executor = Executors.newCachedThreadPool();
                executor.execute(folderScan);
                executor.execute(fileScan);

                latch.await();
                System.out.println("Thank you!");
            } else {
                System.out.println("You did not enter anything");
            }

        } catch (IOException | RuntimeException e) {
            System.out.println("Wrong input!");
            e.printStackTrace();
        } catch (InterruptedException e) {
            System.out.println("Interrupted.");
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {  
        new FileScan().askUserPathAndWord();
    }
}

问题:

  • 在这种情况下,哪种测试的覆盖率最好?
  • 如何更好地测试两种变体中的合同义务?
  • 如果答案是EasyMock,我们应该如何正确地做到这一点?
  • 如果Junit,我们如何测试void方法?
4

2 回答 2

2

JUnit 还是 EasyMock?- 答案是,两者都有!一个是单元测试框架,另一个允许模拟对象,这使您能够编写更好的测试。因此,使用 JUnit加上任何模拟框架(您建议EasyMock,我使用Mockito或有第三个称为jMock)是一个好主意。

哪种测试的覆盖率最好?- 模拟使您可以专注于要进行单元测试的代码区域,因此您最终会比以前测试更多的代码。模拟执行繁重操作的代码(如写入数据库或从文件系统读取 - 就像你的那样)特别有用。因此,将 EasyMock 与 JUnit 一起使用应该比单独使用 JUnit 提供更好的覆盖率(和更好的测试)。

如何更好地测试两种变体中的合同义务?- 确保您的所有公共方法都经过测试。然后,在每次测试结束时彻底断言并验证您的期望。

如果答案是 EasyMock,我们应该如何正确地做到这一点?- 使用该verify方法检查您的模拟对象在测试期间是否被正确调用。

如果是 Junit,我们如何测试 void 方法?- 您需要以其他方式断言结果符合预期。也许您将一个对象作为参数传递给 void 方法,或者也许还有另一种获取结果的方法(例如 getter)。

祝你好运!

于 2013-02-24T17:43:10.760 回答
2

当您考虑测试这个程序时,请记住单元测试是什么。来自Wikipedia,“单元测试是一种方法,通过该方法可以测试各个源代码单元......以确定它们是否适合使用”。

还请记住,您有点跳入深渊,因为测试多线程程序很困难。您应该尽可能地从功能测试中消除线程交互的复杂性。

看看你的程序,你已经有了很好的封装和关注点分离,所以你正在顺利进行。我的策略是*Scan独立于线程测试这两个对象。为此,请牢记他们的角色是什么。我会说以下内容:

  • FolderScan遍历目录结构,查找特定类型的文件,并将通过过滤器的文件放入队列。当它耗尽目录结构时,它将一个特定的文件放入队列中,倒计时一个锁存器,然后退出。
  • FileScan使用一个文件队列,对它们执行操作,并将输出打印到控制台。当它碰到一个特定的文件时,它会倒计时一个锁存器并退出。

由于您可能已经拥有或多或少的工作代码,因此当您对其进行改进测试时(而不是在编写代码时编写它们,这是可取的),您希望尽可能少地更改源代码以通过测试. 之后,您可能希望重构代码,您的测试将使您有信心这样做。

文件夹扫描测试

然后,作为第一步,为FolderScan. 您可以编写多个测试,但从高层次来看,它们中的每一个都应使用一些文件填充目录结构。我至少会测试这些案例中的每一个:

  1. 包含通过过滤器的文件的单个文件夹。
  2. 包含未通过过滤器的文件的单个文件夹。
  3. 一个嵌套文件夹,每个文件夹中都有一个文件。

测试越精细越好。每个测试都简单地创建一个新的实例FolderScan,给它你定义的指向给定文件夹的参数。调用 run(),并确保:

  1. 队列包含File您期望的对象。
  2. CountDownLatch已减少。
  3. 队列中的最后一个元素是 'trigger' File

文件扫描测试

在高层次上,现在应该明确对此的测试:创建一些文本File对象,用它们和标记填充队列,并将它们传递给新的 FileScan 对象。同样,越细越好,所以至少:

  1. 具有匹配字符串的文件
  2. 没有匹配字符串的文件
  3. 多个文件

但是这个类有一个问题,那就是经典的“如何测试单例”问题。这个对象的结果实际上是通过管道传送到的System.out,你必须以某种方式连接到它来检查结果。作为第一遍,我建议重构构造函数以传入PrintStream. 在生产中你会传递System.out,在测试中你会传递一些你可以检查的东西:new PrintStream(new ByteArrayOutputStream()) 你可以检查内容,或者更好的是,一个模拟。

最后,每个测试都应该检查:

  1. 队列被清空。
  2. CountDownLatch已减少
  3. 指定PrintStream的已写入预期内容。

然后,您应该对所宣传FileScan的作品和作品充满信心。FolderScan

* 测试 askUserPathAndWord *

我没有看到任何直接的方法来测试这个函数/类。它违反了单一职责原则,并且干了太多事情。我会将以下职责提取到新方法或类中:

  • 询问用户单词和路径
  • 给定一个词,FileScan用正确的参数创建一个(工厂)
  • 给定路径,FolderScan使用正确的参数创建一个(工厂)
  • 给定两个runnables和一个latch,创建一个ExecutorService,将它们排队,然后等待latch

然后你可以独立地测试它们。

* 下一步 *

拥有这些测试的好处之一是,一旦你拥有它们,你就可以自由地重构,并且确信你没有破坏任何东西。例如,您可以查看 toCallable而不是Runnable,它可以让您处理Future引用,从 中删除输出参数FolderScan,并CountDownLatch完全删除。

祝测试愉快!

于 2013-02-26T15:55:59.610 回答