Ghi đè Java System.cienTimeMillis để kiểm tra mã nhạy cảm thời gian


129

Có cách nào, bằng mã hoặc với các đối số JVM, để ghi đè thời gian hiện tại, như được trình bày qua System.currentTimeMillis, ngoài việc thay đổi thủ công đồng hồ hệ thống trên máy chủ không?

Một chút nền tảng:

Chúng tôi có một hệ thống điều hành một số công việc kế toán xoay quanh phần lớn logic của họ vào ngày hiện tại (tức là ngày đầu tiên của tháng, ngày đầu tiên của năm, v.v.)

Thật không may, rất nhiều mã kế thừa gọi các hàm như , new Date()hoặc Calendar.getInstance()cả hai cuối cùng đều gọi xuống System.currentTimeMillis.

Đối với mục đích thử nghiệm, ngay bây giờ, chúng tôi bị mắc kẹt với việc cập nhật thủ công đồng hồ hệ thống để thao tác thời gian và ngày mà mã nghĩ rằng thử nghiệm đang được chạy.

Vì vậy, câu hỏi của tôi là:

Có cách nào để ghi đè những gì được trả về System.currentTimeMillis? Ví dụ, để báo cho JVM tự động thêm hoặc bớt một số bù trước khi quay lại từ phương thức đó?

Cảm ơn trước!


1
Tôi không biết liệu nó có liên quan nữa hay không, nhưng có một phương pháp khác để đạt được điều này với AspectJ, hãy xem câu trả lời của tôi tại: stackoverflow.com/questions/18239859/
Lỗi

@ NándorElődFekete giải pháp trong liên kết rất thú vị, tuy nhiên nó yêu cầu biên dịch lại mã. Tôi tự hỏi nếu người đăng ban đầu có khả năng biên dịch lại, thực tế là anh ta tuyên bố đang xử lý mã kế thừa.
cleberz

1
@cleberz Một trong những thuộc tính tuyệt vời của AspectJ là hoạt động trực tiếp trên mã byte, do đó, nó không cần mã nguồn gốc để hoạt động.
Nándor Előd Fekete

@ NándorElődFekete cảm ơn bạn rất nhiều vì gợi ý. Tôi đã không biết về thiết bị đo mức byte byJ (đặc biệt là thiết bị đo lớp JDK). Phải mất một thời gian nhưng tôi mới có thể nhận ra mình phải dệt cả rt.jar thời gian biên dịch cũng như dệt thời gian tải của các lớp không phải jdk để phù hợp với nhu cầu của tôi (ghi đè Hệ thống. currentTimeMillis () và System.nanoTime ()).
cleberz

@cleberz Tôi có một câu trả lời khác cho việc dệt qua các lớp JRE , bạn cũng có thể kiểm tra nó.
Nándor Előd Fekete

Câu trả lời:


116

Tôi mạnh mẽ khuyên rằng thay vì can thiệp vào đồng hồ hệ thống, bạn cắn đạn và cấu trúc lại mã di sản để sử dụng một chiếc đồng hồ có thể thay thế. Lý tưởng nhất là nên được thực hiện với tiêm phụ thuộc, nhưng ngay cả khi bạn đã sử dụng một singleton có thể thay thế, bạn sẽ có được khả năng kiểm tra.

Điều này gần như có thể được tự động hóa với tìm kiếm và thay thế cho phiên bản singleton:

  • Thay thế Calendar.getInstance()bằng Clock.getInstance().getCalendarInstance().
  • Thay thế new Date()bằngClock.getInstance().newDate()
  • Thay thế System.currentTimeMillis()bằngClock.getInstance().currentTimeMillis()

(vv theo yêu cầu)

Khi bạn đã thực hiện bước đầu tiên đó, bạn có thể thay thế singleton bằng DI một chút.


49
Làm lộn xộn mã của bạn bằng cách trừu tượng hóa hoặc gói tất cả các API tiềm năng để tăng khả năng kiểm tra là IMHO không phải là một ý tưởng hay. Ngay cả khi bạn có thể thực hiện tái cấu trúc một lần với một tìm kiếm & thay thế đơn giản, mã trở nên khó đọc hơn, hiểu và duy trì hơn nhiều. Một số khung giả định phải có thể sửa đổi hành vi của System.cienTimeMillis, nếu không, sử dụng AOP hoặc tự tạo công cụ là sự lựa chọn tốt hơn.
jarnbjo

