jjzjj

java - 使用 Mockito 模拟服务器-客户端连接

coder 2024-04-01 原文

简介

我正在尝试通过将字符串从一个线程发送到另一个线程来测试套接字连接,其中服务器和客户端套接字使用 Mockito v1.9.5 模拟。

这是我要运行的测试:

@Test
public void testConnection() {        
    //associate a mock server socket with the TCP Connection
    TcpSocketConnection connection = new TcpSocketConnection(mockServerSocket);
    try {
        //begin thread which listens for clients, then sends "Hello, world" to a connected
        //client.
        connection.listen();
        BufferedReader reader = new BufferedReader(
            new InputStreamReader(mockTestClientSocket.getInputStream(), DEFAULT_CHARSET)
        );

        long startTime = System.nanoTime();
        for (long interval = 0;
                interval < TIMEOUT_TIME;
                interval = System.nanoTime() - startTime) {
            if (reader.ready()) {
                String receivedMessage = reader.readLine();
                assertEquals("Hello, world!", receivedMessage);
                mockTestClientSocket.close();
                connection.closeSocket();
                return;
            }
        }
        mockTestClientSocket.close();
        connection.closeSocket();
        fail("Failed to receive message.");
    } catch (IOException e) {
        fail(e.getMessage());
    }
}

测试一直运行到 TIMEOUT_TIME,然后失败的断言是 “Failed to receive message.” 我在这里指定模拟对象的行为:

@Before
public void setup() {
    mockServerSocket = mock(ServerSocket.class);
    try {
        when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);
    } catch (IOException e) {
        fail(e.getMessage());
    }

    mockTestClientSocket = mock(Socket.class);
    try {
        PipedOutputStream oStream = new PipedOutputStream();
        when(mockTestClientSocket.getOutputStream()).thenReturn(oStream);

        PipedInputStream iStream = new PipedInputStream(oStream);
        when(mockTestClientSocket.getInputStream()).thenReturn(iStream);

        when(mockTestClientSocket.isClosed()).thenReturn(false);
    } catch (IOException e) {
        fail(e.getMessage());
    }
}

我要测试的部分内容是在 connection.listen() 中启动的内部类中的以下 run():

class InnerListenerClass implements Runnable {
    @Override
    public void run() {
        try {
            clientSocket = socket.accept();
            writer = new OutputStreamWriter(
               clientSocket.getOutputStream(), DEFAULT_CHARSETNAME);
            out = new PrintWriter(writer, true);
            while (!clientSocket.isClosed()) {
                out.println("Hello, world!");
                Thread.sleep(MILLIS_BETWEEN_MESSAGES);
            }
        } catch (InterruptedException | IOException e) {
            LOG.debug(e.getMessage());
        }
    }

    public InnerListenerClass(final ServerSocket socket) {
        this.socket = socket;
    } 
}

下面是 TcpSocketConnection.java 的一部分:

class TcpSocketConnection() {
    public TcpSocketConnection(final ServerSocket serverSocket) {
        checkNotNull(serverSocket);
        this.serverSocket = serverSocket;
    }
    ...
    public final void listen() throws IOException {
        listenerThread = new Thread(new InnerListenerClass(serverSocket));
        listenerThread.start();
    }
    ...
}

它在我脑海中是如何运作的

我将尝试逐步完成我的测试过程,为这个问题添加一些额外的上下文。从 testConnection() 的开头开始:

TcpSocketConnection connection = new TcpSocketConnection(mockServerSocket);

这将创建一个与与之关联的模拟 ServerSocket 的连接。这将创建一个线程,该线程以以下行开始:

clientSocket = socket.accept();

由于上面的 socket 是对 mockServerSocket 的引用,Mockito 知道返回对名为 mockTestClientSocket 的模拟 Socket 的引用,因为这一行:

when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);

