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ó 10 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 - 8 : Testing in Synch
"Testing in Synch", tay học việc của chúng ta học được một điều: các tests có mục đích phục vụ lớn hơn là chỉ đơn thuần chứng minh là mã nguồn chạy được: "tests" là một dạng tài liệu thực hành và giáo dục.

 
  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 8 -  Testing in Synch  
 

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

 

 


"Testing in Synch", tay học việc của chúng ta học được một điều: các tests có mục đích phục vụ lớn hơn là chỉ đơn thuần chứng minh là mã nguồn chạy được: "tests" là một dạng tài liệu thực hành và giáo dục.

Robert C. Martin

Thang máy tầng 17 lại hỏng nên tôi phải dùng trụ tuột. Trong lúc tụt xuống, tôi bắt đầu ngẫm nghĩ đến chuyện đáng ghi nhận là dùng tests như một thứ đồ nghề thiết kế. Chìm đắm trong suy nghĩ, tôi hơi vô ý nên va cùi chỏ vào trụ thang với sức dội Coriolis [*]. Khi tôi gặp Jerry trong phòng thí nghiệm nó vẫn còn đau nhói.

"Mày sẵn sàng thử cái test dùng để gởi thông điệp 'hello' xuyên qua sockets chưa?" gã hỏi.

"Hiển nhiên rồi", tôi đáp.

Chúng tôi bỏ phần phụ chú (comment) của method TestSendMessage.
 

  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);
}
 


"Như dự đoán, đoạn này không biên dịch được" Jerry phát biểu. "Mình cần phải viết cái HelloServer."

"Tôi nghĩ tôi biết phải làm gì," tôi trả lời. "HelloServer là cái class thừa hưởng từ SocketServer và ứng dụng method server() để gởi thông điệp 'hello' qua socket."

Tôi vớ lấy bàn đánh và điều chỉnh nhóm TestSocketServer.java như sau:

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


Ðoạn này biên dịch được và các tests đều đạt ngay lần đầu.

"Ngon lành," Jerry nói. "Bây giờ chúng ta có thể gởi một thông điệp qua socket."

Tôi biết Jerry bắt đầu nghĩ đến chuyện refactoring và tôi muốn qua mặt gã. Xem xét đoạn mã kỹ lưỡng, tôi nhớ gã có đề cập đến vấn đề trùng lặp.

"Có một số mã trùng lặp trong các phần unit tests," tôi nói. "Trong mỗi cái test mình tạo và đóng cái SocketService. Chúng ta nên bỏ nó đi."

"Tinh mắt lắm!" Jerry phán. "Hãy dời nó vào các function Setup và Teardown." Gã tóm lấy bàn đánh và thay đổi như sau:
 

  private SocketService ss;  
public void setUp() throws Exception {     
     ss = new SocketService();   
}    
public void tearDown() throws Exception {     
     ss.close();   
}
 


Sau đó gã bỏ trọn bộ các dòng ss = newSocketService(); and ss.close(); trong ba cái tests.

"Xem được hơn đó," tôi nói. "Hãy thử xem mình có thể gởi một thông điệp ngược lại không."

"Tao cũng nghĩ y như vậy," Jerry trả lời. "Và tao có một cách làm chuyện đó."
Gã bắt đầu đánh một test case mới:

  public void testReceiveMessage() throws Exception {     
     ss.serve(999, new EchoService());     
     Socket s = new Socket("localhost", 999);     
     InputStream is = s.getInputStream();     
     InputStreamReader isr = new InputStreamReader(is);     
     BufferedReader br = new BufferedReader(isr);     
     OutputStream os = s.getOutputStream();     
     PrintStream ps = new PrintStream(os);     
     ps.println("MyMessage");     
     String answer = br.readLine();     
     s.close();     
     assertEquals("MyMessage", answer);   
}
 


"Eo ôi! Tởm thế," tôi cằn nhằn.