21
@jarnbjo: Tất nhiên, bạn hoan nghênh ý kiến ​​của bạn - nhưng đây là một kỹ thuật IMO khá thành công và chắc chắn tôi đã sử dụng nó rất hiệu quả. Nó không chỉ cải thiện khả năng kiểm tra - nó còn làm cho sự phụ thuộc của bạn vào thời gian hệ thống rõ ràng, có thể hữu ích khi có được một cái nhìn tổng quan về mã.
Jon Skeet

4
@ virgo47: Tôi nghĩ chúng ta sẽ phải đồng ý không đồng ý. Tôi không thấy "nói rõ sự phụ thuộc của bạn" khi làm ô nhiễm mã - tôi thấy đó là một cách tự nhiên để làm cho mã rõ ràng hơn. Tôi cũng không thấy việc che giấu những phụ thuộc đó thông qua các cuộc gọi tĩnh một cách không cần thiết là "hoàn toàn chấp nhận được".
Jon Skeet

26
CẬP NHẬT Gói java.time mới được tích hợp trong Java 8 bao gồm một java.time.Clocklớp "để cho phép các đồng hồ thay thế được cắm vào và khi được yêu cầu".
Basil Bourque

7
Câu trả lời này ngụ ý rằng DI là tất cả tốt. Cá nhân, tôi vẫn chưa thấy một ứng dụng trong thế giới thực sử dụng tốt nó. Thay vào đó, chúng ta thấy một sự giao thoa các giao diện riêng biệt vô nghĩa, "đối tượng" không trạng thái, "đối tượng" chỉ dữ liệu và các lớp kết dính thấp. IMO, đơn giản và thiết kế hướng đối tượng (với các đối tượng thực, trạng thái) là một lựa chọn tốt hơn. Về staticcác phương pháp, chắc chắn rằng chúng không phải là OO, nhưng tiêm một phụ thuộc không trạng thái mà cá thể không hoạt động trên bất kỳ trạng thái cá thể nào không thực sự tốt hơn; dù sao đó cũng chỉ là một cách ngụy trang để che giấu hành vi "tĩnh" hiệu quả.
Rogério

77

tl; dr

Có cách nào, bằng mã hoặc với các đối số JVM, để ghi đè thời gian hiện tại, như được trình bày qua System.civerseTimeMillis, ngoài việc thay đổi thủ công đồng hồ hệ thống trên máy chủ không?

Đúng.

Instant.now( 
    Clock.fixed( 
        Instant.parse( "2016-01-23T12:34:56Z"), ZoneOffset.UTC
    )
)

Clock Trong java.time

Chúng tôi có một giải pháp mới cho vấn đề thay thế đồng hồ có thể cắm để tạo điều kiện kiểm tra với các giá trị ngày giờ giả . Các gói java.time trong Java 8 bao gồm một lớp trừu tượng java.time.Clock, với một mục đích rõ ràng:

để cho phép đồng hồ thay thế được cắm vào và khi cần thiết

Bạn có thể tự mình thực hiện Clock, mặc dù bạn có thể tìm thấy một cái đã được thực hiện để đáp ứng nhu cầu của bạn. Để thuận tiện cho bạn, java.time bao gồm các phương thức tĩnh để mang lại các triển khai đặc biệt. Những triển khai thay thế này có thể có giá trị trong quá trình thử nghiệm.

Thay đổi nhịp

Các tick…phương pháp khác nhau tạo ra đồng hồ làm tăng thời điểm hiện tại với một nhịp khác nhau.

Mặc định Clockbáo cáo thời gian được cập nhật thường xuyên như mili giây trong Java 8 và trong Java 9 tốt như nano giây (tùy thuộc vào phần cứng của bạn). Bạn có thể yêu cầu thời điểm hiện tại thực sự được báo cáo với độ chi tiết khác nhau.

Đồng hồ sai

Một số đồng hồ có thể nói dối, tạo ra kết quả khác với đồng hồ phần cứng của hệ điều hành chủ.

  • fixed - Báo cáo một khoảnh khắc không thay đổi (không tăng) như thời điểm hiện tại.
  • offset- Báo cáo thời điểm hiện tại nhưng thay đổi bởi các Durationđối số được thông qua .

Ví dụ, khóa trong khoảnh khắc đầu tiên của Giáng sinh sớm nhất trong năm nay. nói cách khác, khi ông già Noel và tuần lộc của mình dừng chân đầu tiên . Múi giờ sớm nhất hiện nay dường như là Pacific/Kiritimatitại +14:00.

