Diễn Đàn Tin Học
 Trang chủ      Tutorial Room      Diễn đàn      Liên hệ - Góp ý
Diễn Đàn Tin Học
Thông tin
Download
Tutorial Room
Sản phẩm
Thống kê
Hiện có 11 người đang trực tuyến.
Google


Diễn Đàn Tin Học » Tutorial Room » Lập trình » Software Engineering » Craftsman-Truyện dài nhiều tập

Craftsman 7 - Socket Service2
Alphonse và Jerry thử truy cập nhiều lần Socket Service. Hãy cùng xem để biết kết quả có được như ý của họ không nhé ...

  Thông tin      
  Chuyên đề   Lập trình - Software Engineering  
  Dịch giả   hnd  
  Bài gốc   http://www.vninformatics.com/forum/?action=msg&msg=1023492790#1023492790  
  Tựa gốc   Craftsman 7 -  Socket Service2  
 

Xin được thay mặt diễn đàn cảm ơn bạn về bài viết này.

 

Chào các SE fans, xin gởi đến các fans phần kế tiếp của bộ "truyện dài nhiều tập" craftsman7.


SocketService2

Robert C. Martin
Ngày 15 tháng 10 2002

Lần trước Alphonse và Jerry khởi đầu trên một framework java đơn giản hỗ trợ dịch vụ socket. Test case thứ nhất của họ vạch ra trường hợp dồn đuổi (race condition) mà họ đã giải quyết ổn thoả. Chuỗi unit test hiện tại được dẫn ở mã dẫn 1 và mã nguồn chính ở mã dẫn 2.

Mã dẫn 1

  import junit.framework.TestCase;
import junit.swingui.TestRunner;
import java.io.IOException;
import java.net.Socket;

public class TestSocketServer extends TestCase {
   public static void main(String[] args) {
      TestRunner.main(new String[]{"TestSocketServer"});
   }

   public TestSocketServer(String name) {
     super(name);
   }

   public void testOneConnection() throws Exception {
      SocketService ss = new SocketService();
      ss.serve(999);
      connect(999);
      ss.close();
      assertEquals(1, ss.connections());
}

   private void connect(int port) {
     try {
            Socket s = new Socket("localhost", port);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
            }
            s.close();
    } catch (IOException e) {
        fail("could not connect");
    }
  }
}
 



Mã dẫn 2
 

  import java.io.IOException;
import java.net.*;

public class SocketService {
   private ServerSocket serverSocket = null;
   private int connections = 0;
   private Thread serverThread = null;

   public void serve(int port) throws Exception {
     serverSocket = new ServerSocket(port);
     serverThread = new Thread(
     new Runnable() {
        public void run() {
           try {
              Socket s = serverSocket.accept();
              s.close();
              connections++;
           } catch (IOException e) {
           }
         }
       }
     );
     serverThread.start();
   }

   public void close() throws Exception {
     serverSocket.close();
   }

   public int connections() {
     return connections;
   }
}
 



Sau giờ giải lao, chúng tôi trở lại và sẵn sàng tiếp túc với SocketService.

"Chúng ta đã chứng minh được mình có thể truy cập một lần. Vậy hãy thử truy cập nhiều lần xem sao." Jerry nói.

"Nghe được lắm." Tôi trả lời. Sau đó tôi viết cái test case như sau:
 

  public void testManyConnections() throws Exception {
   SocketService ss = new SocketService();
   ss.serve(999);
   for (int i = 0; i < 10; i++)
   connect(999);
   ss.close();
   assertEquals(10, ss.connections());
}
 


"OK, cái test này hỏng." Tôi nói.

"Y như ước đoán". Jerry đáp. "Cái SocketService chỉ gọi method accept một lần. Chúng ta cần đặt bước gọi đó vào một vòng lặp."

"Khi nào vòng lặp đó chấm dứt?" Tôi hỏi.

Jerry nghĩ ngợi 1 tí và nói: "Khi chúng ta gọi method close của SocketService."

"Như thế này chăng?" Và tôi hiệu đính như sau:
 

  public class SocketService {
   private ServerSocket serverSocket = null;
   private int connections = 0;
   private Thread serverThread = null;
   private boolean running = false;

   public void serve(int port) throws Exception {
     serverSocket = new ServerSocket(port);
     serverThread = new Thread(
        new Runnable() {
           public void run() {
               running = true;
               while (running) {

                  try {
                    Socket s = serverSocket.accept();
                    s.close();
                    connections++;
                  } catch (IOException e) {
                  }
               }
            }
         }
      );
      serverThread.start();
   }

   public void close() throws Exception {
     running = false;
     serverSocket.close();
   }
}
 


Tôi chạy cái test và cả hai đều đạt.

"Tốt." Tôi nói. "Bây giờ chúng ta có thể truy cập bao nhiêu tùy thích. Không may cái SocketService chẳng làm gì nhiều khi mình truy cập đến nó. Nó chỉ đóng lại mà thôi."

"Ừa, đổi nó đi." Jerry nói. "Mình hãy buộc SocketService gởi thông điệp "Hello" mỗi khi chúng ta truy cập đến nó."

