Làm cách nào để thực hiện TDD trên các thiết bị nhúng?


17

Tôi không phải là người mới trong lập trình và tôi thậm chí đã từng làm việc với một số C và ASM cấp thấp trên AVR, nhưng tôi thực sự không thể hiểu được về một dự án C nhúng quy mô lớn hơn.

Bị suy thoái bởi triết lý TDD / BDD của Ruby, tôi không thể hiểu cách mọi người viết và kiểm tra mã như thế này. Tôi không nói đó là một mã xấu, tôi chỉ không hiểu làm thế nào điều này có thể hoạt động.

Tôi muốn tham gia nhiều hơn vào một số chương trình cấp thấp, nhưng tôi thực sự không biết làm thế nào để tiếp cận điều này, vì nó trông giống như một suy nghĩ hoàn toàn khác mà tôi đã từng sử dụng. Tôi không gặp khó khăn khi hiểu các thuật ngữ con trỏ hoặc cách phân bổ bộ nhớ hoạt động, nhưng khi tôi thấy mã C / C ++ phức tạp trông như thế nào so với Ruby, nó có vẻ khó khăn vô cùng.

Vì tôi đã đặt mua cho mình một bo mạch Arduino, tôi rất muốn tham gia vào một số cấp độ C thấp và thực sự hiểu cách làm mọi thứ đúng cách, nhưng có vẻ như không có quy tắc nào của ngôn ngữ cấp cao được áp dụng.

Thậm chí có thể thực hiện TDD trên các thiết bị nhúng hoặc khi phát triển trình điều khiển hoặc những thứ như bộ tải khởi động tùy chỉnh, v.v.?


3
Xin chào Darth, chúng tôi thực sự không thể giúp bạn vượt qua nỗi sợ C, nhưng câu hỏi về TDD trên các thiết bị nhúng là chủ đề ở đây: Tôi đã sửa đổi câu hỏi của bạn thành tính năng thay thế.

Câu trả lời:


18

Trước hết, bạn nên biết rằng cố gắng hiểu mã bạn không viết khó hơn gấp 5 lần so với tự viết. Bạn có thể học C bằng cách đọc mã sản xuất, nhưng sẽ mất nhiều thời gian hơn học bằng cách làm.

Bị suy thoái bởi triết lý TDD / BDD của Ruby, tôi không thể hiểu cách mọi người viết và kiểm tra mã như thế này. Tôi không nói đó là một mã xấu, tôi chỉ không hiểu làm thế nào điều này có thể hoạt động.

Đó là một kỹ năng; bạn trở nên tốt hơn ở nó Hầu hết các lập trình viên C không hiểu cách mọi người sử dụng Ruby, nhưng điều đó không có nghĩa là họ không thể.

Thậm chí có thể thực hiện TDD trên các thiết bị nhúng hoặc khi phát triển trình điều khiển hoặc những thứ như bộ tải khởi động tùy chỉnh, v.v.?

Vâng, có những cuốn sách về chủ đề này:

nhập mô tả hình ảnh ở đây Nếu một con ong nghệ có thể làm điều đó, bạn cũng có thể!

Hãy nhớ rằng áp dụng thực tiễn từ các ngôn ngữ khác thường không hoạt động. TDD là khá phổ quát mặc dù.


2
Mỗi TDD tôi đã thấy cho các hệ thống nhúng của mình chỉ tìm thấy lỗi trong các hệ thống dễ giải quyết lỗi mà tôi có thể tự tìm thấy dễ dàng. Họ sẽ không bao giờ tìm thấy những gì tôi cần giúp đỡ, các tương tác phụ thuộc thời gian với các chip khác và các tương tác gián đoạn.
Kortuk

3
Điều này phụ thuộc vào loại hệ thống bạn đang làm việc. Tôi đã thấy rằng việc sử dụng TDD để kiểm tra phần mềm, cùng với khả năng trừu tượng hóa phần cứng tốt, thực sự cho phép tôi chế tạo các tương tác phụ thuộc thời gian đó dễ dàng hơn nhiều. Lợi ích khác mà mọi người thường xem qua là các bài kiểm tra, được tự động hóa, có thể được chạy bất cứ lúc nào và không yêu cầu ai đó ngồi trên thiết bị với bộ phân tích logic để đảm bảo phần mềm hoạt động. TDD đã tiết kiệm cho tôi nhiều tuần gỡ lỗi trong dự án hiện tại của tôi. Thông thường, đó là những lỗi mà chúng tôi nghĩ là dễ phát hiện gây ra lỗi mà chúng tôi không mong đợi.
Nick Pascucci