LocalDate ld = LocalDate.now( ZoneId.of( "America/Montreal" ) );
LocalDate xmasThisYear = MonthDay.of( Month.DECEMBER , 25 ).atYear( ld.getYear() );
ZoneId earliestXmasZone = ZoneId.of( "Pacific/Kiritimati" ) ;
ZonedDateTime zdtEarliestXmasThisYear = xmasThisYear.atStartOfDay( earliestXmasZone );
Instant instantEarliestXmasThisYear = zdtEarliestXmasThisYear.toInstant();
Clock clockEarliestXmasThisYear = Clock.fixed( instantEarliestXmasThisYear , earliestXmasZone );

Sử dụng đồng hồ cố định đặc biệt đó để luôn luôn trả lại cùng một thời điểm. Chúng tôi có được khoảnh khắc đầu tiên của ngày Giáng sinh ở Kiritimati , với UTC hiển thị thời gian đồng hồ treo tường là mười bốn giờ trước đó, 10 giờ sáng vào ngày trước của ngày 24 tháng 12.

Instant instant = Instant.now( clockEarliestXmasThisYear );
ZonedDateTime zdt = ZonedDateTime.now( clockEarliestXmasThisYear );

tức thì.toString (): 2016-12-24T10: 00: 00Z

zdt.toString (): 2016-12-25T00: 00 + 14: 00 [Thái Bình Dương / Kiritimati]

Xem mã trực tiếp trong IdeOne.com .

Đúng giờ, múi giờ khác

Bạn có thể kiểm soát múi giờ nào được chỉ định bởi việc Clockthực hiện. Điều này có thể hữu ích trong một số thử nghiệm. Nhưng tôi không khuyến nghị điều này trong mã sản xuất, nơi bạn phải luôn chỉ định rõ ràng các tùy chọn ZoneIdhoặc ZoneOffsetđối số.

Bạn có thể chỉ định rằng UTC là vùng mặc định.

ZonedDateTime zdtClockSystemUTC = ZonedDateTime.now ( Clock.systemUTC () );

Bạn có thể chỉ định bất kỳ múi giờ cụ thể. Chỉ định một tên múi giờ thích hợp trong các định dạng của continent/region, chẳng hạn như America/Montreal, Africa/Casablancahoặc Pacific/Auckland. Không bao giờ sử dụng chữ viết tắt 3-4 chữ cái như ESThoặc ISTchúng không phải là múi giờ thực, không được chuẩn hóa và thậm chí không phải là duy nhất (!).

ZonedDateTime zdtClockSystem = ZonedDateTime.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );

Bạn có thể chỉ định múi giờ mặc định hiện tại của JVM phải là mặc định cho một Clockđối tượng cụ thể.

ZonedDateTime zdtClockSystemDefaultZone = ZonedDateTime.now ( Clock.systemDefaultZone () );

Chạy mã này để so sánh. Lưu ý rằng tất cả họ báo cáo cùng một thời điểm, cùng một điểm trên dòng thời gian. Chúng chỉ khác nhau về thời gian đồng hồ treo tường ; nói cách khác, ba cách để nói cùng một điều, ba cách để hiển thị cùng một khoảnh khắc.

System.out.println ( "zdtClockSystemUTC.toString(): " + zdtClockSystemUTC );
System.out.println ( "zdtClockSystem.toString(): " + zdtClockSystem );
System.out.println ( "zdtClockSystemDefaultZone.toString(): " + zdtClockSystemDefaultZone );

America/Los_Angeles là vùng mặc định hiện tại của JVM trên máy tính chạy mã này.

zdtClockSystemUTC.toString (): 2016-12-31T20: 52: 39.688Z

zdtClockSystem.toString (): 2016-12-31T15: 52: 39.750-05: 00 [Mỹ / Montreal]

zdtClockSystemDefaultZone.toString (): 2016-12-31T12: 52: 39.762-08: 00 [America / Los_Angele]

Các Instantlớp luôn trong UTC theo định nghĩa. Vì vậy, ba công Clockdụng liên quan đến khu vực này có tác dụng chính xác như nhau.

Instant instantClockSystemUTC = Instant.now ( Clock.systemUTC () );
Instant instantClockSystem = Instant.now ( Clock.system ( ZoneId.of ( "America/Montreal" ) ) );
Instant instantClockSystemDefaultZone = Instant.now ( Clock.systemDefaultZone () );

