2012-04-05 17 views
5

Tôi đang kiểm tra đơn vị một lớp mà tôi cần một khoảng thời gian nhất định để vượt qua trước khi tôi có thể kiểm tra kết quả. Cụ thể tôi cần x phút để vượt qua trước khi tôi có thể biết liệu bài kiểm tra có hiệu quả hay không. Tôi đã đọc điều đó trong thử nghiệm đơn vị, chúng ta nên kiểm tra giao diện chứ không phải thực hiện, vì vậy chúng ta không nên truy cập vào biến riêng, nhưng ngoài việc đặt một giấc ngủ vào bài kiểm tra đơn vị của mình, tôi không biết cách kiểm tra mà không sửa đổi biến riêng.Làm thế nào để kiểm tra việc vượt qua thời gian trong thử nghiệm jUnit mà không cần truy cập các biến riêng tư?

thử nghiệm của tôi được thiết lập như thế này:

@Test 
public void testClearSession() { 
    final int timeout = 1; 
    final String sessionId = "test"; 
    sessionMgr.setTimeout(timeout); 
    try { 
     sessionMgr.createSession(sessionId); 
    } catch (Exception e) { 
     e.printStackTrace(); 
    } 
    DBSession session = sessionMgr.getSession(sessionId); 
    sessionMgr.clearSessions(); 
    assertNotNull(sessionMgr.getSession(sessionId)); 
    Calendar accessTime = Calendar.getInstance(); 
    accessTime.add(Calendar.MINUTE, - timeout - 1); 
    session.setAccessTime(accessTime.getTime()); // MODIFY PRIVATE VARIABLE VIA PROTECTED SETTER 
    sessionMgr.clearSessions(); 
    assertNull(sessionMgr.getSession(sessionId)); 
} 

Có thể kiểm tra điều này không phải là sửa đổi biến tin accessTime (thông qua việc tạo ra các setter setAccessTime hoặc phản chiếu), hoặc chèn một giấc ngủ trong các thử nghiệm đơn vị ?

EDIT 11 tháng Tư năm 2012

Tôi đặc biệt cố gắng để kiểm tra rằng đối tượng SessionManager tôi xóa phiên sau một thời gian nhất định đã trôi qua. Cơ sở dữ liệu tôi đang kết nối sẽ thả các kết nối sau một khoảng thời gian cố định. Khi tôi đến gần thời gian chờ đó, đối tượng SessionManager sẽ xóa các phiên bằng cách gọi thủ tục "hoàn tất phiên" trên cơ sở dữ liệu và xóa các phiên khỏi danh sách nội bộ của nó.

Đối tượng SessionManager được thiết kế để chạy trong một chuỗi riêng biệt. Mã này tôi đang thử nghiệm trông như sau:

public synchronized void clearSessions() { 
    log.debug("clearSessions()"); 
    Calendar cal = Calendar.getInstance(); 
    cal.add(Calendar.MINUTE, - timeout); 
    Iterator<Entry<String, DBSession>> entries = sessionList.entrySet().iterator(); 
    while (entries.hasNext()) { 
     Entry<String, DBSession> entry = entries.next(); 
     DBSession session = entry.getValue(); 
     if (session.getAccessTime().before(cal.getTime())) { 
      // close connection 
      try { 
       connMgr.closeconn(session.getConnection(), entry.getKey()); 
      } catch (Exception e) { 
       e.printStackTrace(); 
      } 
      entries.remove(); 
     } 
    } 
} 

các cuộc gọi đến connMgr (ConnectionManager đối tượng) là một chút phức tạp, nhưng tôi đang trong quá trình mã di sản refactoring và nó là nó là gì vào lúc này. Đối tượng Session lưu trữ kết nối đến cơ sở dữ liệu cũng như một số dữ liệu liên quan.

+0

Một lưu ý phụ: kiểm tra đơn vị thường không nên kiểm tra bất cứ điều gì hoạt động trong nền (trong các chủ đề riêng biệt). Vì vậy, bạn có thể tách logic và chạy nó trong một chủ đề khác. Cái đầu tiên sau đó có thể dễ dàng được kiểm tra bởi các bài kiểm tra đơn vị, và thứ hai - theo các bài kiểm tra chức năng/chấp nhận – zerkms

+0

Mã mà bạn đang kiểm tra gọi là 'System.getCurrentTimeMillis()' (hoặc một cái gì đó tương đương như 'new Date()')? Nếu vậy, đây là một cái gì đó của một mô hình chống thử nghiệm; bạn muốn ủy quyền điều này cho một lớp "nguồn thời gian", mà bạn có thể tiêm. Sau đó, bạn có thể thực hiện các thử nghiệm của mình bằng nguồn thời gian giả. Nếu bạn có thể đăng nguồn mà bạn đang cố gắng kiểm tra, tôi có thể giúp bạn hiểu cách làm những gì tôi đề xuất. –

+0

@DavidWallace - Tôi cho rằng GetInstance là cuộc gọi đó. JavaDocs xác nhận phương thức getInstance của Lịch trả về một đối tượng Lịch có các trường thời gian đã được khởi tạo với ngày tháng và thời gian hiện tại: ' – Gishu

Trả lời

4
  • Thử nghiệm có thể thực hiện với một số phép tái cấu trúc để làm cho mục đích rõ ràng hơn. Nếu những gì tôi hiểu là chính xác ...

.

public void TestClearSessionsMaintainsSessionsUnlessLastAccessTimeIsOverThreshold() { 

    final int timeout = 1; 
    final String sessionId = "test"; 
    sessionMgr = GetSessionManagerWithTimeout(timeout); 
    DBSession session = CreateSession(sessionMgr, sessionId); 

    sessionMgr.clearSessions(); 
    assertNotNull(sessionMgr.getSession(sessionId)); 

    session.setAccessTime(PastInstantThatIsOverThreshold()); // MODIFY PRIVATE VARIABLE VIA PROTECTED SETTER 
    sessionMgr.clearSessions(); 
    assertNull(sessionMgr.getSession(sessionId)); 
} 
  • Bây giờ đến vấn đề kiểm tra mà không cần phải phơi bày tình trạng tin
    • Làm thế nào là biến tin sửa đổi trong cuộc sống thực? Có phương pháp nào khác mà bạn có thể gọi để cập nhật thời gian truy cập không?
    • Vì đồng hồ/thời gian là một khái niệm quan trọng, tại sao không làm cho điều đó rõ ràng là một vai trò. Vì vậy, bạn có thể chuyển một đối tượng Clock đến Session, nó sử dụng để cập nhật thời gian truy cập nội bộ của nó. Trong các thử nghiệm của bạn, bạn có thể vượt qua trong một MockClock, phương thức getCurrentTime() của nó sẽ trả về bất cứ giá trị nào bạn muốn. Tôi đang tạo ra cú pháp mocking .. vì vậy hãy cập nhật với bất kỳ thứ gì bạn đang sử dụng.

.

public void TestClearSessionsMaintainsSessionsUnlessLastAccessTimeIsOverThreshold() { 

     final int timeout = 1; 
     final String sessionId = "test"; 
     expect(mockClock).GetCurrentTime(); willReturn(CurrentTime()); 
     sessionMgr = GetSessionManagerWithTimeout(timeout, mockClock); 
     DBSession session = CreateSession(sessionMgr, sessionId); 

     sessionMgr.clearSessions(); 
     assertNotNull(sessionMgr.getSession(sessionId)); 

     expect(mockClock).GetCurrentTime(); willReturn(PastInstantThatIsOverThreshold()); 
     session.DoSomethingThatUpdatesAccessTime(); 
     sessionMgr.clearSessions(); 
     assertNull(sessionMgr.getSession(sessionId)); 
} 
+0

+1 cho tên phương thức thử nghiệm .. – Jayan

+0