Tôi không cần chuyện này cho lắm. Tôi nói: "Tại sao mình làm bẩn cái SocketService bằng thông điệp "Hello" chỉ để thoả mãn cái test của mình? SocketService có thể gởi thông điệp thì tốt nhưng mình không muốn thông điệp này là một phần của mã nguồn SocketService!"

"Ðúng thế!" Jerry đồng ý. "Mình muốn thông điệp được chỉ định và xác thực do cái test."

"Mình làm sao đây?" Tôi hỏi.

Jerry mỉm cười đáp: "Chúng ta dùng cái Mock Object pattern. Nói một cách ngắn gọn, mình tạo ra một cái interface từ đó SocketService sẽ thao tác sau khi nhận một truy cập. Chúng ta sẽ có cái test ứng dụng cái interface đó dùng để gởi thông điệp "Hello". Sau đó, mình sẽ có cái test dùng để đọc thông điệp từ socket của client và xác thực thông tin được gởi đi một cách đúng đắn."

Tôi chẳng biết Mock Object pattern là gì cả và thành phần interface của gã làm tôi bối rối. "Ông chỉ cho tôi được không?" Tôi hỏi.

Thế rồi Jerry vớ lấy bàn đánh và bắt đầu gõ.

"Ðầu tiên chúng ta viết cái test."
 

  public void testSendMessage() throws Exception {
   SocketService ss = new SocketService();
   ss.serve(999, new HelloServer());
   Socket s = new Socket("localhost", 999);
   InputStream is = s.getInputStream();
   InputStreamReader isr = new InputStreamReader(is);
   BufferedReader br = new BufferedReader(isr);
   String answer = br.readLine();
   s.close();
   assertEquals("Hello", answer);
}
 


Tôi kiểm tra đoạn mã này cẩn thận. "OK, ông tạo ra cái gọi là HelloServer và đưa nó vào trong method serve. Cái này sẽ làm hỏng hết các cái test khác!"

"Hay lắm!" Jerry thốt lên. "Ðiều đó có nghĩa là chúng ta cần refactor những test khác trước khi tiếp tục."

"Nhưng các dịch vụ trong hai cái test kia chẳng làm gì hết." Tôi ý kiến.

"Tất nhiên là chúng làm việc - chúng đếm số truy cập! Mày có nhớ là mày ghét mấy cái biến số truy cập đến thế nào không, và nó chỉ là phần phụ mà thôi? Bây giờ mình sẽ dẹp chúng đi."

"Mình sắp sửa làm thế à?"

"Xem đây." Jerry cười rộ. "Ðầu tiên chúng ta đổi hai cái test và thêm biến số connections vào test case."
 

  public void testOneConnection() throws Exception {
    ss.serve(999, connectionCounter);
    connect(999);
   assertEquals(1, connections);
}

public void testManyConnections() throws Exception {
   ss.serve(999, connectionCounter);
   for (int i=0; i<10; i++)
   connect(999);
   assertEquals(10, connections);
}
 


"Kế tiếp mình tạo cái interface."

  import java.net.Socket;
public interface SocketServer {
   public void serve(Socket s);
}
 