Thêm vào đó, nó cho phép phát triển và thử nghiệm ngoài mục tiêu.
cp.engr

Tôi có thể theo dõi cuốn sách này để hiểu TDD cho C không nhúng không? Đối với bất kỳ người dùng lập trình C không gian?
trao đổi quá

15

Rất nhiều câu trả lời ở đây ... chủ yếu là giải quyết vấn đề theo nhiều cách khác nhau.

Tôi đã viết phần mềm và chương trình cơ sở nhúng thấp trong hơn 25 năm bằng nhiều ngôn ngữ - chủ yếu là C (nhưng có sự chuyển hướng sang Ada, Occam2, PL / M và nhiều bộ lắp ráp trên đường đi).

Sau một thời gian dài suy nghĩ và thử nghiệm và sai sót, tôi đã giải quyết một phương pháp thu được kết quả khá nhanh và khá dễ dàng để tạo các hàm bao và khai thác thử nghiệm (trong đó chúng THÊM GIÁ TRỊ!)

Phương thức này giống như thế này:

  1. Viết một trình điều khiển mã trừu tượng phần cứng hoặc trình điều khiển cho mỗi thiết bị ngoại vi chính mà bạn muốn sử dụng. Đồng thời viết một cái để khởi tạo bộ xử lý và thiết lập mọi thứ (điều này làm cho môi trường thân thiện). Thông thường trên các bộ xử lý nhúng nhỏ - AVR của bạn là một ví dụ - có thể có 10 - 20 đơn vị như vậy, tất cả đều nhỏ. Đây có thể là các đơn vị để khởi tạo, chuyển đổi A / D thành bộ đệm bộ nhớ không được quét, đầu ra bitwise, đầu vào nút bấm (không gỡ lỗi chỉ lấy mẫu), trình điều khiển độ rộng xung, trình điều khiển nối tiếp UART / đơn giản sử dụng ngắt và bộ đệm I / O nhỏ. Có thể có thêm một vài - ví dụ trình điều khiển I2C hoặc SPI cho các thiết bị EEPROM, EPROM hoặc các thiết bị I2C / SPI khác.

  2. Đối với mỗi đơn vị trừu tượng hóa phần cứng (HAL) / trình điều khiển, sau đó tôi viết chương trình thử nghiệm. Điều này phụ thuộc vào một cổng nối tiếp (UART) và init bộ xử lý - vì vậy chương trình thử nghiệm đầu tiên chỉ sử dụng 2 đơn vị đó và chỉ thực hiện một số đầu vào và đầu ra cơ bản. Điều này cho phép tôi kiểm tra rằng tôi có thể khởi động bộ xử lý và tôi có hoạt động gỡ lỗi cơ bản hỗ trợ I / O nối tiếp. Khi nó hoạt động (và chỉ sau đó), sau đó tôi sẽ phát triển các chương trình thử nghiệm HAL khác, xây dựng các chương trình này trên đầu các đơn vị UART và INIT tốt đã biết. Vì vậy, tôi có thể có các chương trình thử nghiệm để đọc các đầu vào bitwise và hiển thị các đầu vào này ở dạng đẹp (hex, thập phân, bất cứ thứ gì) trên thiết bị đầu cuối gỡ lỗi nối tiếp của tôi. Sau đó tôi có thể chuyển sang những thứ lớn hơn và phức tạp hơn như các chương trình thử nghiệm EEPROM hoặc EPROM - Tôi thực hiện hầu hết các menu này để tôi có thể chọn một thử nghiệm để chạy, chạy nó và xem kết quả. Tôi không thể SCRIPT nhưng thường thì tôi không '

  3. Khi tôi có tất cả HAL của mình đang chạy, sau đó tôi tìm cách để đánh dấu vào bộ đếm thời gian thông thường. Điều này thường ở tốc độ khoảng 4 đến 20 ms. Điều này phải thường xuyên, được tạo ra trong một ngắt. Rollover / tràn bộ đếm thường là làm thế nào điều này có thể được thực hiện. Trình xử lý ngắt sau đó TĂNG một kích thước byte "semaphore". Tại thời điểm này, bạn cũng có thể loay hoay với quản lý năng lượng nếu bạn cần. Ý tưởng của semaphore là nếu giá trị của nó> 0, bạn cần chạy "vòng lặp chính".

  4. EXECUTIVE chạy vòng lặp chính. Nó gần như chỉ chờ đợi semaphore đó trở thành số 0 (tôi tóm tắt chi tiết này đi). Tại thời điểm này, bạn có thể chơi với các bộ đếm để đếm các tích tắc này (vì bạn biết tỷ lệ đánh dấu) và do đó bạn có thể đặt cờ hiển thị nếu dấu kiểm điều hành hiện tại là trong khoảng thời gian 1 giây, 1 phút và các khoảng thời gian phổ biến khác bạn có thể muốn sử dụng. Khi giám đốc điều hành biết rằng semaphore> 0, nó sẽ chạy một lượt thông qua mọi chức năng "cập nhật" của ứng dụng.

  5. Các quy trình ứng dụng có hiệu quả ngồi cạnh nhau và được chạy thường xuyên bằng cách đánh dấu "cập nhật". Đây chỉ là một chức năng được gọi bởi giám đốc điều hành. Đây thực sự là một người nghèo đa nhiệm với một RTOS được trồng tại nhà rất đơn giản, dựa trên tất cả các ứng dụng nhập vào, thực hiện một công việc nhỏ và thoát ra. Các ứng dụng cần duy trì các biến trạng thái của riêng chúng và không thể thực hiện các phép tính chạy dài vì không có hệ điều hành ưu tiên để buộc sự công bằng. NGHIÊM TÚC thời gian chạy của các ứng dụng (tích lũy) phải nhỏ hơn thời gian đánh dấu chính.