tức thìClockSystemUTC.toString (): 2016-12-31T20: 52: 39.763Z

tức thìClockSystem.toString (): 2016-12-31T20: 52: 39.763Z

tức thìClockSystemDefaultZone.toString (): 2016-12-31T20: 52: 39.763Z

Đồng hồ mặc định

Việc thực hiện được sử dụng theo mặc định Instant.nowlà một trong đó được trả về Clock.systemUTC(). Đây là việc thực hiện được sử dụng khi bạn không chỉ định a Clock. Xem cho chính mình trong mã nguồn Java 9 trước khi phát hànhInstant.now .

public static Instant now() {
    return Clock.systemUTC().instant();
}

Mặc định Clockcho OffsetDateTime.nowZonedDateTime.nowClock.systemDefaultZone(). Xem mã nguồn .

public static ZonedDateTime now() {
    return now(Clock.systemDefaultZone());
}

Hành vi của các triển khai mặc định đã thay đổi giữa Java 8 và Java 9. Trong Java 8, thời điểm hiện tại được ghi lại với độ phân giải chỉ tính bằng mili giây mặc dù khả năng của các lớp là lưu trữ độ phân giải nano giây . Java 9 mang đến một triển khai mới có thể ghi lại khoảnh khắc hiện tại với độ phân giải nano giây - tất nhiên phụ thuộc vào khả năng của đồng hồ phần cứng máy tính của bạn.


Giới thiệu về java.time

Các java.time khung được xây dựng vào Java 8 và sau đó. Những lớp học thay thế cái cũ phiền hà di sản lớp học ngày thời gian như java.util.Date, Calendar, & SimpleDateFormat.

Các Joda thời gian dự án, bây giờ trong chế độ bảo trì , khuyên chuyển đổi sang các java.time lớp.

Để tìm hiểu thêm, xem Hướng dẫn Oracle . Và tìm kiếm Stack Overflow cho nhiều ví dụ và giải thích. Đặc điểm kỹ thuật là JSR 310 .

Bạn có thể trao đổi các đối tượng java.time trực tiếp với cơ sở dữ liệu của bạn. Sử dụng trình điều khiển JDBC tương thích với JDBC 4.2 trở lên. Không cần chuỗi, không cần java.sql.*lớp.

Nơi để có được các lớp java.time?

  • Java SE 8 , Java SE 9 và mới hơn
    • Được xây dựng trong.
    • Một phần của API Java tiêu chuẩn với việc triển khai đi kèm.
    • Java 9 thêm một số tính năng nhỏ và sửa lỗi.
  • Java SE 6 Java SE 7
    • Phần lớn chức năng java.time được chuyển ngược lại sang Java 6 & 7 trong ThreeTen-Backport .
  • Android
    • Các phiên bản sau của triển khai gói Android của các lớp java.time.
    • Đối với Android trước đó (<26), các ThreeTenABP dự án thích nghi ThreeTen-backport (nêu trên). Xem Cách sử dụng ThreeTenABP .

Các ThreeTen-Extra dự án mở rộng java.time với các lớp bổ sung. Dự án này là một nền tảng chứng minh cho các bổ sung có thể trong tương lai cho java.time. Bạn có thể tìm thấy một số các lớp học hữu ích ở đây chẳng hạn như Interval, YearWeek, YearQuarter, và nhiều hơn nữa .


42

Như đã nói của Jon Skeet :

"sử dụng Joda Time" hầu như luôn là câu trả lời tốt nhất cho bất kỳ câu hỏi nào liên quan đến "làm thế nào để tôi đạt được X với java.util.Date/CalWiki?"

Vì vậy, ở đây đi (giả sử bạn vừa thay thế tất cả của bạn new Date()với new DateTime().toDate())

//Change to specific time
DateTimeUtils.setCurrentMillisFixed(millis);
//or set the clock to be a difference from system time
DateTimeUtils.setCurrentMillisOffset(millis);
//Reset to system time
DateTimeUtils.setCurrentMillisSystem();

Nếu bạn muốn nhập thư viện có giao diện (xem bình luận của Jon bên dưới), bạn chỉ có thể sử dụng Đồng hồ của Prevayler , nơi sẽ cung cấp các triển khai cũng như giao diện chuẩn. Bình đầy đủ chỉ 96kB, vì vậy nó không nên phá vỡ ngân hàng ...


