2009-05-18 34 views
10

Giống như nó hay không, đôi khi bạn phải viết các bài kiểm tra cho các lớp học mà làm cho việc sử dụng nội bộ của bộ đếm thời gian.Làm cách nào để bạn kiểm tra các lớp đơn vị sử dụng bộ hẹn giờ trong nội bộ?

Nói ví dụ một lớp học mà các báo cáo về hệ thống sẵn có và đặt ra một sự kiện nếu hệ thống đã xuống quá lâu

public class SystemAvailabilityMonitor { 
    public event Action SystemBecameUnavailable = delegate { }; 
    public event Action SystemBecameAvailable = delegate { }; 
    public void SystemUnavailable() { 
     //.. 
    } 
    public void SystemAvailable() { 
     //.. 
    } 
    public SystemAvailabilityMonitor(TimeSpan bufferBeforeRaisingEvent) { 
     //.. 
    } 
} 

Tôi có một vài thủ thuật mà tôi sử dụng (sẽ đăng này như là một câu trả lời) nhưng tôi tự hỏi những gì người khác làm vì tôi không hoàn toàn hài lòng với một trong hai cách tiếp cận của tôi.

Trả lời

8

Tôi trích xuất bộ hẹn giờ từ đối tượng phản ứng với báo thức. Trong Java, bạn có thể chuyển nó thành một ScheduledExecutorService, ví dụ. Trong các bài kiểm tra đơn vị tôi vượt qua nó một thực hiện mà tôi có thể kiểm soát xác định, chẳng hạn như jMock's DeterministicScheduler.

+0

Vâng, rằng một số loại công văn kép (tôi nghĩ?) sẽ là cách tiếp cận lý tưởng, quá tệ. NET làm cho nó trở thành một rắc rối để làm điều này. Tôi thỉnh thoảng cuộn giao diện hẹn giờ của riêng mình nhưng luôn cảm thấy như tôi đang giới thiệu sự phức tạp. –

+0

Âm thanh giống như tiêm phụ thuộc hơn so với công văn đôi với tôi. –

+0

Thuật ngữ thuật ngữ nhỏ nhưng tôi cho rằng điều này đúng là gọi công văn kép hoặc khách truy cập.DI của nó chắc chắn nhưng bạn cũng đang dùng một đối tượng và yêu cầu nó áp dụng chính sách cập nhật của nó cho 'this'. –

0

Những cách mà tôi thường xử lý này là một trong hai

  1. Đặt hẹn giờ để đánh dấu từng 100 mili giây và con số mà khả năng của nó chủ đề của tôi sẽ được chuyển sang bởi sau đó. Điều này là khó xử và tạo ra một số kết quả không xác định.
  2. Chuyển sự kiện đã qua của bộ hẹn giờ thành sự kiện Tick() công khai hoặc được bảo vệ. Sau đó, từ bài kiểm tra thiết lập khoảng thời gian của bộ định thời cho một cái gì đó rất lớn và kích hoạt phương thức Tick() theo cách thủ công từ thử nghiệm. Điều này cung cấp cho bạn các bài kiểm tra xác định nhưng có một số điều mà bạn không thể thử nghiệm với phương pháp này.
0

Tôi tái cấu trúc các giá trị thời gian này là tham số cho phương pháp và sau đó tạo một phương pháp khác không thực hiện được gì ngoài chuyển thông số chính xác. Bằng cách đó tất cả các hành vi thực tế được cô lập và dễ dàng kiểm tra trên tất cả các trường hợp cạnh khủng khiếp chỉ để lại chèn thông số rất tầm thường chưa được kiểm tra.

Như một ví dụ cực kỳ tầm thường, nếu tôi bắt đầu với điều này:

public long timeElapsedSinceJan012000() 
{ 
    Date now = new Date(); 
    Date jan2000 = new Date(2000, 1, 1); // I know...deprecated...bear with me 
    long difference = now - jan2000; 
    return difference; 
} 

tôi sẽ cấu trúc lại điều này, và đơn vị kiểm tra Phương pháp thứ hai:

public long timeElapsedSinceJan012000() 
{ 
    return calcDifference(new Date()); 
} 

public long calcDifference(Date d) { 
    Date jan2000 = new Date(2000, 1, 1); 
    long difference = d - jan2000; 
    return difference; 
} 
+1

Tôi không chắc chắn tôi nhận được nó, làm thế nào điều này liên quan đến giờ? –

1

Âm thanh như người ta nên thử hẹn giờ nhưng than ôi ... sau khi Google một cách nhanh chóng this other SO question với một số câu trả lời là hit tìm kiếm hàng đầu. Nhưng sau đó tôi bắt được khái niệm về câu hỏi là về các lớp học sử dụng bộ đếm thời gian nội bộ, doh. Nhưng dù sao, khi lập trình trò chơi/động cơ - đôi khi bạn vượt qua các bộ tính giờ làm tham số tham chiếu cho các nhà xây dựng - điều này sẽ khiến họ có thể chế nhạo chúng một lần nữa tôi đoán? Nhưng sau đó một lần nữa, tôi là coder noob ^^

+0

Không có gì đúng, cách lý tưởng để làm điều đó là vượt qua đối tượng hẹn giờ, vấn đề duy nhất là điều này phá vỡ một số quy ước của .NET framework và cảm thấy nặng nề nặng nề đối với một số thứ (bây giờ tôi phải cấu hình container IoC của mình với nhưng một đối tượng khác chẳng hạn) –

3

Đây là những gì tôi đang sử dụng. Tôi tìm thấy nó trong cuốn sách: Kiểm tra lái xe - TDD thực tế và TDD chấp nhận cho nhà phát triển Java bởi Lasse Koskela.

public interface TimeSource { 
    long millis(); 
} 


public class SystemTime { 

    private static TimeSource source = null; 

    private static final TimeSource DEFAULTSRC = 
     new TimeSource() { 
     public long millis() { 
      return System.currentTimeMillis(); 
     } 
    }; 


    private static TimeSource getTimeSource() { 
     TimeSource answer; 
     if (source == null) { 
      answer = DEFAULTSRC; 
     } else { 
      answer = source; 
     } 
     return answer; 
    } 

    public static void setTimeSource(final TimeSource timeSource) { 
     SystemTime.source = timeSource; 
    } 

    public static void reset() { 
     setTimeSource(null); 
    } 

    public static long asMillis() { 
     return getTimeSource().millis(); 
    } 

    public static Date asDate() { 
     return new Date(asMillis()); 
    } 

} 

Lưu ý rằng nguồn thời gian mặc định, DEFAULTSRC, là System.currentTimeMillis(). Nó được thay thế trong các bài kiểm tra đơn vị; tuy nhiên, hành vi bình thường là thời gian hệ thống tiêu chuẩn.

Đây là nơi nó được sử dụng:

public class SimHengstler { 

    private long lastTime = 0; 

    public SimHengstler() { 
     lastTime = SystemTime.asMillis(); //System.currentTimeMillis(); 
    } 
} 

Và đây là bài kiểm tra đơn vị:

import com.company.timing.SystemTime; 
import com.company.timing.TimeSource; 

public class SimHengstlerTest { 
    @After 
    public void tearDown() { 
     SystemTime.reset(); 
    } 

