繁体   English   中英

单元测试线程应用程序

[英]Unit testing a Threaded Application

我正在考虑如何使用Mockito为此编写测试用例。

例如,在我的主线程中,部分逻辑是创建一个执行3件事的线程。 请在下面查看我的注释代码。

现在,根据来自主程序的输入数量,可以多次生成RequestThread。

public class MainThreads {
    public static void main(String[] args) {
        RequestThread rt = new RequestThread("sample");
        rt.start();

        //RequestThread another = new RequestThread("sample-2");
        //another.start();

        //RequestThread newThread = new RequestThread("sample-3");
        //newThread.start();
    }

    public static class RequestThread implements Runnable{
        private final String request;

        public RequestThread(String request) {
            this.request = request;
        }

        @Override
        public void run() {
            //1. Instantiate a service passing the required request parameter
            MyDataWebService service = new MyDataWebService(request);

            //2. Get the returned data
            List<String> dataList = service.requestData();

            //3. Write to file
            Path file = Paths.get("/someDir/" + request);
            Files.write(file, dataList, Charset.forName("UTF-8"));
        }

    }
}

我的问题是,我不知道如何为线程类正确编写JUnit / Mockito测试。 一般而言,我对Mockito和JUnit不太熟悉,所以我正在寻找一种对线程化应用程序进行单元测试的方法。

有人可以指导我如何对这种东西进行单元测试吗?

您需要对代码进行一些更改,以使其更易于测试。 尤其是:

  • 您要模拟的对象应实现一个接口
  • 不要实例化要模拟的对象以测试功能

这是对类的重写,因此您可以模拟MyDataWebService并测试RequestThread 根据此示例,您将更容易为MainThreads类编写完整的测试。

public class MainThreads {
    public static void main(String[] args) {
        RequestThread rt = new RequestThread("sample");
        rt.start();

        //RequestThread another = new RequestThread("sample-2");
        //another.start();

        //RequestThread newThread = new RequestThread("sample-3");
        //newThread.start();
    }

    public static class RequestThread extends Thread {
        private final String request;
        // One important thing to note here, "service" has to be non-final. Else mockito won't be able to inject the mock.
        private MyDataWebServiceInterface service;

        public RequestThread(String request) {
            this.request = request;
            //1. Instantiate a service passing the required request parameter
            // => do it in constructor, or passed as parameter, but NOT in the function to test
            service = new MyDataWebService(request);
        }

        @Override
        public void run() {
            //2. Get the returned data
            List<String> dataList = service.requestData();

            //3. Write to file
            Path file = Paths.get("someDir/" + request);
            try {
                Files.write(file, dataList, Charset.forName("UTF-8"));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

MyDataWebService的接口和实现:

interface MyDataWebServiceInterface {
    List<String> requestData();
}

class MyDataWebService implements MyDataWebServiceInterface {
    public MyDataWebService(String request) {
    }

    @Override
    public List<String> requestData() {
        return Arrays.asList("foo", "bar");
    }
}

并使用mockito进行测试。 注意,对现有文件和线程休眠的检查可能不是此处要做的最优雅的事情。 如果您有能力在RequestThread添加一些标记来指示数据已被写入,那么它肯定会使测试变得更好和更安全(文件系统I / O有时很难测试)。

@RunWith(MockitoJUnitRunner.class)
public class RequestThreadTest {

    private static final Path FILE = Paths.get("someDir", "sample");

    @Mock
    MyDataWebServiceInterface service;

    @InjectMocks
    MainThreads.RequestThread reqThread = new MainThreads.RequestThread("sample");

    @Before
    public void setup() throws IOException, InterruptedException {
        if (Files.exists(FILE)) {
            Files.delete(FILE);
            while (Files.exists(FILE)) {
                Thread.sleep(50);
            }
        }
    }

    @Test
    public void shouldWriteFile() throws InterruptedException {
        Mockito.when(service.requestData()).thenReturn(Arrays.asList("one", "two"));
        reqThread.start();
        while (!Files.exists(FILE)) {
            Thread.sleep(50);
        }
        // HERE run assertions about file content
    }
}

现在,测试异步代码通常比同步更为复杂,因为您经常会遇到不确定性的行为,计时问题等。您可能想在测试中设置超时时间,但请记住:持续集成工具(詹金斯,travis等)。通常会比您的计算机运行慢,这是导致问题的常见原因,因此请不要设置得太紧。 据我所知,还没有针对非确定性问题的“万事通”解决方案。

马丁·福勒(Martin Fowler)撰写了一篇关于非确定性测试的出色文章: https//martinfowler.com/articles/nonDeterminism.html

一个独特的非答案:在2018年,您不再使用“原始”线程。

Java现在提供了更好的抽象,例如ExecutorService 猜猜是什么:当您将代码提交任务到这样的服务中时,您可以使用同线程执行程序服务对其进行测试。

含义:通过使用这种抽象并将您的交付分解为特定的服务,您不仅可以(几乎)完全测试小型单元,而且还可以测试任务如何进入系统并进行工作。

换句话说:您可以对“任务”进行单元测试,然后在任务进入此类执行程序时对单元的集成进行“单元”测试。 然后,您只需要进行一些实际的功能/集成测试即可检查“真正的并行”解决方案的行为是否符合预期。

其他任何事情都会很快变得复杂。 在普通的单元测试中使用真实线程会导致行为不一致或运行时间增加(例如测试等待线程异步执行某项操作)。

如您的示例所示:您的测试只需坐在那里,并定期检查预期文件是否写入了预期内容。 导致:失败之前应该等待多长时间? 等待时间不够长意味着您的测试有时会失败,因为代码有时会花费更长的时间。 如果等待时间太长,则总计需要运行测试的总时间。 您不希望最终得到数百个单元测试,而有些则需要10、20秒,因为“等待其他线程”。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM