首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >单元测试线程应用程序

单元测试线程应用程序
EN

Stack Overflow用户
提问于 2018-07-25 04:31:46
回答 2查看 274关注 0票数 0

我正在思考如何使用mockito来编写测试用例。

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

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

代码语言:javascript
复制
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上没有那么好的诗句,所以我正在寻找一种方法来测试线程应用程序。

有人能指点我怎么才能对这种东西进行单元测试吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-07-25 07:50:33

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

  • 您希望模拟的对象应该实现一个接口。
  • 不要在要测试的函数中实例化要模拟的对象。

下面是对类的重写,以便您可以模拟MyDataWebService并测试RequestThread。基于这个示例,您将能够更容易地为MainThreads类编写完整的测试。

代码语言:javascript
复制
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的接口与实现

代码语言:javascript
复制
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有时很难测试)。

代码语言:javascript
复制
@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
    }
}

现在,测试异步代码通常比同步代码更复杂,因为您经常会遇到不确定性行为、定时问题等。您可能希望在测试中设置一个超时,但请记住:持续集成工具(jenkins,travis等)。运行速度往往比你的机器慢,这是一个常见的问题,所以不要设置太紧。据我所知,对于非确定性问题,没有“一刀切”的解决办法。

马丁·福勒( Martin )在测试中有一篇关于不确定性的优秀文章:https://martinfowler.com/articles/nonDeterminism.html

票数 2
EN

Stack Overflow用户

发布于 2018-07-25 06:31:57

一个与众不同的答案是:2018年,你不再使用“原始”线程了。

到目前为止,Java提供了更好的抽象,例如ExecutorService。您猜怎么着:当您将代码提交任务提交到这样的服务中时,您可能可以使用同螺纹执行器服务对其进行测试。

意义:通过使用这样的抽象并将您的交付分解成特定的服务,您可能(几乎)能够(几乎)完全测试这些小单元,以及任务是如何进入您的系统和如何工作的。

换句话说:您单元测试您的“任务”,然后您“单元”测试任务的集成时,他们进入这样一个执行者。然后,您只需要进行一些实际的函数/集成测试,以检查“真正的并行”解决方案是否符合预期。

其他事情很快就会变得复杂。在普通单元测试中使用真正的线程会导致不一致的行为,或者增加运行时(比如等待线程异步执行的测试)。

如您的示例所示:您的测试只是停留在那里,并定期检查预期的文件是否带有预期的内容。导致:它应该等待多久才能失败?等待时间不够意味着您的测试偶尔会失败,因为代码有时只需要更长的时间。如果等待时间太长,这就意味着运行测试所需的总时间。您不想以数百个单元测试结束--有些测试需要10到20秒,因为“等待其他线程”。

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/51510823

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档