    @Test 
    public final void testComputeAccel() { 
     // Setup 
     setStartTime(); 
     SimHengstler instance = new SimHengstler(); 
     setEndTime(1020L); 
    } 
    private void setStartTime() { 
     final long fakeStartTime = 1000L; 
     SystemTime.setTimeSource(new TimeSource() { 
      public long millis() { 
       return fakeStartTime; 
      } 
     }); 
    } 
    private void setEndTime(final long t) { 
     final long fakeEndTime = t; // 20 millisecond time difference 
     SystemTime.setTimeSource(new TimeSource() { 
      public long millis() { 
       return fakeEndTime; 
      } 
     }); 
    } 

Trong thử nghiệm đơn vị, tôi thay thế TimeSource chỉ với một số trong đó đã được thiết lập đến 1000 mili giây. Điều đó sẽ phục vụ như là thời gian bắt đầu. Khi gọi setEndTime(), tôi nhập 1020 mili giây cho thời gian hoàn thành. Điều này đã cho tôi một sự khác biệt thời gian 20 phần nghìn giây được kiểm soát.

Không có mã thử nghiệm trong mã sản xuất, chỉ nhận được Giờ hệ thống bình thường.

Đảm bảo đặt lại cuộc gọi sau khi thử nghiệm để quay lại sử dụng phương pháp thời gian của hệ thống thay vì thời gian giả.

0

Tôi nhận ra đây là một câu hỏi Java, nhưng nó có thể được quan tâm để hiển thị như thế nào được thực hiện trong thế giới Perl. Bạn có thể chỉ cần ghi đè lên các chức năng thời gian cốt lõi trong các bài kiểm tra của bạn. :) Điều này có vẻ đáng sợ, nhưng điều đó có nghĩa là bạn không cần phải tiêm thêm toàn bộ sự vô hướng vào mã sản xuất của bạn chỉ để kiểm tra nó. Test::MockTime là một ví dụ. Thời gian đóng băng trong thử nghiệm của bạn làm cho một số điều dễ dàng hơn nhiều. Giống như những thử nghiệm so sánh thời gian không nguyên tử nhạy cảm này, nơi bạn chạy một cái gì đó vào thời gian X và vào thời điểm bạn kiểm tra X + 1 của nó. Có một ví dụ trong đoạn code dưới đây.

Thông thường hơn một chút, gần đây tôi đã có một lớp PHP để lấy dữ liệu từ cơ sở dữ liệu bên ngoài. Tôi muốn nó xảy ra nhiều nhất một lần mỗi X giây. Để kiểm tra nó, tôi đặt cả thời gian cập nhật cuối cùng và khoảng thời gian cập nhật làm thuộc tính của đối tượng. Ban đầu tôi đã tạo ra các hằng số, vì vậy thay đổi này để thử nghiệm cũng cải thiện mã. Sau đó, kiểm tra có thể fiddle với những giá trị như vậy:

function testUpdateDelay() { 
    $thing = new Thing; 

    $this->assertTrue($thing->update, "update() runs the first time"); 

    $this->assertFalse($thing->update, "update() won't run immediately after"); 

    // Simulate being just before the update delay runs out 
    $just_before = time() - $thing->update_delay + 2; 
    $thing->update_ran_at = $just_before; 
    $this->assertFalse($thing->update, "update() won't run just before the update delay runs out"); 
    $this->assertEqual($thing->update_ran_at, $just_before, "update_ran_at unchanged"); 

    // Simulate being just after 
    $just_after = time() - $thing->update_delay - 2; 
    $thing->update_ran_at = $just_after; 
    $this->assertTrue($thing->update, "update() will run just after the update delay runs out"); 

    // assertAboutEqual() checks two numbers are within N of each other. 
    // where N here is 1. This is to avoid a clock tick between the update() and the 
    // check 
    $this->assertAboutEqual($thing->update_ran_at, time(), 1, "update_ran_at updated"); 
} 
4

Nếu bạn đang tìm kiếm câu trả lời cho vấn đề này, bạn có thể quan tâm trong blog này: http://thorstenlorenz.blogspot.com/2009/07/mocking-timer.html

Trong đó tôi giải thích một cách để ghi đè hành vi thông thường của lớp System.Timers.Timer, để kích hoạt nó trên Start().

Dưới đây là phiên bản ngắn:

class FireOnStartTimer : System.Timers.Timer 
{ 
public new event System.Timers.ElapsedEventHandler Elapsed; 

public new void Start() 
{ 
    this.Elapsed.Invoke(this, new EventArgs() as System.Timers.ElapsedEventArgs); 
} 
} 

Tất nhiên điều này đòi hỏi bạn phải có khả năng để vượt qua bộ đếm thời gian vào lớp dưới kiểm tra. Nếu điều này là không thể, thì thiết kế của lớp là thiếu sót khi nói đến testability vì nó không hỗ trợ tiêm phụ thuộc. Bạn nên thay đổi thiết kế của nó nếu bạn có thể. Nếu không, bạn có thể không may mắn và không thể kiểm tra bất cứ điều gì về lớp đó liên quan đến bộ đếm thời gian bên trong của nó.

Để được giải thích kỹ lưỡng hơn hãy truy cập blog.

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