java - 对线程应用程序进行单元测试
问题描述
我正在考虑如何使用 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
}
}
现在,测试异步代码通常比同步代码更复杂,因为您经常会遇到非确定性行为、时间问题等。您可能希望在测试中设置超时,但请记住:持续集成工具(jenkins、travis 等)通常会比您的机器运行得慢,这是导致问题的常见原因,因此请不要将其设置得太紧。据我所知,对于非确定性问题没有“万能”的解决方案。
Martin Fowler 有一篇关于测试中非确定性的优秀文章:https ://martinfowler.com/articles/nonDeterminism.html
推荐阅读
- python - 使用字典将列表转换为数据框
- python - 重命名熊猫中的列值
- cmd - 我可以创建在 cmd 中使用的简写名称吗?
- c++ - UWP C++:只写入而不是读取全局变量?
- windows - 使用 mingw32-make 安装 RSEM 时,MinGW 找不到我安装的 zlib.h 和 zlib 文件
- heatmap - 如何下载网格化 sst 数据?
- database - laravel 一对多关系返回 null
- javascript - Selenium:如何检查第二个选项卡是否已完全加载
- c++ - 函数标识符未定义 C++
- python - 如何使用 Pyserial 检测串行端口是否正在被另一个应用程序使用?