接下来是下面一行:注意:我相信这是我的理解和现实存在分歧的地方,因为我相信基于调试,这个线程在创建这个 OutputStreamWriter 对象时挂起。我还没弄明白为什么。

writer = new OutputStreamWriter(clientSocket.getOutputStream(), DEFAULT_CHARSETNAME);

在给定 OutputStream 的情况下创建一个新的 OutputStreamWriter。由于 setup 部分中的这些行,Mockito 知道模拟客户端套接字的输出流应该是什么样子:

PipedOutputStream oStream = new PipedOutputStream();                 
when(mockTestClientSocket.getOutputStream()).thenReturn(oStream);

另外,因为 setup 发生在测试之前,我们知道我们的 InputStream 有一个对这个 OutputStream 的引用,因为这一行:

PipedInputStream iStream = new PipedInputStream(oStream);

根据此构造函数的文档,此 “创建了一个 PipedInputStream,以便它连接到管道输出流 (oStream)。然后写入 (oStream) 的数据字节将可用作此流的输入。 "

run() 中的 while 循环开始并导致“Hello, world!”被发送出 OutputStream(也被 InputStream 接收)。 接下来,我们很好地包装 inputStream:

BufferedReader reader = new BufferedReader(new InputStreamReader(mockTestClientSocket.getInputStream(), DEFAULT_CHARSET)); 

借助 Mockito 的魔力,mockTestClientSocket.getInputStream() 调用实际上返回了之前的 iStream,因为以下行:

when(mockTestClientSocket.getInputStream()).thenReturn(iStream);   

所以现在我们有一个带有输入流的阅读器,并且该输入流连接到一个输出流。该输出流连接到 PrintWriter,它正在 printlning “Hello,world!”。然而,读者似乎从来没有得到 ready()

问题

为什么创建的监听器线程在创建 OutputStreamWriter 期间挂起,以及我的 Hello, World! 字符串如何从模拟套接字正确发送到模拟套接字客户?

抱歉,我是 Mockito/java.net.* 的新手,总体来说有点厚。我想我已经包含了代码的所有相关部分,但如果有任何不清楚的地方,请告诉我。

最佳答案

我可以通过修改您代码中的两处来使您的单元测试通过:

1。正确模拟 mockServerSocket.accept()

到目前为止,你 mock mockServerSocket.accept() 太早了,因为 mockTestClientSocket 还没有设置,所以它会返回 null,你需要先设置它,这样你的代码应该是:

mockServerSocket = mock(ServerSocket.class);
// Set it first
mockTestClientSocket = mock(Socket.class);

try {
    // Then mock it
    when(mockServerSocket.accept()).thenReturn(mockTestClientSocket);
} catch (IOException e) {
    fail(e.getMessage());
}
...

2。正确同步你的线程

当您启动一个专用线程来管理您的客户端套接字时,您需要同步您的线程以确保您的消息准备好被读取。

为此,您可以:

  1. 通过调用 reader.readLine() 来简化您的代码,但这是一种阻塞方法,因为您的当前线程将等待另一个线程需要的时间写这行(这里的消息)。

代码可以是:

BufferedReader reader = ...

String receivedMessage = reader.readLine();
assertEquals("Hello, world!", receivedMessage);
mockTestClientSocket.close();
connection.closeSocket();
  1. TIMEOUT_TIME 设置一个足够大甚至过大的值以确保其他线程准备就绪,例如,因为它是一个以纳秒为单位的值,您可以将它设置为 30 秒到 30_000_000_000L。如果您没有设置足够大的值,您的测试在缓慢和/或过载和/或共享系统(例如用于持续集成的服务器)中可能会不稳定。

关于java - 使用 Mockito 模拟服务器-客户端连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40414930/

有关java - 使用 Mockito 模拟服务器-客户端连接的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

    我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

  3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

    类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

  4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

    很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

  5. ruby - 在 Ruby 中使用匿名模块 - 2

    假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

  6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

    我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

  7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  9. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  10. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

    我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

随机推荐