Cách tiếp cận trên có thể dễ dàng mở rộng để bạn có thể có những thứ như ngăn xếp giao tiếp được thêm chạy không đồng bộ và tin nhắn comms sau đó có thể được gửi đến các ứng dụng (bạn thêm một chức năng mới cho mỗi "rx_message_handler" và bạn viết một bộ điều phối tin nhắn. ra ứng dụng nào để gửi đến).

Cách tiếp cận này hoạt động với hầu hết mọi hệ thống truyền thông mà bạn quan tâm để đặt tên - nó có thể (và đã thực hiện) hoạt động cho nhiều hệ thống độc quyền, các hệ thống tiêu chuẩn mở, thậm chí nó hoạt động cho các ngăn xếp TCP / IP.

Nó cũng có lợi thế là được xây dựng trong các phần mô-đun với giao diện được xác định rõ. Bạn có thể kéo các mảnh vào và ra bất cứ lúc nào, thay thế các mảnh khác nhau. Tại mỗi điểm trên đường đi, bạn có thể thêm khai thác thử nghiệm hoặc trình xử lý dựa trên các phần lớp dưới tốt đã biết (công cụ bên dưới). Tôi đã thấy rằng khoảng 30% đến 50% thiết kế có thể được hưởng lợi từ việc thêm các bài kiểm tra đơn vị bằng văn bản đặc biệt thường được thêm vào khá dễ dàng.

Tôi đã tiến xa hơn một bước (một ý tưởng mà tôi đặt biệt danh từ một người khác đã thực hiện điều này) và thay thế lớp HAL bằng một lớp tương đương cho PC. Vì vậy, ví dụ, bạn có thể sử dụng C / C ++ và winforms hoặc tương tự trên PC và bằng cách viết mã CẨN THẬN, bạn có thể mô phỏng từng giao diện (ví dụ EEPROM = một tệp đĩa đọc vào bộ nhớ PC) và sau đó chạy toàn bộ ứng dụng nhúng trên PC. Khả năng sử dụng một môi trường gỡ lỗi thân thiện có thể tiết kiệm một lượng lớn thời gian và công sức. Chỉ những dự án thực sự lớn thường có thể biện minh cho lượng nỗ lực này.

Mô tả ở trên là một cái gì đó không phải là duy nhất đối với cách tôi làm mọi thứ trên nền tảng nhúng - tôi đã bắt gặp rất nhiều tổ chức thương mại làm tương tự. Cách thức thực hiện của nó thường rất khác nhau trong việc thực hiện nhưng các nguyên tắc thường giống nhau nhiều.