13
Không, tôi sẽ không khuyến nghị điều này - nó không đưa bạn tới một chiếc đồng hồ có thể thay thế thực sự; nó giữ cho bạn sử dụng thống kê trong suốt. Sử dụng Joda Time tất nhiên là một ý tưởng hay, nhưng tôi vẫn muốn sử dụng giao diện Đồng hồ hoặc tương tự.
Jon Skeet

@Jon: Đúng - để thử nghiệm, không sao, nhưng tốt hơn là nên có giao diện.
Stephen

5
@Jon: Đây là một cách hoàn hảo và dễ dàng IMHO nếu bạn muốn kiểm tra mã phụ thuộc thời gian đã sử dụng JodaTime. Cố gắng phát minh lại bánh xe bằng cách giới thiệu một lớp / khung trừu tượng khác không phải là cách để đi, nếu mọi thứ đã được giải quyết cho bạn với JodaTime.
Stefan Haberl

2
@JonSkeet: Đây không phải là những gì Mike đã yêu cầu ban đầu. Ông muốn có một công cụ để kiểm tra mã phụ thuộc thời gian và không phải là đồng hồ có thể thay thế hoàn toàn trong mã sản xuất. Tôi thích giữ kiến ​​trúc của mình phức tạp khi cần nhưng càng đơn giản càng tốt. Giới thiệu một lớp trừu tượng bổ sung (tức là giao diện) ở đây đơn giản là không cần thiết
Stefan Haberl

2
@StefanHaberl: Có nhiều điều không thực sự "cần thiết" - nhưng thực sự những gì tôi đề xuất là làm cho một sự phụ thuộc đã có mặt rõ ràng và dễ dàng kiểm chứng hơn trong sự cô lập. Làm cho thời gian hệ thống với statics có tất cả các vấn đề bình thường của statics - đó là trạng thái toàn cầu không có lý do chính đáng . Mike đang yêu cầu một cách để viết mã có thể kiểm tra được sử dụng ngày và giờ hiện tại. Tôi vẫn tin rằng đề xuất của tôi sạch sẽ hơn (và làm cho sự phụ thuộc rõ ràng hơn) hơn là làm mờ một cách hiệu quả một tĩnh.
Jon Skeet

15

Mặc dù sử dụng một số mẫu DateFactory có vẻ tốt, nhưng nó không bao gồm các thư viện mà bạn không thể kiểm soát - hãy tưởng tượng chú thích Xác thực @Past với việc triển khai dựa trên System.cienTimeMillis (có như vậy).

Đó là lý do tại sao chúng tôi sử dụng jmockit để mô phỏng trực tiếp thời gian hệ thống:

import mockit.Mock;
import mockit.MockClass;
...
@MockClass(realClass = System.class)
public static class SystemMock {
    /**
     * Fake current time millis returns value modified by required offset.
     *
     * @return fake "current" millis
     */
    @Mock
    public static long currentTimeMillis() {
        return INIT_MILLIS + offset + millisSinceClassInit();
    }
}

Mockit.setUpMock(SystemMock.class);

Vì không thể đạt được giá trị ban đầu của millis, chúng tôi sử dụng bộ đếm thời gian nano - điều này không liên quan đến đồng hồ treo tường, nhưng thời gian tương đối đủ ở đây:

// runs before the mock is applied
private static final long INIT_MILLIS = System.currentTimeMillis();
private static final long INIT_NANOS = System.nanoTime();

private static long millisSinceClassInit() {
    return (System.nanoTime() - INIT_NANOS) / 1000000;
}

Có một vấn đề được ghi nhận rằng với HotSpot, thời gian trở lại bình thường sau một số cuộc gọi - đây là báo cáo sự cố: http://code.google.com.vn/p/jmockit/issues/detail?id=43

Để khắc phục điều này, chúng ta phải bật một tối ưu hóa HotSpot cụ thể - chạy JVM với đối số này -XX:-Inline.

Mặc dù điều này có thể không hoàn hảo cho sản xuất, nhưng nó chỉ tốt cho các thử nghiệm và nó hoàn toàn minh bạch cho ứng dụng, đặc biệt là khi DataFactory không có ý nghĩa kinh doanh và chỉ được giới thiệu vì các thử nghiệm. Sẽ thật tuyệt nếu có tùy chọn JVM tích hợp để chạy trong thời gian khác nhau, quá tệ là không thể nếu không có các bản hack như thế này.

Câu chuyện hoàn chỉnh có trong bài đăng trên blog của tôi ở đây: http://virgo47.wordpress.com/2012/06/22/changing-system-time-in-java/

