Kiểm tra đơn vị xác nhận rằng luồng hiện tại là luồng chính


8

Câu hỏi đặt ra là có ý nghĩa gì khi viết một bài kiểm tra đơn vị trong đó khẳng định rằng luồng hiện tại là luồng chính? Ưu / nhược điểm?

Gần đây tôi đã thấy bài kiểm tra đơn vị xác nhận luồng hiện tại cho cuộc gọi lại dịch vụ. Tôi không chắc chắn, đó là một ý tưởng tốt, tôi tin rằng đó là loại thử nghiệm tích hợp nhiều hơn. Theo tôi, thử nghiệm đơn vị nên khẳng định phương pháp trong sự cô lập và không nên biết về bản chất của người tiêu dùng dịch vụ.

Ví dụ: trong iOS, người tiêu dùng của dịch vụ này được dự định là một UI mà theo mặc định có một ràng buộc để chạy mã ở luồng chính.


1
FWIW, trong mã của chúng tôi, thay vì làm cho nó trở thành một xác nhận kiểm tra đơn vị, chúng tôi làm cho nó trở thành một xác nhận thời gian chạy, vì đó là nơi thực sự quan trọng.
dùng1118321

Có vẻ giống như một khẳng định hơn là một trường hợp thử nghiệm thực tế.
whatsisname

Câu trả lời:


11

Hãy để tôi chỉ cho bạn các nguyên tắc kiểm tra đơn vị yêu thích của tôi:

Một bài kiểm tra không phải là một bài kiểm tra đơn vị nếu:

  1. Nó nói chuyện với cơ sở dữ liệu
  2. Nó giao tiếp trên mạng
  3. Nó chạm vào hệ thống tập tin
  4. Nó không thể chạy cùng lúc với bất kỳ bài kiểm tra đơn vị nào khác của bạn
  5. Bạn phải làm những điều đặc biệt cho môi trường của mình (chẳng hạn như chỉnh sửa tập tin cấu hình) để chạy nó.

Một bộ quy tắc kiểm tra đơn vị - Michael Feathers

Nếu bài kiểm tra đơn vị của bạn khẳng định rằng đó là "luồng chính", thật khó để không chạy afoul của số 4.

Điều này có vẻ khắc nghiệt nhưng hãy nhớ rằng một bài kiểm tra đơn vị chỉ là một trong nhiều loại bài kiểm tra khác nhau.

nhập mô tả hình ảnh ở đây

Bạn được tự do vi phạm các nguyên tắc này. Nhưng khi bạn làm, đừng gọi bài kiểm tra của bạn là bài kiểm tra đơn vị. Đừng đặt nó với các bài kiểm tra đơn vị thực sự và làm chậm chúng.


Làm thế nào để bạn đơn vị kiểm tra một logger? Bạn có phải moc IO? Điều đó có vẻ đáng ghét.
whn

1
@opa nếu logger của bạn có logic kinh doanh thú vị, bạn tách biệt nó và kiểm tra đơn vị đó. Mã nhàm chán xác nhận rằng chúng tôi có một hệ thống tệp không cần kiểm tra đơn vị.
candied_orange

@candied_orange Vậy bây giờ bạn phải phơi bày logic bên trong? Bởi vì làm thế nào khác bạn sẽ tách chuỗi được tạo bởi logger trước khi nó được xuất ra một tệp với việc tách nó ra thành một phương thức riêng tư ?
whn

@opa Nếu bạn cần kiểm tra rằng bạn có thể làm điều đó bằng cách truyền vào luồng của riêng bạn. Đơn vị kiểm tra đơn vị kiểm tra không phải tài nguyên hệ thống.
candied_orange

@candied_orange ngoại trừ khi logger chỉ lấy tên tệp mà nó sẽ xuất thành đầu vào. Điểm không phải là "Đơn vị kiểm tra đơn vị không phải tài nguyên hệ thống" là sai, điều đó đúng 100%. Bạn không thể nói quá giáo điều khi kiểm tra chạm vào tệp io rằng đó không phải là kiểm tra đơn vị hợp lệ. Kiểm tra một logger thông qua việc đọc đầu ra tệp của nó dễ dàng hơn nhiều so với việc công khai chức năng riêng tư chỉ vì mục đích kiểm tra đơn vị, điều mà bạn thực sự không bao giờ nên làm . Nó không kiểm tra IO, nó kiểm tra logger, nó chỉ xảy ra cách dễ nhất là đọc tệp nếu bạn vẫn muốn mã sạch.
whn

5