Tôi hy vọng những điều trên mang lại một chút hương vị ... phương pháp này hoạt động đối với các hệ thống nhúng nhỏ chạy trong một vài kB với khả năng quản lý pin tích cực thông qua các quái vật từ 100K trở lên chạy bằng nguồn vĩnh viễn. Nếu bạn chạy "nhúng" trên một hệ điều hành lớn như Windows CE hoặc hơn thì tất cả những điều trên là hoàn toàn không quan trọng. Nhưng dù sao đó không phải là chương trình nhúng THỰC SỰ.


2
Hầu hết các thiết bị ngoại vi phần cứng bạn không thể kiểm tra thông qua UART, vì thường thì bạn chủ yếu quan tâm đến các đặc điểm thời gian. Nếu bạn muốn kiểm tra tốc độ mẫu ADC, chu kỳ nhiệm vụ của PWM, hành vi của một số thiết bị ngoại vi nối tiếp khác (SPI, CAN, v.v.) hoặc đơn giản là thời gian thực hiện của một phần chương trình của bạn, thì bạn không thể thực hiện điều này thông qua một UART. Bất kỳ kiểm tra phần mềm nhúng nghiêm trọng nào cũng bao gồm máy hiện sóng - bạn không thể lập trình các hệ thống nhúng mà không có.

1
Ồ vâng, hoàn toàn. Tôi chỉ quên đề cập đến một trong những. Nhưng một khi bạn đã khởi động và chạy UART, việc cài đặt các trường hợp kiểm tra hoặc kiểm tra rất dễ dàng (đó là câu hỏi gì), kích thích mọi thứ, cho phép người dùng nhập liệu, nhận kết quả và hiển thị một cách thân thiện. Điều này + CRO của bạn làm cho cuộc sống rất dễ dàng.
quick_now

2

Mã có lịch sử phát triển gia tăng và tối ưu hóa lâu dài cho nhiều nền tảng, chẳng hạn như các ví dụ bạn đã chọn, thường khó đọc hơn.

Điều về C là nó thực sự có khả năng mở rộng các nền tảng trên một phạm vi rộng lớn về hiệu suất phần cứng và hiệu năng phần cứng API (và thiếu nó). MacVim chạy rất nhạy trên các máy có hiệu năng xử lý và bộ nhớ ít hơn 1000 lần so với điện thoại thông minh thông thường hiện nay. Mã Ruby của bạn có thể không? Đó là một trong những lý do có thể trông đơn giản hơn các ví dụ C trưởng thành mà bạn đã chọn.


2

Tôi đang ở vị trí ngược lại khi đã dành hầu hết 9 năm qua làm lập trình viên C và gần đây đang làm việc trên một số mặt trước của Ruby on Rails.

Những thứ tôi làm trong C hầu hết là các hệ thống tùy chỉnh cỡ trung bình để kiểm soát kho tự động (chi phí điển hình là vài trăm nghìn bảng, lên đến vài triệu đồng). Chức năng ví dụ là một cơ sở dữ liệu trong bộ nhớ tùy chỉnh, giao tiếp với máy móc với một số yêu cầu thời gian đáp ứng ngắn và quản lý cấp cao hơn của quy trình làm việc của kho.

Tôi có thể nói trước hết, chúng tôi không làm bất kỳ TDD nào. Tôi đã thử một số lần giới thiệu các bài kiểm tra đơn vị, nhưng trong C, nó rắc rối hơn giá trị của nó - ít nhất là khi phát triển phần mềm tùy chỉnh. Nhưng tôi muốn nói rằng TDD ít cần thiết hơn ở C so với Ruby. Chủ yếu, đó chỉ là do C được biên dịch và nếu nó biên dịch mà không có cảnh báo, bạn đã thực hiện một số lượng thử nghiệm khá giống với các thử nghiệm giàn giáo được tạo tự động rspec trong Rails. Ruby mà không có bài kiểm tra đơn vị là không khả thi.

Nhưng điều tôi muốn nói là C không cần phải khó như một số người làm. Phần lớn thư viện chuẩn C là một mớ hỗn độn của các tên hàm không thể hiểu được và rất nhiều chương trình C tuân theo quy ước này. Tôi rất vui khi nói rằng chúng tôi không, và trên thực tế có rất nhiều trình bao bọc cho chức năng thư viện tiêu chuẩn (ST_Copy thay vì strncpy, ST_PotypeMatch thay vì regcomp / regexec, CHARSET_Convert thay vì iconv_open / iconv / iconv_close, v.v.). Mã C trong nhà của chúng tôi đọc tốt hơn cho tôi so với hầu hết những thứ khác tôi đã đọc.