Hoàn thành lớp tiện dụng SystemTimeShifter được cung cấp trong bài. Lớp có thể được sử dụng trong các thử nghiệm của bạn hoặc nó có thể được sử dụng làm lớp chính đầu tiên trước lớp chính thực sự của bạn rất dễ dàng để chạy ứng dụng của bạn (hoặc thậm chí toàn bộ máy chủ ứng dụng) trong một thời gian khác. Tất nhiên, điều này được dự định cho mục đích thử nghiệm là chủ yếu, không phải cho môi trường sản xuất.

EDIT tháng 7 năm 2014: JMockit đã thay đổi rất nhiều thời gian gần đây và bạn nhất định sử dụng JMockit 1.0 để sử dụng chính xác (IIRC). Chắc chắn không thể nâng cấp lên phiên bản mới nhất nơi giao diện hoàn toàn khác nhau. Tôi đã suy nghĩ về nội tuyến chỉ là những thứ cần thiết, nhưng vì chúng tôi không cần thứ này trong các dự án mới của mình nên tôi không phát triển thứ này chút nào.


1
Thực tế là bạn đang thay đổi thời gian cho mọi lớp học theo cả hai cách ... ví dụ: nếu bạn giả lập nanoTime thay vì currentTimeMillis, bạn phải hết sức cẩn thận để không phá vỡ java.util.conc hiện. Theo nguyên tắc thông thường, nếu thư viện của bên thứ 3 khó sử dụng trong các bài kiểm tra, bạn không nên chế giễu các đầu vào của thư viện: bạn nên tự chế giễu thư viện.
Dan Berindei

1
Chúng tôi sử dụng kỹ thuật này cho các thử nghiệm mà chúng tôi không chế nhạo bất cứ điều gì khác. Đây là thử nghiệm tích hợp và chúng tôi kiểm tra toàn bộ ngăn xếp xem nó có hoạt động như thế nào khi một số hoạt động xảy ra lần đầu tiên trong năm, v.v. Điểm hay về nanoTime, may mắn là chúng ta không cần phải chế giễu rằng nó không có đồng hồ treo tường có ý nghĩa gì cả Tôi rất thích thấy Java với các tính năng thay đổi thời gian vì tôi cần chúng hơn một lần và làm rối tung thời gian hệ thống vì điều này rất không may mắn.
virgo47

7

Powermock hoạt động tuyệt vời. Chỉ cần sử dụng nó để chế nhạo System.currentTimeMillis().


Tôi đã thử sử dụng Powermock và nếu tôi hiểu chính xác thì nó sẽ không thực sự nhạo báng. Thay vào đó, nó tìm thấy tất cả người gọi và hơn là áp dụng giả. Điều này có thể cực kỳ tốn thời gian vì bạn phải để nó kiểm tra MỌI lớp trong đường dẫn lớp của bạn nếu bạn muốn thực sự chắc chắn rằng ứng dụng của bạn hoạt động trong thời gian thay đổi. Chỉnh sửa cho tôi nếu tôi sai về cách chế giễu Powermock.
virgo47

1
@ virgo47 Không, bạn hiểu sai rồi. PowerMock sẽ không bao giờ "kiểm tra mọi lớp trong đường dẫn của bạn"; nó sẽ chỉ kiểm tra các lớp mà bạn đặc biệt yêu cầu nó kiểm tra (thông qua @PrepareForTestchú thích trên lớp kiểm tra).
Rogério

1
@ Rogério - đó chính xác là cách tôi hiểu về nó - Tôi đã nói "bạn phải để nó ...". Đó là nếu bạn mong đợi cuộc gọi đến System.currentTimeMillisbất cứ nơi nào trên đường dẫn lớp của bạn (bất kỳ lib), bạn phải kiểm tra mọi lớp. Ý tôi là vậy, không có gì khác. Vấn đề là bạn chế giễu hành vi đó ở phía người gọi ("bạn nói cụ thể"). Điều này ổn đối với thử nghiệm đơn giản nhưng không phải đối với các thử nghiệm mà bạn không thể chắc chắn những gì gọi phương thức đó từ đâu (ví dụ: các thử nghiệm thành phần phức tạp hơn với các thư viện liên quan). Điều đó không có nghĩa là Powermock hoàn toàn sai, nó chỉ có nghĩa là bạn không thể sử dụng nó cho loại thử nghiệm này.
virgo47