Bạn có nên viết một bài kiểm tra đơn vị mà kiểm tra nếu một chủ đề là chủ đề chính? Phụ thuộc, nhưng có lẽ không phải là một ý tưởng tốt.

Nếu quan điểm của một bài kiểm tra đơn vị có nghĩa là để xác minh hoạt động đúng của một phương thức, thì việc kiểm tra khía cạnh này gần như chắc chắn không kiểm tra khía cạnh này. Như bạn đã nói, về mặt khái niệm, đây là một thử nghiệm tích hợp, vì hoạt động đúng của một phương thức không có khả năng thay đổi dựa trên luồng nào đang chạy nó và người chịu trách nhiệm xác định luồng nào được sử dụng là người gọi chứ không phải phương pháp chính nó.

Tuy nhiên, đây là lý do tại sao tôi nói nó phụ thuộc, bởi vì nó rất có thể phụ thuộc vào chính phương thức, nên phương thức sẽ tạo ra các luồng mới. Mặc dù rất có thể đây không phải là trường hợp của bạn.

Lời khuyên của tôi là hãy bỏ kiểm tra này ra khỏi bài kiểm tra đơn vị và chuyển nó sang một bài kiểm tra tích hợp thích hợp cho khía cạnh này.


2

không nên biết về bản chất của người tiêu dùng dịch vụ.

Khi người tiêu dùng sử dụng dịch vụ, có "hợp đồng" thể hiện hoặc ngụ ý về chức năng được cung cấp và cách thức. Hạn chế về chủ đề mà cuộc gọi lại có thể xảy ra là một phần của hợp đồng đó.

Tôi đoán rằng mã của bạn chạy trong một bối cảnh có một loại "công cụ sự kiện" nào đó chạy trong "luồng chính" và một cách để các luồng khác yêu cầu mã lịch trình của sự kiện được chạy trong luồng chính.

Trong quan điểm thuần túy nhất của các bài kiểm tra đơn vị, bạn sẽ chế giễu tuyệt đối mọi sự phụ thuộc. Trong thế giới thực rất ít người làm điều đó. Mã được thử nghiệm được phép sử dụng các phiên bản thực của ít nhất các tính năng nền tảng cơ bản.

Vì vậy, câu hỏi thực sự IMO là bạn có xem công cụ sự kiện và hỗ trợ luồng thời gian chạy là một phần của "tính năng nền tảng cơ bản" mà các bài kiểm tra đơn vị có được phép phụ thuộc hay không? Nếu bạn làm như vậy thì sẽ rất hợp lý để kiểm tra xem cuộc gọi lại có xảy ra trên "luồng chính" hay không. Nếu bạn đang chế nhạo công cụ sự kiện và hệ thống luồng thì bạn sẽ thiết kế mô phỏng của mình để họ có thể xác định liệu cuộc gọi lại có xảy ra trên luồng chính hay không. Nếu bạn đang chế nhạo công cụ sự kiện nhưng không hỗ trợ luồng thời gian chạy, bạn sẽ muốn kiểm tra xem cuộc gọi lại có xảy ra trên luồng nơi công cụ sự kiện giả của bạn chạy không.


1

Tôi có thể thấy tại sao nó sẽ là một bài kiểm tra hấp dẫn. Nhưng những gì nó thực sự sẽ được thử nghiệm? nói rằng chúng ta có chức năng

showMessage(string m)
{
    new DialogueBox(m).Show(); //will fail if not called from UI thread
}

Bây giờ tôi có thể kiểm tra điều này và chạy nó trong một luồng không phải UI, chứng minh rằng đã xảy ra lỗi. Nhưng thực sự để kiểm tra đó có bất kỳ giá trị nào, hàm cần có một số hành vi cụ thể mà bạn muốn nó thực hiện khi chạy trong một luồng không phải UI.

showMessage(string m)
{
    try {
        new DialogueBox(m).Show(); //will fail if not called from UI thread
    }
    catch {
        Logger.Log(m); //alternative display method
    }
}

Bây giờ tôi có một cái gì đó để kiểm tra, nhưng không rõ loại thay thế này sẽ hữu ích trong cuộc sống thực


1

Nếu cuộc gọi lại được ghi lại là không an toàn cho luồng để gọi trên bất kỳ trường hợp nào khác, chẳng hạn như luồng UI hoặc luồng chính, thì có vẻ như hoàn toàn hợp lý trong một thử nghiệm đơn vị của mô-đun mã khiến cho việc gọi lại cuộc gọi này được đảm bảo nó không làm điều gì đó không an toàn cho luồng bằng cách làm việc bên ngoài luồng mà hệ điều hành hoặc khung ứng dụng được ghi lại để đảm bảo an toàn.