Nhưng khi bạn nói các quy tắc từ các ngôn ngữ cấp cao khác dường như không áp dụng, tôi sẽ không đồng ý. Rất nhiều mã C tốt "cảm thấy" hướng đối tượng. Bạn thường thấy một mô hình khởi tạo tay cầm cho tài nguyên, gọi một số hàm truyền tay cầm làm đối số và cuối cùng giải phóng tài nguyên. Thật vậy, các nguyên tắc thiết kế của lập trình hướng đối tượng phần lớn đến từ những điều tốt đẹp mà mọi người đang làm trong các ngôn ngữ thủ tục.

Thời gian khi C trở nên thực sự phức tạp thường là khi làm những việc như trình điều khiển thiết bị và nhân hệ điều hành, về cơ bản chỉ ở mức rất thấp. Khi bạn viết một hệ thống cấp cao hơn, bạn cũng có thể sử dụng các tính năng cấp cao hơn của C và tránh sự phức tạp ở cấp độ thấp.

Một điều rất thú vị mà bạn có thể muốn xem qua là mã nguồn C cho Ruby. Trong các tài liệu API của Ruby (http://www.ruby-doc.org/core-1.9.3/), bạn có thể nhấp và xem mã nguồn cho các phương thức khác nhau. Điều thú vị là mã này trông khá đẹp và thanh lịch - nó không phức tạp như bạn tưởng tượng.


" ... Bạn cũng có thể sử dụng các tính năng cấp cao hơn của C ... ", như có? ;-)
kiềm

Tôi có nghĩa là mức cao hơn so với thao tác bit và con trỏ đến thuật sĩ con trỏ mà bạn có xu hướng nhìn thấy trong mã loại trình điều khiển thiết bị! Và nếu bạn không lo lắng về chi phí của một vài lệnh gọi hàm, bạn có thể tạo mã C thực sự trông ở mức cao hợp lý.
asc99c

" ... bạn có thể tạo mã C thực sự trông có vẻ hợp lý ở mức cao. ", tuyệt đối, tôi hoàn toàn đồng ý với điều đó. Nhưng mặc dù " ... các tính năng cấp cao hơn ... " không phải của C, nhưng trong đầu bạn, phải không?
kiềm

2

Những gì tôi đã làm là tách mã phụ thuộc thiết bị khỏi mã độc lập của thiết bị, sau đó kiểm tra mã độc lập của thiết bị. Với mô đun tốt và kỷ luật, bạn sẽ sẽ đối mặt với vấn chủ yếu codebase được kiểm tra kỹ.


2

Không có lý do tại sao bạn không thể. Vấn đề là có thể không có các khung kiểm thử đơn vị "ngoài luồng" đẹp như bạn có trong các loại phát triển khác. Vậy là được rồi. Điều đó chỉ có nghĩa là bạn phải thực hiện một cách tiếp cận "cuộn của riêng bạn" để thử nghiệm.

Chẳng hạn, bạn có thể phải lập trình thiết bị để tạo ra "đầu vào giả" cho bộ chuyển đổi A / D của mình hoặc có thể bạn sẽ phải tạo một luồng "dữ liệu giả" cho thiết bị nhúng của mình để phản hồi.

Nếu bạn gặp phải sự kháng cự khi sử dụng từ "TDD", hãy gọi nó là "DVT" (kiểm tra xác minh thiết kế) sẽ giúp EE thoải mái hơn với ý tưởng này.


0

Thậm chí có thể thực hiện TDD trên các thiết bị nhúng hoặc khi phát triển trình điều khiển hoặc những thứ như bộ tải khởi động tùy chỉnh, v.v.?

Cách đây một thời gian, tôi cần phải viết bộ tải khởi động cấp đầu tiên cho CPU ARM. Trên thực tế có một từ những người bán CPU này. Và chúng tôi đã sử dụng một sơ đồ trong đó bộ nạp khởi động của họ khởi động bộ tải khởi động của chúng tôi. Nhưng điều này rất chậm, vì chúng tôi cần flash hai tệp vào flash flash thay vì một, chúng tôi cần xây dựng kích thước của bộ tải khởi động của chúng tôi vào bộ tải khởi động đầu tiên và xây dựng lại mỗi khi chúng tôi thay đổi bộ tải khởi động, v.v.

