单元测试线程应用程序



我正在思考如何使用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");
}
}

并用CCD_ 5。注意,检查现有的文件和线程睡眠可能不是这里最优雅的事情。如果您可以在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
}
}

现在,测试异步代码通常比测试同步代码更复杂,因为你经常会面临非确定性行为、时间问题等。你可能想在测试中设置一个超时,但请记住:连续集成工具(jenkins、travis等(通常会比你的机器运行得慢,这是导致问题的常见原因,所以不要设置得太紧。据我所知,对于非决定论问题,没有"一刀切"的解决方案。

Martin Fowler有一篇关于测试中的非决定论的优秀文章:https://martinfowler.com/articles/nonDeterminism.html

一个与众不同的否定答案:在2018年,您不再使用"原始"线程。

Java现在可以提供更好的抽象,例如ExecutorService。你猜怎么着:当你的代码将任务提交到这样的服务中时,你可能可以使用相同的线程执行器服务来测试它。

意思是:通过使用这样的抽象并将您的交付分解为特定的服务,您可能不仅能够(几乎(完全测试小单元,还能够测试任务是如何进入您的系统并进行工作的。

换句话说:你对你的"任务"进行单元测试,然后当任务进入这样一个执行器时,你对任务的集成进行单元测试。然后,您只剩下一点实际的功能/集成测试来检查"真正的并行"解决方案是否如预期那样运行。

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

与您的示例一样:您的测试只需坐在那里,定期检查预期的文件是否使用预期的内容编写。导致:失败之前应该等待多长时间?等待时间不够长意味着您的测试偶尔会失败,因为代码有时只需要更长的时间。如果您等待的时间太长,则会增加运行测试所需的总时间。你不想以数百个单元测试结束,有些测试需要10-20秒,因为"在等待其他线程"。

最新更新