1

Sắp xếp
hành động
khẳng định

Một bài kiểm tra đơn vị có nên khẳng định rằng nó nằm trên một luồng cụ thể không? Không, bởi vì nó không kiểm tra một đơn vị trong trường hợp đó, thậm chí nó không phải là kiểm tra tích hợp, vì nó không nói gì về cấu hình diễn ra trong đơn vị ...

Khẳng định bài kiểm tra nào được bật, sẽ giống như Xác nhận tên của máy chủ kiểm tra của bạn hoặc tốt hơn là tên của phương thức kiểm tra.

Bây giờ, có thể có ý nghĩa Sắp xếp để thử nghiệm được chạy trên một luồng cụ thể, nhưng chỉ khi bạn muốn chắc chắn rằng nó sẽ trả về một kết quả cụ thể dựa trên luồng.


1

Nếu bạn có một chức năng được ghi lại là chỉ chạy chính xác trên luồng chính và chức năng đó được gọi trên một luồng nền, đó không phải là lỗi chức năng. Lỗi là lỗi của người gọi, vì vậy các bài kiểm tra đơn vị là vô nghĩa.

Nếu hợp đồng được thay đổi để chức năng sẽ chạy chính xác trên luồng chính và báo lỗi khi chạy trên luồng nền, bạn có thể viết một bài kiểm tra đơn vị cho điều đó, trong đó bạn gọi nó một lần trên luồng chính và sau đó vào một chủ đề nền và kiểm tra xem nó hoạt động như tài liệu. Vì vậy, bây giờ bạn có một bài kiểm tra đơn vị, nhưng không phải kiểm tra đơn vị kiểm tra xem nó có chạy trên luồng chính không.

Tôi rất muốn đề xuất rằng dòng đầu tiên của chức năng của bạn sẽ khẳng định rằng nó chạy trên luồng chính.


1

Kiểm thử đơn vị chỉ hữu ích nếu kiểm tra thực hiện đúng chức năng đã nói, không sử dụng đúng, vì kiểm thử đơn vị không thể nói rằng một chức năng sẽ không được gọi từ một luồng khác trong phần còn lại của cơ sở mã của bạn, giống như nó có thể ' Chúng tôi nói rằng các hàm sẽ không được gọi với các tham số vi phạm các điều kiện tiên quyết của chúng (và về mặt kỹ thuật những gì bạn đang thử nghiệm về cơ bản là vi phạm điều kiện tiên quyết trong sử dụng, đây là điều bạn không thể kiểm tra một cách hiệu quả vì thử nghiệm có thể ' Tuy nhiên, hạn chế cách các vị trí khác trong cơ sở mã sử dụng các chức năng đó, tuy nhiên, bạn có thể kiểm tra xem việc vi phạm các điều kiện tiên quyết có dẫn đến lỗi / ngoại lệ hay không.

Đây là một trường hợp khẳng định với tôi khi thực hiện các chức năng có liên quan như một số người khác đã chỉ ra, hoặc thậm chí tinh vi hơn là đảm bảo các chức năng này an toàn cho luồng (mặc dù điều này không phải lúc nào cũng thực tế khi làm việc với một số API).

Cũng chỉ là một ghi chú bên cạnh nhưng "chủ đề chính"! = "Chủ đề giao diện người dùng" trong mọi trường hợp. Nhiều API GUI không an toàn cho luồng (và làm cho bộ GUI an toàn luồng bị hỏng rất nhiều), nhưng điều đó không có nghĩa là bạn phải gọi chúng từ cùng một luồng như luồng có điểm vào cho ứng dụng của bạn. Điều đó có thể hữu ích ngay cả khi triển khai các xác nhận của bạn bên trong các chức năng UI có liên quan để phân biệt "luồng UI" với "luồng chính", như chụp ID luồng hiện tại khi một cửa sổ được tạo để so sánh với thay vì từ điểm nhập chính của ứng dụng (đó là ít nhất là giảm số lượng các giả định / hạn chế sử dụng mà việc triển khai chỉ áp dụng cho những gì thực sự phù hợp).