Vì vậy, tôi quyết định tích hợp các chức năng của bộ tải khởi động của họ vào chúng ta. Bởi vì đó là mã thương mại, tôi phải đảm bảo rằng tất cả mọi thứ hoạt động như mong đợi. Vì vậy, tôi đã sửa đổi QEMU để mô phỏng các khối IP của CPU đó (không phải tất cả, chỉ những khối chạm vào bộ tải khởi động) và thêm mã vào QEMU để "printf" tất cả đọc / ghi vào các thanh ghi điều khiển các công cụ như bộ điều khiển PLL, UART, SRAM và Sớm. Sau đó, tôi đã nâng cấp bộ tải khởi động của chúng tôi để hỗ trợ CPU này và sau đó so sánh đầu ra cung cấp cho bộ tải khởi động của chúng tôi và trình giả lập của chúng, điều này giúp tôi bắt được một số lỗi. Nó được viết một phần bằng trình biên dịch ARM, một phần bằng C. Ngoài ra, sau đó QEMU đã sửa đổi đó đã giúp tôi bắt được một lỗi, tôi không thể bắt được bằng JTAG và CPU ARM thực sự.

Vì vậy, ngay cả với C và trình biên dịch, bạn có thể sử dụng các bài kiểm tra.


-2

Có, có thể làm TDD trên phần mềm nhúng. Những người nói rằng nó là không thể, không liên quan, hoặc không áp dụng là không chính xác. Có giá trị nghiêm trọng đạt được từ TDD khi được nhúng như với bất kỳ phần mềm nào.

Cách tốt nhất để làm điều đó là không chạy các thử nghiệm của bạn trên mục tiêu mà là trừu tượng hóa các phần phụ thuộc phần cứng của bạn và biên dịch và chạy trên PC chủ của bạn.

Khi bạn đang làm TDD, bạn sẽ tạo và chạy rất nhiều bài kiểm tra. Bạn cần phần mềm để giúp bạn làm điều này. Bạn muốn có một khung kiểm tra để làm cho nó nhanh chóng và dễ dàng để làm điều này, với phát hiện thử nghiệm tự động và tạo giả.

Tùy chọn tốt nhất cho C ngay bây giờ là Ceedling. Đây là một bài viết về tôi đã viết về nó:

http://www.electronvector.com/blog/try-embedded-test-driven-development-right-now-with-ceedling

Và nó được xây dựng trong Ruby! Bạn không cần phải biết bất kỳ Ruby để sử dụng nó mặc dù.


câu trả lời dự kiến ​​sẽ tự đứng vững. Buộc người đọc phải truy cập vào tài nguyên bên ngoài để tìm ra chất được tán thành tại Stack Exchange ("đọc bài viết hoặc kiểm tra Ceedling"). Xem xét chỉnh sửa ing để làm cho nó phù hợp với tiêu chuẩn chất lượng trang web
gnat

Ceedling có bất kỳ cơ chế nào để hỗ trợ các sự kiện không đồng bộ không? Một trong những khía cạnh thách thức hơn của các ứng dụng nhúng thời gian thực là chúng xử lý việc nhận đầu vào từ các hệ thống rất phức tạp mà bản thân chúng khó tạo mô hình ...
Jay Elston

@Jay Nó không có gì đặc biệt để hỗ trợ điều đó. Tuy nhiên, tôi đã thử nghiệm thành công loại vật đó bằng cách chế nhạo và bằng cách thiết lập một kiến ​​trúc để hỗ trợ nó. Ví dụ, gần đây tôi đã làm việc trong một dự án nơi các sự kiện điều khiển ngắt được đưa vào hàng đợi và sau đó được tiêu thụ trong một máy trạng thái "xử lý sự kiện". Đây thực chất chỉ là một chức năng được gọi bất cứ khi nào sự kiện xảy ra. Khi kiểm tra chức năng đó, tôi có thể giả định lệnh gọi hàm đã kéo các sự kiện ra khỏi hàng đợi và do đó có thể mô phỏng mọi sự kiện xảy ra trong hệ thống. Lái thử cũng giúp ở đây.
cherno
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.