"Ừa, đúng thật," Jerry thú nhận. "Hãy làm cho nó chạy cái đã rồi mình dọn dẹp nó sau. Chúng ta không muốn mớ lộn xộn đó ở đây lâu! Mày biết tao định làm gì phải không?"

"Vâng," Tôi trả lời. "EchoService sẽ nhận một thông điệp từ socket và gởi ngược lại ngay. Bởi thế, đoạn test của ông chỉ gởi MyMessage; rồi đọc nó lại."

"Ðúng rồi. muốn thử ngoáy phần EchoService không?" "Tất nhiên," tôi nói một cách hăm hở.
 

  class EchoService implements SocketServer {   
     public void serve(Socket s) {     
          try {       
                InputStream is = s.getInputStream();       
                InputStreamReader isr = new InputStreamReader(is);       
                BufferedReader br = new BufferedReader(isr);       
                OutputStream os = s.getOutputStream();       
                PrintStream ps = new PrintStream(os);       
                String token = br.readLine();       
                ps.println(token);     
         } catch (IOException e) {      }   
     } 
}
 



"Oái," tôi nói. "Lại thêm một mớ mã xấu xí. Mình cứ tạo các objects PrintStream và BufferedReader từ socket. Chúng ta cần phải dọn dẹp mới được."

"Mình sẽ làm chuyện đó ngay sau khi mấy cái test chạy ngon lành," Jerry đáp, trong khi nhìn tôi có vẻ kỳ vọng.

"Oh!" tôi rú lên. "Tôi quên chạy cái test." Xấu hổ, tôi nhấn nút test và theo dõi nó chạy. "Cũng không khó lắm," tôi nói. "Bây giờ hãy vứt phần mã xấu xí ấy đi." Tôi rút nhiều functions ra khỏi EchoService.
 

  class EchoService implements SocketServer {   
     public void serve(Socket s) {
          try {       
               BufferedReader br = getBufferedReader(s);       
               PrintStream ps = getPrintStream(s);
       
               String token = br.readLine();       
               ps.println(token);     
        } catch (IOException e) {      }   
     }    
     private PrintStream getPrintStream(Socket s) throws IOException {    
         OutputStream os = s.getOutputStream();     
         PrintStream ps = new PrintStream(os);     
         return ps;   
      }    
     private BufferedReader getBufferedReader(Socket s) throws IOException {     
          InputStream is = s.getInputStream();     
          InputStreamReader isr = new InputStreamReader(is);     
          BufferedReader br = new BufferedReader(isr);     
          return br;   
     } 

}
 


"Ðoạn này cải tiến method EchoService," Jerry nói, "nhưng nó khá rối cái class. Hơn nữa, nó không giúp gì cho function testRecieveMessage, đó cũng là một điểm không đẹp. Thử nghĩ getBufferedReader và getPrintStream có nằm đúng chỗ không?"

"Ðây sẽ là vấn đề lặp lại," tôi nói. "Ai muốn dùng SocketService phải sẽ chuyển socket thành BufferedReader và PrintStream."

"Chính là câu trả lời!" Jerry đáp lại. "Các methods getBufferedReader và getPrintStream quả thực thuộc về SocketService."