An toàn chủ đề thực sự là điểm vấp "gotcha" trong một nhóm cũ của tôi và trong trường hợp cụ thể của chúng tôi, tôi đã gọi nó là "tối ưu hóa vi mô" phản tác dụng nhất trong số chúng có thể phát sinh nhiều chi phí bảo trì hơn lắp ráp viết tay. Chúng tôi đã có phạm vi bảo hiểm mã khá toàn diện trong các thử nghiệm đơn vị của mình, cùng với các thử nghiệm tích hợp khá phức tạp, chỉ gặp phải các bế tắc và điều kiện chạy đua trong phần mềm trốn tránh các thử nghiệm của chúng tôi. Và đó là bởi vì các nhà phát triển đã sử dụng mã đa luồng một cách ngớ ngẩn mà không nhận thức được mọi tác động phụ có thể xảy ra trong chuỗi các lệnh gọi có thể xảy ra từ chính họ, với một ý tưởng khá ngây thơ rằng họ có thể khắc phục các lỗi như vậy chỉ bằng cách ném khóa quanh trái và phải,

Tôi đã bị lệch về hướng ngược lại như một kiểu trường học cũ không tin tưởng đa luồng, là một người đến sau thực sự để nắm lấy nó, và nghĩ rằng sự đúng đắn đánh bại hiệu suất đến mức hiếm khi sử dụng hết tất cả các lõi này mà chúng ta có bây giờ, cho đến khi tôi phát hiện ra mọi thứ như các hàm thuần túy và các thiết kế bất biến và các cấu trúc dữ liệu bền bỉ cuối cùng cho phép tôi sử dụng hoàn toàn phần cứng đó mà không phải lo lắng về thế giới về điều kiện chủng tộc và các bế tắc. Tôi phải thừa nhận rằng cho đến tận năm 2010 hoặc lâu hơn, tôi ghét sự đa luồng với một niềm đam mê ngoại trừ một vài vòng lặp song song ở đây và ở những khu vực tầm thường để lý giải về an toàn luồng, và ưa thích mã tuần tự hơn nhiều cho thiết kế sản phẩm cho tôi đau buồn với đa luồng trong các đội cũ.

Đối với tôi, cách đa luồng trước và sửa lỗi sau là một chiến lược khủng khiếp để đa luồng đến mức gần như khiến tôi ghét đa luồng ban đầu; bạn có thể đảm bảo rằng các thiết kế của bạn an toàn cho chủ đề chắc chắn và việc triển khai chúng chỉ sử dụng các chức năng với các đảm bảo tương tự (ví dụ: các hàm thuần túy) hoặc bạn tránh đa luồng. Điều đó có thể bắt gặp một chút giáo điều nhưng nó đánh bại việc phát hiện (hoặc tệ hơn, không phát hiện ra) các vấn đề khó tái tạo trong nhận thức muộn mà thử nghiệm. Sẽ không có điểm nào tối ưu hóa một động cơ tên lửa nếu điều đó sẽ dẫn đến việc nó dễ bị nổ bất ngờ giữa nửa đường màu xanh dọc hành trình ra ngoài vũ trụ.

Nếu bạn chắc chắn phải làm việc với mã không an toàn cho chuỗi, thì tôi không thấy đó là vấn đề cần giải quyết với kiểm thử đơn vị / tích hợp rất nhiều. Lý tưởng sẽ là hạn chế truy cập . Nếu mã GUI của bạn được tách rời khỏi logic nghiệp vụ, thì bạn có thể thực thi một thiết kế hạn chế quyền truy cập vào các cuộc gọi đó từ bất kỳ thứ gì khác ngoài luồng / đối tượng tạo ra nó *. Đó là chế độ lý tưởng đối với tôi là làm cho các luồng khác không thể gọi các hàm đó hơn là cố gắng đảm bảo chúng không hoạt động.

  • Vâng, tôi nhận ra rằng luôn có những cách xung quanh bất kỳ hạn chế thiết kế nào mà bạn thi hành thường là nơi trình biên dịch không thể bảo vệ bạn. Tôi chỉ đang nói thực tế; nếu bạn có thể trừu tượng đối tượng "GUI Thread" hoặc bất cứ điều gì, thì đó có thể là người duy nhất trao tham số cho các đối tượng / chức năng GUI và bạn có thể hạn chế đối tượng đó truy cập vào các luồng khác. Tất nhiên, nó có thể vượt qua và đào sâu và tìm đường xung quanh các vòng như vậy để truyền các chức năng / đối tượng GUI đã nói đến các luồng khác để gọi, nhưng ít nhất có một rào cản ở đó và bạn có thể gọi bất kỳ ai làm "kẻ ngốc" đó ", Và không sai, ít nhất, vì đã bỏ qua rõ ràng và tìm kiếm sơ hở cho những gì thiết kế rõ ràng đang cố gắng hạn chế. :-D Đó '
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.