单元测试执行器可调用

时间:2018-07-30 04:19:28

标签: java unit-testing junit mockito

我有一个像这样的控制器类,它将一些执行程序传递给可运行实例。 这不是它的工作方式,只是为了简单起见,我这样做是这样的。

package com.test.executor;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Controller {
    private ExecutorService executor;

    public Controller() {
        executor = Executors.newCachedThreadPool();
    }

    public void doRun() {
        MyRunner runner = new MyRunner(executor);
        Thread myRunner = new Thread(runner);
        myRunner.start();
    }

    public static void main(String[] args) {
        new Controller().doRun();
    }
}

运行程序接收执行程序的实例,然后传递某些可调用对象。 现在,可调用项有些不同,因为某些可调用项访问数据库/调用某些Web服务/文件系统

在这种情况下,我对如何编写适当的JUnit有一些问题。 我想尽可能多地覆盖代码。

package com.test.executor;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class MyRunner implements Runnable {

    private ExecutorService executor;

    public MyRunner(ExecutorService executor) {
        this.executor = executor;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        Future<Boolean> ret1 = executor.submit(new SampleCall1());

        try {
            ret1.get(5, TimeUnit.MINUTES);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }

        // Do other things

        Future<List<String>> ret2 = executor.submit(new SampleCall2());

        try {
            ret2.get(5, TimeUnit.MINUTES);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }

        // Do other things

        Future<Long> ret3 = executor.submit(new SampleCall3());

        try {
            ret3.get(5, TimeUnit.MINUTES);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }
    }

    public static class SampleCall1 implements Callable<Boolean> {

        @Override
        public Boolean call() throws Exception {
            // Sample Return Only
            // This will call JSON web service
            return true;
        }

    }

    public static class SampleCall2 implements Callable<List<String>> {

        @Override
        public List<String> call() throws Exception {
            // Sample Return Only
            // This will call Database
            return Collections.EMPTY_LIST;
        }

    }

    public static class SampleCall3 implements Callable<Long> {

        @Override
        public Long call() throws Exception {
            // Sample Return Only
            // This will access some file system
            return 1L;
        }

    }

}

我的问题是为此编写单元测试的正确方法是什么?我正在收集有关如何对该课程进行单元测试的一些建议? 我不确定在junit / mockito实例中该模拟什么。 我应该嘲笑每个可调用对象吗?然后为MyRunner创建一个测试用例。

我担心依赖关系。因为我正在连接数据库/ Web服务/文件系统,所以我想请教一些建议。

更新2

package com.test.executor;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class MyRunner implements Runnable {

    private ExecutorService executor;

    public MyRunner(ExecutorService executor) {
        this.executor = executor;
    }

    @Override
    public void run() {
        executeTasks1();
        // Do other things

        executeTasks2();
        // Do other things


        executeTasks3();

    }

    public boolean executeTasks1(){
        // TODO Auto-generated method stub
        Future<Boolean> ret1 = executor.submit(new SampleCall1());

        try {
            return ret1.get(5, TimeUnit.MINUTES);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }

    }

    public List<String> executeTasks2(){
        Future<List<String>> ret2 = executor.submit(new SampleCall2());

        try {
            return ret2.get(5, TimeUnit.MINUTES);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }
    }

    public Long executeTasks3(){
        Future<Long> ret3 = executor.submit(new SampleCall3());

        try {
            return ret3.get(5, TimeUnit.MINUTES);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }
    }

    public static class SampleCall1 implements Callable<Boolean> {

        @Override
        public Boolean call() throws Exception {
            // Sample Return Only
            // This will call JSON web service
            return true;
        }

    }

    public static class SampleCall2 implements Callable<List<String>> {

        @Override
        public List<String> call() throws Exception {
            // Sample Return Only
            // This will call Database
            return Collections.EMPTY_LIST;
        }

    }

    public static class SampleCall3 implements Callable<Long> {

        @Override
        public Long call() throws Exception {
            // Sample Return Only
            // This will access some file system
            return 1L;
        }

    }

}

2 个答案:

答案 0 :(得分:1)

不要在代码覆盖上花费太多时间。以某种方式务实。测试什么值得测试。提高测试质量,而不是数量。关于度量标准的一些旁注:100%的线路覆盖率不如高的分支覆盖率重要,而获得高的分支覆盖率可能会花费您很多时间,甚至如此:您可能不需要走每一个可能的路线。 PIT (mutation testing)是一个有用的“工具”,它通过更改被测代码来检查您的测试的实际效果。但是,请将该信息用作指导,而不是作为实施更多测试的措施。不要夸张! (也许,如果您正在开发一个库,您不想再进行经常更改,或者您不想让它变得坚如磐​​石(并且您有空余时间),可以,但是否则我不会)< / p>

一段时间以来,我非常准确地测试了所有内容。问题是:一旦开始进行更改(例如:今天我们需要X,明天是Y),您的许多测试就会失败。然后需要大量的返工。如果您测试最合适的产品,那么只要需求发生重大变化,您就可以专注于重要的产品,这将花费更少的时间。

看看您提供的代码,我可能会为每个Callable实现编写一个测试(类),以确保确保它们可以执行我想要的操作(这可能会导致提取{{ 1}}类作为副作用)。对于控制者和跑步者,我还不确定……Callable对我来说似乎已经过时了……但也许不是。所以大概我只会测试控制器...

关于模拟:我尽可能地开始省略模拟。大多数时候,我还添加集成测试,并且我想知道系统如何整体运作以及它是否按预期工作。我已经看到了很多测试,其中有很多模拟,通过更改任何代码,即使单元测试在我看来也没有失败。我还看到很多单元测试,实际上单元测试看起来像集成测试,但是到处都是模拟。模拟游戏首先有什么帮助?单独设置模拟可能花费了太多时间。但这又是我的看法。因此,即使许多人喜欢测试金字塔具有广泛的单元测试基础,但我认为它更为实用,并将一些测试移至集成测试“层”,而不是使用大量的模拟。最后,这也是可用性和速度的问题。您希望测试能够给出快速的结果,但您仍然希望结果具有最大的价值(模拟仅部分给出结果)。

答案 1 :(得分:0)

编写庞大的MyRunnableTest测试类与编写庞大的MyRunnable生产类一样具有代码味。由于您的每个Callable都是不同的,并且访问不同的I / O资源,因此您要分别测试它们。实际方法例如应根据具体情况选择文件系统操作的单元测试或与嵌入式H2数据库的集成测试。

提取Callable应该会给您一个较小的MyRunnable类。现在,您可以将run()拆分为较小的方法或类,然后分别进行测试。此时,您可以模拟ExecutorService

您应该在创建了实际对象的ExecutorService类的测试中测试Controller对象。

覆盖率只是一个可帮助您衡量测试质量的工具。高覆盖率本身并不是目标。如果您将它发挥到极致,那么您很容易拥有100%的代码覆盖率,而在测试中没有一个断言。

您可以应用其他技术,例如Test Driven Development和工具,例如PIT mutation testing来改善测试。但是,您应该首先使生产代码易于测试。