Tôi dời hai functions vào class SocketService và thay đổi EchoService theo đó.
 

  public class SocketService {   
     [...]   
     public static PrintStream getPrintStream(Socket s) throws IOException {     
          OutputStream os = s.getOutputStream();     
          PrintStream ps = new PrintStream(os);     
          return ps;   
     }    

     public static BufferedReader getBufferedReader(Socket s) throws IOException {     
          InputStream is = s.getInputStream();     
          InputStreamReader isr = new InputStreamReader(is);     
          BufferedReader br = new BufferedReader(isr);     
          return br;   
     } 



class EchoService implements SocketServer {   
     public void serve(Socket s) {    
          try {       
                BufferedReader br = SocketService.getBufferedReader(s);       
                PrintStream ps = SocketService.getPrintStream(s);
       
                String token = br.readLine();       
                ps.println(token);     
          } catch (IOException e) {      }   
     } 
}
 


Các tests đều chạy. Giữ vững tình hình, tôi nói: "bây giờ tôi hẳn có thể sửa đổi method testReceiveMessage luôn." Trong lúc Jerry quan sát, tôi thay đổi phần này như sau:

  public void testReceiveMessage() throws Exception {     
     ss.serve(999, new EchoService());     
     Socket s = new Socket("localhost", 999);     
     BufferedReader br = SocketService.getBufferedReader(s);     
     PrintStream ps = SocketService.getPrintStream(s);
     
     ps.println("MyMessage");     
     String answer = br.readLine();     
     s.close();     
     assertEquals("MyMessage", answer);   
}
 


"Ừa, coi được hơn đó," Jerry nói.

"Không chỉ như vậy mà các tests đều đạt," tôi gáy lên. Thế rồi tôi nhận thấy thêm một điều. "Ui, có thêm một cái nữa trong testSendMessage." Tôi sửa cái đó luôn.
 

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

Các tests vẫn chạy. Tôi ngồi tẩn mẩn xét lại class TestSocketServer, tìm xem có gì loại bỏ được không.

"Mày xong chưa?" Jerry hỏi.

Tôi gật đầu.

"Tốt," gã đáp lại. "Mày sắp xịt khói lỗ tai đó."

"Tôi có một câu hỏi. Chúng ta chẳng thay đổi tí nào cái SocketService. Mình thêm testSendMessage và testRecieveMessage và cả hai đều chạy. Mình lại tốn rất nhiều thời gian để viết mấy cái test và lo chuyện refactoring. Làm như thế có lợi gì cho mình nhỉ? Chúng ta chẳng thay đổi mã nguồn chính của sản phẩm gì hết!"

Jerry nhướn mày. "Bộ mày nghĩ là getBufferedReader và getPrintStream đáng được đưa vào sản phẩm?" Mấy cái này khá tầm thường; chúng chỉ hỗ trợ cho mấy cái test mà thôi.

Jerry thở dài. "Nếu mày dính vào project này, tao chỉ cho mày mấy cái test, chúng dạy mày được những gì?"

Tôi học được gì từ mấy cái test đó? Tôi học được cách tạo SocketService và gắn SocketServer từ đó. Tôi cũng học được cách gởi và nhận thông điệp. Tôi học được tên và vị trí của các classes trong framework và cách xử dụng chúng. "Ý ông mình viết mấy cái tests này để làm ví dụ cho những người khác?"

"Ðó là một phần lý do, Alphonse. Những người khác sẽ có thể đọc mấy cái test này và xem cách làm việc của mã nguồn. Họ cũng có thể làm việc xuyên qua lý giải. Hơn thế, họ sẽ có thể biên dịch và thao tác các cái tests này và chứng minh với chính họ rằng cách lý giải của chúng ta đáng thuyết phục. Còn nhiều điều hơn thế nữa," gã nói tiếp, " nhưng chúng ta để dành vấn đề này cho một dịp khác."

Cùi chỏ tôi vẫn còn đau nhức nên tôi mừng là thang máy đã chữa. Trong khi đi thang máy, tôi không ngừng nghĩ ngợi: "Tests là một dạng tài liệu-có thể biên dịch được, có thể thao tác và luôn luôn đồng bộ."

--------------------------------------------

[*] Còn có tên gọi là Coriolis effect gọi theo tên của kỹ sư - nhà toán học Pháp Gustave-Gaspard Coriolis. Ở đây dường như tác giả mô tả hành động tay học việc ôm cột tụt xuống và trong khi tụt xuống, anh ta ở trạng thái xoay vòng trên cột nên bị "Coriolis force". Xem thêm chi tiết ở: http://zebu.uoregon.edu/~js/glossary/coriolis_effect.html. và http://satftp.soest.hawaii.edu/ocn620/coriolis/ - (chú thích của người dịch). [Trở lên vị trí cũ]
 

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