1
@ virgo47 Vâng, tôi hiểu ý của bạn. Tôi nghĩ bạn có nghĩa là PowerMock sẽ phải tự động kiểm tra mọi lớp trong đường dẫn lớp, điều này rõ ràng là vô lý. Tuy nhiên, trong thực tế, đối với các bài kiểm tra đơn vị, chúng ta thường biết lớp nào đọc thời gian, vì vậy việc xác định nó không phải là vấn đề; đối với các thử nghiệm tích hợp, thực sự, yêu cầu trong PowerMock phải có tất cả các lớp sử dụng được chỉ định có thể là một vấn đề.
Rogério

6

Sử dụng Lập trình hướng theo khía cạnh (AOP, ví dụ AspectJ) để dệt lớp Hệ thống để trả về giá trị được xác định trước mà bạn có thể đặt trong các trường hợp thử nghiệm của mình.

Hoặc dệt các lớp ứng dụng để chuyển hướng cuộc gọi đến System.currentTimeMillis()hoặc new Date()đến một lớp tiện ích khác của riêng bạn.

java.lang.*Tuy nhiên, các lớp hệ thống dệt ( ) phức tạp hơn một chút và bạn có thể cần thực hiện dệt ngoại tuyến cho rt.jar và sử dụng JDK / rt.jar riêng cho các thử nghiệm của mình.

Nó được gọi là dệt nhị phân và cũng có các công cụ đặc biệt để thực hiện dệt các lớp Hệ thống và khắc phục một số vấn đề với điều đó (ví dụ: bootstrapping VM có thể không hoạt động)


Điều này hoạt động, tất nhiên, nhưng nó dễ thực hiện hơn với một công cụ giả định so với công cụ AOP; loại công cụ thứ hai là quá chung chung cho mục đích trong câu hỏi.
Rogério

3

Thực sự không có cách nào để thực hiện điều này trực tiếp trong VM, nhưng bạn hoàn toàn có thể thiết lập thời gian hệ thống trên máy kiểm tra theo chương trình. Hầu hết (tất cả?) Hệ điều hành có các lệnh dòng lệnh để làm điều này.


Ví dụ, trên windows datetimecác lệnh. Trên Linux datelệnh.
Jeremy Raymond

Tại sao không nên làm điều này với AOP hoặc công cụ hóa (đã được thực hiện điều đó)?
jarnbjo

3

Một cách làm việc để ghi đè thời gian hệ thống hiện tại cho mục đích thử nghiệm JUnit trong ứng dụng web Java 8 với EasyMock, không có Joda Time và không có PowerMock.

Đây là những gì bạn cần làm:

Những gì cần phải được thực hiện trong lớp thử nghiệm

Bước 1

Thêm một java.time.Clockthuộc tính mới vào lớp được kiểm tra MyServicevà đảm bảo rằng thuộc tính mới sẽ được khởi tạo đúng ở các giá trị mặc định với khối khởi tạo hoặc hàm tạo:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  private Clock clock;
  public Clock getClock() { return clock; }
  public void setClock(Clock newClock) { clock = newClock; }

  public void initDefaultClock() {
    setClock(
      Clock.system(
        Clock.systemDefaultZone().getZone() 
        // You can just as well use
        // java.util.TimeZone.getDefault().toZoneId() instead
      )
    );
  }
  { 
    initDefaultClock(); // initialisation in an instantiation block, but 
                        // it can be done in a constructor just as well
  }
  // (...)
}

Bước 2

Tiêm thuộc tính mới clockvào phương thức gọi thời gian hiện tại. Ví dụ, trong trường hợp của tôi, tôi phải thực hiện kiểm tra xem ngày lưu trữ trong dataase có xảy ra trước LocalDateTime.now()đó hay không LocalDateTime.now(clock), như tôi đã nhắc lại , như vậy:

import java.time.Clock;
import java.time.LocalDateTime;

public class MyService {
  // (...)
  protected void doExecute() {
    LocalDateTime dateToBeCompared = someLogic.whichReturns().aDate().fromDB();
    while (dateToBeCompared.isBefore(LocalDateTime.now(clock))) {
      someOtherLogic();
    }
  }
  // (...) 
}

Những gì cần phải được thực hiện trong lớp thử nghiệm

Bước 3

Trong lớp kiểm tra, tạo một đối tượng đồng hồ giả và đưa nó vào đối tượng của lớp được kiểm tra ngay trước khi bạn gọi phương thức đã kiểm tra doExecute(), sau đó đặt lại nó ngay sau đó, như sau:

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import org.junit.Test;