Bạn nên đề cập rằng bạn đã sử dụng [mockito] (http://code.google.com/p/mockito/) để nhận cú pháp mocking đẹp. –

+0

@ BjörnPollex bạn có chắc chắn không? Nó không giống như cú pháp mockito với tôi. –

1

EDIT: Tôi thích câu trả lời của Gishu tốt hơn. Ông cũng khuyến khích bạn thử thời gian, nhưng ông đối xử với nó như là một đối tượng lớp học đầu tiên.

Quy tắc bạn đang cố kiểm tra chính xác là gì? Nếu tôi đọc mã của bạn đúng, có vẻ như mong muốn của bạn là để xác minh rằng phiên liên quan đến ID "kiểm tra" hết hạn sau một thời gian chờ nhất định, đúng không?

Thời gian là một điều khó khăn trong các bài kiểm tra đơn vị vì đó là trạng thái cơ bản toàn cầu, vì vậy đây là một ứng cử viên tốt hơn cho bài kiểm tra chấp nhận (như zerkms được đề xuất).

Nếu bạn vẫn muốn có một bài kiểm tra đơn vị cho nó, thường tôi cố gắng trừu tượng và/hoặc cô lập tham chiếu đến thời gian, vì vậy tôi có thể thử chúng trong các bài kiểm tra của tôi. Một cách để làm điều này là bằng cách phân lớp lớp dưới kiểm tra. Đây là một sự phá vỡ nhỏ trong đóng gói, nhưng nó hoạt động sạch hơn là cung cấp một phương thức setter được bảo vệ, và tốt hơn nhiều so với sự phản chiếu.

Một ví dụ:

class MyClass { 
    public void doSomethingThatNeedsTime(int timeout) { 
    Date now = getNow(); 
    if (new Date().getTime() > now.getTime() + timeout) { 
     // timed out! 
    } 
    } 

    Date getNow() { 
    return new Date(); 
    } 
} 

class TestMyClass { 
    @Test 
    public void testDoSomethingThatNeedsTime() { 
    MyClass mc = new MyClass() { 
     Date getNow() { 
     // return a time appropriate for my test 
     }  
    }; 

    mc.doSomethingThatNeedsTime(1); 

    // assert 
    } 
} 

Đây là một chút của một ví dụ giả tạo, nhưng hy vọng bạn sẽ có được điểm. Bằng cách phân lớp phương thức getNow(), thử nghiệm của tôi không còn phụ thuộc vào thời gian toàn cầu. Tôi có thể thay thế bất cứ lúc nào tôi muốn. Giống như tôi đã nói, điều này phá vỡ đóng gói một chút, bởi vì phương thức REAL getNow() không bao giờ được kiểm tra, và nó yêu cầu thử nghiệm để biết điều gì đó về việc thực hiện. Đó là lý do tại sao nó tốt để giữ cho một phương pháp nhỏ và tập trung, không có tác dụng phụ. Ví dụ này cũng giả định rằng lớp đang được kiểm tra không phải là cuối cùng.

Mặc dù có những hạn chế, nó sạch hơn (theo ý kiến ​​của tôi) hơn là cung cấp một setter scoped cho một biến riêng tư, mà thực sự có thể cho phép một lập trình viên để làm hại. Trong ví dụ của tôi, nếu một số quá trình giả mạo gọi phương thức getNow(), thì không có tác hại thực sự nào được thực hiện.

+0

Có, tôi đang cố gắng nắm bắt phiên ngay trước khi kết nối cơ sở dữ liệu được liên kết hết giờ, vì vậy tôi có thể đóng kết nối một cách rõ ràng, thay vì hết thời gian chờ. Cảm ơn các ý kiến, quan điểm khác nhau là hữu ích. – Allan5

1

Có vẻ như chức năng đang được kiểm tra là SessionManager evitcs tất cả các phiên đã hết hạn.

Tôi sẽ xem xét việc tạo lớp thử nghiệm mở rộng DBSession.

AlwaysExpiredDBSession extends DBSession { 
.... 
// access time to be somewhere older 'NOW' 

} 
+0

Đây là một khả năng thú vị mặc dù đối tượng DBSession của tôi được tạo bởi đối tượng SessionManager mà tôi đang thử nghiệm, vì vậy tôi sẽ cần phải thực hiện một số cách để tiêm đối tượng. Giao diện hiện tại của tôi cho phép tạo và truy xuất DBSessions nhưng không chèn. Tôi nghĩ rằng tôi sẽ kết thúc đến cùng một vấn đề của việc phải sửa đổi một biến riêng trên đối tượng SessionManager thay vì một biến riêng của đối tượng DBSession. – Allan5

+0

Tôi đã suy nghĩ về điều này và nhận ra nó sẽ hoạt động.Trong trường hợp chung, cách tiếp cận này chắc chắn sẽ hoạt động, nhưng trong trường hợp của tôi, tôi đang tạo các đối tượng DBSession bằng cách sử dụng một nhà máy, vì vậy tôi chỉ cần chuyển một nhà máy khác đến SessionManager và nó sẽ hoạt động. – Allan5

0

tôi về cơ bản theo Gishu của đề nghị https://stackoverflow.com/a/10023832/1258214, nhưng tôi nghĩ tôi sẽ ghi lại những thay đổi chỉ vì lợi ích của bất cứ ai khác đọc bài viết này (và do đó bất cứ ai có thể bình luận về các vấn đề với việc thực hiện). Cảm ơn bạn đã bình luận chỉ tôi đến JodaTime và Mockito.

Ý tưởng liên quan là nhận ra sự phụ thuộc của mã theo thời gian và trích xuất ra (xem: https://stackoverflow.com/a/5622222/1258214). Điều này đã được thực hiện bằng cách tạo ra một giao diện:

import org.joda.time.DateTime; 

public interface Clock { 
    public DateTime getCurrentDateTime() ; 
} 

Sau đó, tạo ra một thực hiện:

import org.joda.time.DateTime; 

public class JodaClock implements Clock { 

    @Override 
    public DateTime getCurrentDateTime() { 
     return new DateTime(); 
    } 

} 

này sau đó đã được thông qua vào constructor SessionManager của:

SessionManager(ConnectionManager connMgr, SessionGenerator sessionGen, 
     ObjectFactory factory, Clock clock) { 

tôi sau đó có thể sử dụng mã tương tự với những gì Gishu đề xuất (lưu ý trường hợp thấp hơn 't' ở đầu testClear ... kiểm thử đơn vị của tôi rất thành công với chữ hoa 'T' cho đến khi tôi nhận ra rằng bài kiểm tra không chạy ...):

@Test 
public void testClearSessionsMaintainsSessionsUnlessLastAccessTimeIsOverThreshold() { 
    final String sessionId = "test"; 
    final Clock mockClock = mock(Clock.class); 

    when(mockClock.getCurrentDateTime()).thenReturn(getNow()); 
    SessionManager sessionMgr = getSessionManager(connMgr, 
      sessionGen, factory, mockClock); 
    createSession(sessionMgr, sessionId); 

    sessionMgr.clearSessions(defaultTimeout); 
    assertNotNull(sessionMgr.getSession(sessionId)); 

    when(mockClock.getCurrentDateTime()).thenReturn(getExpired()); 

    sessionMgr.clearSessions(defaultTimeout); 
    assertNull(sessionMgr.getSession(sessionId)); 
} 

này chạy tuyệt vời, nhưng loại bỏ tôi về Session.setAccessTime() tạo ra một vấn đề với một thử nghiệm testOnlyExpiredSessionsCleared() nơi tôi muốn một phiên sắp hết hạn nhưng không phải là khác. Liên kết này https://stackoverflow.com/a/6060814/1258214 dẫn tôi đến suy nghĩ về thiết kế của SessionManager.Phương thức clearSessions() và tôi đã tái cấu trúc việc kiểm tra nếu một phiên hết hạn từ SessionManager, đến chính đối tượng DBSession.

Từ:

if (session.getAccessTime().before(cal.getTime())) { 

Để:

if (session.isExpired(expireTime)) { 

Sau đó, tôi chèn một đối tượng mockSession (tương tự như đề nghị Jayan của https://stackoverflow.com/a/10023916/1258214)

@Test 
public void testOnlyOldSessionsCleared() { 
    final String sessionId = "test"; 
    final String sessionId2 = "test2"; 

    ObjectFactory mockFactory = spy(factory); 
    SessionManager sm = factory.createSessionManager(connMgr, sessionGen, 
     mockFactory, clock); 

    // create expired session 
    NPIISession session = factory.createNPIISession(null, clock); 
    NPIISession mockSession = spy(session); 
    // return session expired 
    doReturn(true).when(mockSession).isExpired((DateTime) anyObject()); 

    // get factory to return mockSession to sessionManager 
    doReturn(mockSession).when(mockFactory).createDBSession(
     (Connection) anyObject(), eq(clock)); 
    createSession(sm, sessionId); 

    // reset factory so return normal session 
    reset(mockFactory); 
    createSession(sm, sessionId2); 

    assertNotNull(sm.getSession(sessionId)); 
    assertNotNull(sm.getSession(sessionId2)); 

    sm.clearSessions(defaultTimeout); 
    assertNull(sm.getSession(sessionId)); 
    assertNotNull(sm.getSession(sessionId2)); 
} 

Nhờ mọi người giúp đỡ của họ với điều này . Vui lòng cho tôi biết nếu bạn thấy bất kỳ sự cố nào với các thay đổi.

Các vấn đề liên quan