"Sau đó chúng ta tạo biến số connectionCounter và khởi động nó trong constructor của TestSocketServer bằng một anonymous inner class để nó tăng cấp biến số connections.
 

  public class TestSocketServer extends TestCase {
   private int connections = 0;
   private SocketServer connectionCounter;

   public static void main(String[] args) {
     TestRunner.main(new String[]{"TestSocketServer"});
   }

   public TestSocketServer(String name) {
     super(name);
     connectionCounter = new SocketServer() {
        public void serve(Socket s) {
        connections++;
      }
   };

}
 

...
"Cuối cùng, chúng ta cho nó biên dịch trọn bộ bằng cách thêm đối số phụ vào method serve của SocketService và biến cái test mới thành phần chú giải (để nó khỏi chạy)."

  public void serve(int port, SocketServer server) throws Exception {
...
}
 


"OK, tôi biết ý ông rồi." Tôi nói. "Hai cái test cũ lúc này hẳn phải hỏng bởi lẽ SocketService không bao giờ gây ra method serve từ đối số SocketServer của nó."

Tất nhiên các cái test đã hỏng vì chính lý do ấy.

Tôi biết phải làm gì kế tiếp. Tôi vớ lấy bàn đánh và thay đổi như sau:

 

  public class SocketService {
   private ServerSocket serverSocket = null;
   private int connections = 0;
   private Thread serverThread = null;
   private boolean running = false;
   private SocketServer itsServer;

   public void serve(int port, SocketServer server) throws Exception {
     itsServer = server;
     serverSocket = new ServerSocket(port);
     serverThread = new Thread(
       new Runnable() {
         public void run() {
            running = true;
            while (running) {
               try {
                  Socket s = serverSocket.accept();
                  itsServer.serve(s);
                  s.close();
                  connections++;
               } catch (IOException e) {
               }
            }
         }
      }
    );
   serverThread.start();
}
...
 
 


Ðoạn mã này làm các test chạy được.

"Hay lắm!" Jerry nói. "Bây giờ chúng ta phải làm cho cái test mới chạy."

Thế nên tôi tháo bỏ phần chú giải cho đoạn test và biên dịch nó. Nó "la làng" trong phần HelloServer.

"Ô, đúng rồi. Mình phải thực thi cái HelloServer. Nó sẽ phun ra chữ "hello" từ socket, phải không?"

"Ðúng thế." Jerry xác nhận.

Thế rồi tôi viết cái class mới trong hồ sơ TestSocketServer.java như sau
 

  class HelloServer implements SocketServer {
   public void serve(Socket s) {
      try {
          PrintStream ps = new PrintStream(s.getOutputStream());
          ps.println("Hello");
     } catch (IOException e) {
     }
   }
}
 


Các test đều ổn.

"Cũng dễ thôi." Jerry nói.

"Ừa. Phần pattern Mock Oject khá hữu dụng. Nó cho phép ta duy trì các mã dùng để test trong kế hoạch test. SocketService không biết gì cả."

"Còn hữu dụng hơn thế." Jerry trả lời. "Các servers thật cũng sẽ ứng dụng interface SocketServer."

"Tôi biết." Tôi trả lời. "Thật lý thú khi thấy từ nhu cầu tạo ra một unit test đưa mình đến chỗ tạo ra một đồ hình hữu dụng một cách tổng quát."

"Ðiều này thường xảy ra mà." Jerry nói. "Tests là người dùng đó. Nhu cầu dùng tests thường trùng hợp với nhu cầu của người dùng thật sự."

"Nhưng tại sao lại gọi nó là Mock Object?"

"Hãy nghĩ trên phương diện thế này. HelloServer dùng để thay thế cho, hoặc là một bản nháp, của một server thật. Cái pattern này cho phép chúng ta thay thế bản nháp của chuyện test vào mã nguồn ứng dụng thật sự."

"À ra vậy." Tôi đáp. "Thôi thì bây giờ mình nên dọn dẹp phần mã này và xoá bỏ cái biến số truy cập vô dụng kia vậy."

"Ðồng ý."

Thế rồi chúng tôi dọn dẹp thêm một chút nữa và nghỉ giải lao. Kết quả của SocketService như sau:
 

  import java.io.IOException;
import java.net.*;

public class SocketService {
   private ServerSocket serverSocket = null;
   private Thread serverThread = null;
   private boolean running = false;
   private SocketServer itsServer;

   public void serve(int port, SocketServer server) throws Exception {
     itsServer = server;
     serverSocket = new ServerSocket(port);
     serverThread = makeServerThread();
     serverThread.start();
   }

   private Thread makeServerThread() {
      return new Thread (
        new Runnable() {
          public void run() {
            running = true;
            while (running) {
              acceptAndServeConnection();
            }
          }
        }
      );
   }

   private void acceptAndServeConnection() {
      try {
         Socket s = serverSocket.accept();
         itsServer.serve(s);
         s.close();
     } catch (IOException e) {
     }
   }

   public void close() throws Exception {
      running = false;
      serverSocket.close();
   }
}
 


<đón xem phần kế tiếp>
 

Các bài viết mới nhất
Bạn không được phép truy cập vào địa chỉ này!
[Download] - NT Password Recovery Bootdisk
Craftsman 18 - Slow and Steady
Quản lý MySQL Server sử dụng lệnh trên console
Memory-RAM - Một số thuật ngữ và kỹ thuật
Đưa chương trình vào đường dẫn hệ thống
Thay thế BIND với djbdns - phần 1
Phương pháp khôi phục lại password trong hệ thống Win2k/XP/2K3 (Support NTFS)
Cài đặt ActivePython 2.4 trên IIS
Sử Dụng Tiếng Việt Với LaTeX
Cài đặt 1 SMTP server tại nhà với Microsoft IIS
Linux - Vì sao sáng trên bầu trời CNTT
Biên dịch Linux kernel - phần 4
Tự học lập trình Borland Delphi
Thiết kế và Lập trình Web bằng ASP
Cài đặt PHP 4 trên IIS
Giới thiệu về XML-RPC
Sử dụng CSDL MySQL
Một chương trình download manager đơn giản
Giới thiệu - Sơ lược về ngôn ngữ PHP
Các bài viết liên quan
Craftsman 18 - Slow and Steady
Craftsman 17 - Call the Guards
Craftsman 16 - Excess Politesse
Craftsman 15 - Ess Are Pee
Craftsman 14 - Transaction Actions
Craftsman 13 - A Better Solution
Craftsman 12 - Three Ugly Lines
Craftsman 11 - Forget the Main()
Craftsman 10 - Iterations Unbound
Craftsman 9 - Dangerous Threads
Craftsman - 8 : Testing in Synch
Craftsman 7 - Socket Service2
Craftsman 6 - Socket Service
Craftsman 05 - Baby Steps
Craftsman 04 - A Test of Patience
Craftsman 03 - Clarity and Collaboration
Craftsman 02 - Crash Diet
Craftsman 01 - Opening Disaster
Quảng cáo
HOME | TUTORIAL ROOM | FORUM | CONTACT