public class MyServiceTest {
  // (...)
  private int year = 2017;
  private int month = 2;
  private int day = 3;

  @Test
  public void doExecuteTest() throws Exception {
    // (...) EasyMock stuff like mock(..), expect(..), replay(..) and whatnot

    MyService myService = new MyService();
    Clock mockClock =
      Clock.fixed(
        LocalDateTime.of(year, month, day, 0, 0).toInstant(OffsetDateTime.now().getOffset()),
        Clock.systemDefaultZone().getZone() // or java.util.TimeZone.getDefault().toZoneId()
      );
    myService.setClock(mockClock); // set it before calling the tested method

    myService.doExecute(); // calling tested method 

    myService.initDefaultClock(); // reset the clock to default right afterwards with our own previously created method

    // (...) remaining EasyMock stuff: verify(..) and assertEquals(..)
    }
  }

Kiểm tra nó trong chế độ gỡ lỗi và bạn sẽ thấy ngày 3 tháng 2 năm 2017 đã được đưa vào chính xác myServicevà được sử dụng trong hướng dẫn so sánh, và sau đó đã được đặt lại chính xác cho đến ngày hiện tại initDefaultClock().


2

Theo tôi chỉ có một giải pháp không xâm lấn có thể làm việc. Đặc biệt nếu bạn có libs bên ngoài và một cơ sở mã di sản lớn, không có cách nào đáng tin cậy để chế nhạo thời gian.

JMockit ... chỉ hoạt động với số lần hạn chế

PowerMock & Co ... cần phải giả định các máy khách thành System.cienTimeMillis (). Lại một lựa chọn xâm lấn.

Từ đây tôi chỉ thấy cách tiếp cận javaagent hoặc aop được đề cập là minh bạch cho toàn bộ hệ thống. Có ai đã làm điều đó và có thể chỉ ra một giải pháp như vậy?

@jarnbjo: bạn có thể hiển thị một số mã javaagent không?


3
Giải pháp jmockit hoạt động không giới hạn số lần với một chút -XX: -Inline hack (trên Sun's HotSpot): virgo47.wordpress.com/2012/06/22/changing-system-time-in-java Hoàn thành lớp tiện dụng SystemTimeShifter trong bài Lớp có thể được sử dụng trong các thử nghiệm của bạn hoặc nó có thể được sử dụng làm lớp chính đầu tiên trước lớp chính thực sự của bạn rất dễ dàng để chạy ứng dụng của bạn (hoặc thậm chí toàn bộ máy chủ ứng dụng) trong một thời gian khác. Tất nhiên, điều này được dự định cho mục đích thử nghiệm là chủ yếu, không phải cho môi trường sản xuất.
virgo47

2

Nếu bạn đang chạy Linux, bạn có thể sử dụng nhánh chính của libfaketime hoặc tại thời điểm thử nghiệm cam kết 4ce2835 .

Chỉ cần đặt biến môi trường theo thời gian bạn muốn mô phỏng ứng dụng java của bạn và chạy nó bằng ld-preloading:

# bash
export FAKETIME="1985-10-26 01:21:00"
export DONT_FAKE_MONOTONIC=1
LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 java -jar myapp.jar

Biến môi trường thứ hai là tối quan trọng đối với các ứng dụng java, nếu không sẽ đóng băng. Nó đòi hỏi nhánh chính của libfaketime tại thời điểm viết.

Nếu bạn muốn thay đổi thời gian của dịch vụ được quản lý systemd, chỉ cần thêm phần sau vào tập tin đơn vị của bạn, ví dụ như đối với elaticsearch, đây sẽ là /etc/systemd/system/elasticsearch.service.d/override.conf:

[Service]
Environment="FAKETIME=2017-10-31 23:00:00"
Environment="DONT_FAKE_MONOTONIC=1"
Environment="LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1"

Đừng quên tải lại systemd bằng cách sử dụng `systemctl daemon-reload


-1

Nếu bạn muốn mô phỏng phương thức có System.currentTimeMillis()đối số thì bạn có thể chuyển anyLong()lớp Matchers làm đối số.

PS Tôi có thể chạy trường hợp thử nghiệm của mình thành công bằng cách sử dụng thủ thuật trên và chỉ để chia sẻ thêm chi tiết về thử nghiệm của tôi rằng tôi đang sử dụng khung công tác PowerMock và Mockito.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.