Bằng cách này, tôi đang viết mã này có thể kiểm tra được, nhưng tôi có thiếu sót gì không?


13

Tôi có một giao diện được gọi là IContext. Đối với mục đích này, nó thực sự không quan trọng những gì nó làm ngoại trừ những điều sau đây:

T GetService<T>();

Phương pháp này làm là nhìn vào thùng chứa DI hiện tại của ứng dụng và cố gắng giải quyết sự phụ thuộc. Tôi nghĩ là khá chuẩn.

Trong ứng dụng ASP.NET MVC của tôi, hàm tạo của tôi trông như thế này.

protected MyControllerBase(IContext ctx)
{
    TheContext = ctx;
    SomeService = ctx.GetService<ISomeService>();
    AnotherService = ctx.GetService<IAnotherService>();
}

Vì vậy, thay vì thêm nhiều tham số trong hàm tạo cho mỗi dịch vụ (vì điều này sẽ thực sự gây phiền nhiễu và tốn thời gian cho các nhà phát triển mở rộng ứng dụng) Tôi đang sử dụng phương pháp này để nhận dịch vụ.

Bây giờ, nó cảm thấy sai . Tuy nhiên, cách mà tôi hiện đang biện minh nó trong đầu là thế này - tôi có thể chế giễu nó .

Tôi có thể. Sẽ không khó để giả lập IContextđể kiểm tra Bộ điều khiển. Dù sao tôi cũng phải:

public class MyMockContext : IContext
{
    public T GetService<T>()
    {
        if (typeof(T) == typeof(ISomeService))
        {
            // return another mock, or concrete etc etc
        }

        // etc etc
    }
}

Nhưng như tôi đã nói, nó cảm thấy sai. Bất kỳ suy nghĩ / lạm dụng hoan nghênh.


8
Đây được gọi là Trình định vị dịch vụ và tôi không thích nó. Đã có rất nhiều bài viết về chủ đề này - xem martinfowler.com/articles/injection.htmlblog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Potype để bắt đầu.
Benjamin Hodgson

Từ bài viết của Martin Fowler: "Tôi thường nghe thấy phàn nàn rằng những loại định vị dịch vụ này là một điều xấu bởi vì chúng không thể kiểm tra được vì bạn không thể thay thế việc triển khai cho chúng. Chắc chắn bạn có thể thiết kế chúng một cách tồi tệ loại rắc rối, nhưng bạn không phải làm thế. Trong trường hợp này, đối tượng định vị dịch vụ chỉ là một người giữ dữ liệu đơn giản. Tôi có thể dễ dàng tạo trình định vị bằng các triển khai thử nghiệm các dịch vụ của mình. " Bạn có thể giải thích tại sao bạn không thích nó? Có lẽ trong một câu trả lời?
LiverpoolsNumber9

8
Anh ấy đúng, đây là thiết kế xấu. Thật dễ dàng : public SomeClass(Context c). Mã này khá rõ ràng phải không? Nó tuyên bố, that SomeClassphụ thuộc vào a Context. Err, nhưng chờ đợi, nó không! Nó chỉ phụ thuộc vào sự phụ thuộc Xmà nó nhận được từ Ngữ cảnh. Điều đó có nghĩa là, mỗi khi bạn thay đổi Contextcó thể bị hỏng SomeObject, mặc dù bạn chỉ thay đổi Contexts Y. Nhưng vâng, bạn biết rằng bạn chỉ thay đổi Ykhông X, vậy SomeClasslà tốt. Nhưng viết mã tốt không phải là về những gì bạn biết mà là những gì nhân viên mới biết khi anh ta nhìn vào mã của bạn lần đầu tiên.
valenterry

@DocBrown Đối với tôi đó chính xác là những gì tôi đã nói - Tôi không thấy sự khác biệt ở đây. Bạn có thể vui lòng giải thích thêm?
valenterry

1
@DocBrown Tôi thấy quan điểm của bạn bây giờ. Vâng, nếu Bối cảnh của anh ta chỉ là một bó của tất cả các phụ thuộc, thì đây không phải là thiết kế tồi. Tuy nhiên, nó có thể được đặt tên xấu, nhưng đó cũng chỉ là một giả định. OP nên làm rõ nếu có nhiều phương thức (đối tượng bên trong) của bối cảnh. Ngoài ra, thảo luận về mã là tốt, nhưng đây là lập trình viên .stackexchange vì vậy với tôi, chúng ta cũng nên cố gắng xem "đằng sau" những điều để làm cho OP cải thiện.
valenterry

Câu trả lời:


5

Có một thay vì nhiều tham số trong hàm tạo không phải là phần có vấn đề trong thiết kế này . Miễn là IContextlớp học của bạn không là gì ngoài mặt tiền dịch vụ , cụ thể là cung cấp các phụ thuộc được sử dụng MyControllerBasevà không phải là công cụ định vị dịch vụ chung được sử dụng trong toàn bộ mã của bạn, một phần mã của bạn là IMHO ok.

Ví dụ đầu tiên của bạn có thể được thay đổi thành

protected MyControllerBase(IContext ctx)
{
    TheContext = ctx;
    SomeService = ctx.GetSomeService();
    AnotherService = ctx.GetAnotherService();
}

đó sẽ không phải là một sự thay đổi thiết kế đáng kể của MyControllerBase . Nếu thiết kế này tốt hay xấu chỉ phụ thuộc vào thực tế nếu bạn muốn

  • chắc chắn TheContext, SomeServiceAnotherServiceluôn tất cả khởi tạo với các đối tượng giả, hoặc tất cả trong số họ với các đối tượng thực
  • hoặc, để cho phép khởi tạo chúng với các kết hợp khác nhau của 3 đối tượng (có nghĩa là, trong trường hợp này, bạn sẽ cần phải truyền các tham số riêng lẻ)

Vì vậy, chỉ sử dụng một tham số thay vì 3 trong hàm tạo có thể hoàn toàn hợp lý.

Điều có vấn đề là IContextphơi bày GetServicephương pháp ở nơi công cộng. IMHO bạn nên tránh điều này, thay vào đó hãy giữ rõ ràng "phương pháp nhà máy". Vì vậy, nó có ổn không khi thực hiện GetSomeServiceGetAnotherServicecác phương thức từ ví dụ của tôi bằng cách sử dụng trình định vị dịch vụ? IMHO mà phụ thuộc. Miễn là IContextlớp tiếp tục duy trì một nhà máy trừu tượng đơn giản cho mục đích cụ thể là cung cấp một danh sách rõ ràng các đối tượng dịch vụ, đó là IMHO có thể chấp nhận được. Các nhà máy trừu tượng thường chỉ là mã "keo", không phải tự kiểm tra đơn vị. Tuy nhiên, bạn nên tự hỏi mình trong bối cảnh của các phương pháp nhưGetSomeService , nếu bạn thực sự cần một trình định vị dịch vụ, hoặc nếu một lệnh gọi hàm tạo rõ ràng sẽ không đơn giản hơn.

Vì vậy, hãy cẩn thận, khi bạn tuân theo một thiết kế trong đó việc IContexttriển khai chỉ là một trình bao bọc xung quanh một GetServicephương thức chung, chung , cho phép giải quyết bất kỳ sự thiếu hụt tùy ý nào bằng các lớp tùy ý, thì mọi thứ đều áp dụng những gì @BenjaminHodgson đã viết trong câu trả lời của anh ấy.


Tôi đồng ý với điều này. Vấn đề với ví dụ là GetServicephương pháp chung . Tái cấu trúc cho các phương thức được đặt tên và gõ rõ ràng là tốt hơn. Vẫn tốt hơn là đánh vần các phụ thuộc của việc IContexttriển khai một cách rõ ràng trong hàm tạo.
Benjamin Hodgson

1
@BenjaminHodgson: đôi khi có thể tốt hơn, nhưng không phải lúc nào cũng tốt hơn. Bạn lưu ý mùi mã khi danh sách tham số ctor ngày càng dài hơn. Xem câu trả lời trước đây của tôi tại đây: lập trình
Doc Brown

@DocBrown "mùi mã" của quá trình xây dựng quá mức là dấu hiệu cho thấy vi phạm SRP, đây là vấn đề thực sự. Đơn giản chỉ cần gói một số dịch vụ vào một lớp Mặt tiền, chỉ để lộ chúng là thuộc tính, không có gì để giải quyết nguồn gốc của vấn đề. Do đó, Mặt tiền không nên là một trình bao bọc đơn giản xung quanh các thành phần khác, nhưng lý tưởng nhất là cung cấp API đơn giản hóa để tiêu thụ chúng (hoặc nên đơn giản hóa chúng theo một cách khác) ...
AlexFoxGill 15/1/2015

... Hãy nhớ rằng "mùi mã" như quá trình xây dựng quá mức không phải là vấn đề. Đó chỉ là một gợi ý rằng có một vấn đề sâu hơn trong mã thường có thể được giải quyết bằng cách tái cấu trúc một cách hợp lý một khi vấn đề đã được xác định
AlexFoxGill

Bạn đã ở đâu khi Microsoft nướng cái này vào IValidatableObject?
RubberDuck

15

Thiết kế này được gọi là Trình định vị dịch vụ * và tôi không thích nó. Có rất nhiều tranh luận chống lại nó:

Dịch vụ định vị kết nối bạn với container của bạn . Sử dụng phép nội xạ phụ thuộc thường xuyên (trong đó hàm tạo sẽ loại bỏ các phụ thuộc một cách rõ ràng), bạn có thể thay thế đơn giản bộ chứa của mình bằng một bộ khác hoặc quay lại new-expression. Với IContextđiều đó là không thực sự có thể.

Dịch vụ định vị ẩn phụ thuộc . Là một khách hàng, rất khó để nói những gì bạn cần để xây dựng một thể hiện của một lớp. Bạn cần một số loại IContext, nhưng bạn cũng cần thiết lập bối cảnh để trả về các đối tượng chính xác để thực hiện MyControllerBasecông việc. Điều này hoàn toàn không rõ ràng từ chữ ký của nhà xây dựng. Với DI thông thường, trình biên dịch sẽ cho bạn biết chính xác những gì bạn cần. Nếu lớp học của bạn có nhiều phụ thuộc, bạn sẽ cảm thấy đau đớn vì nó sẽ thúc đẩy bạn tái cấu trúc. Dịch vụ định vị che giấu các vấn đề với thiết kế xấu.

Dịch vụ định vị gây ra lỗi thời gian chạy . Nếu bạn gọi GetServicevới một tham số loại xấu, bạn sẽ nhận được một ngoại lệ. Nói cách khác, GetServicechức năng của bạn không phải là một chức năng tổng thể. (Tổng số hàm là một ý tưởng từ thế giới FP, nhưng về cơ bản, điều đó có nghĩa là các hàm phải luôn trả về một giá trị.) Tốt hơn là để trình biên dịch trợ giúp và cho bạn biết khi bạn gặp lỗi phụ thuộc.

Dịch vụ định vị vi phạm Nguyên tắc thay thế Liskov . Vì hành vi của nó thay đổi dựa trên đối số loại, Trình định vị dịch vụ có thể được xem như thể nó thực sự có vô số phương thức trên giao diện! Lập luận này được đánh vần chi tiết ở đây .

Dịch vụ định vị là khó để kiểm tra . Bạn đã đưa ra một ví dụ về giả mạo IContextđể kiểm tra, điều này tốt, nhưng chắc chắn tốt hơn là không phải viết mã đó ngay từ đầu. Chỉ cần tiêm trực tiếp phụ thuộc giả của bạn, mà không cần thông qua công cụ định vị dịch vụ của bạn.

Nói tóm lại, đừng làm điều đó . Nó có vẻ như là một giải pháp quyến rũ cho vấn đề của các lớp học với rất nhiều sự phụ thuộc, nhưng về lâu dài bạn sẽ làm cho cuộc sống của bạn trở nên khốn khổ.

* Tôi đang xác định Bộ định vị dịch vụ là một đối tượng với một Resolve<T>phương thức chung có khả năng giải quyết các phụ thuộc tùy ý và được sử dụng trong toàn bộ cơ sở mã (không chỉ tại Root gốc). Điều này không giống với Mặt tiền dịch vụ (một đối tượng kết hợp một số nhóm phụ thuộc nhỏ đã biết) hoặc Tóm tắt Factory (một đối tượng tạo các thể hiện của một loại duy nhất - loại của một Nhà máy trừu tượng có thể chung chung nhưng phương thức thì không) .


1
Bạn đang phàn nàn về mẫu Trình định vị dịch vụ (mà tôi đồng ý). Nhưng trên thực tế, trong ví dụ của OP, MyControllerBasekhông được ghép nối với một thùng chứa DI cụ thể, đây cũng không phải là "ví dụ" thực sự cho mẫu chống trình định vị dịch vụ.
Doc Brown

@DocBrown Tôi đồng ý. Không phải vì nó làm cho cuộc sống của tôi dễ dàng hơn, mà bởi vì hầu hết các ví dụ nêu trên không liên quan đến mã của tôi.
LiverpoolsNumber9

2
Đối với tôi, dấu hiệu của mẫu chống định vị dịch vụ là GetService<T>phương thức chung . Giải quyết các phụ thuộc tùy ý là mùi thực, hiện diện và chính xác trong ví dụ của OP.
Benjamin Hodgson

1
Một vấn đề khác khi sử dụng Trình định vị dịch vụ là nó làm giảm tính linh hoạt: bạn chỉ có thể có một triển khai của mỗi giao diện dịch vụ. Nếu bạn xây dựng hai lớp dựa vào IFrobnicator, nhưng sau đó quyết định rằng một lớp nên sử dụng triển khai DefaultFrobnicator ban đầu của bạn, nhưng lớp kia thực sự phải sử dụng trình trang trí CacheingFrobnicator xung quanh nó, trong khi đó bạn phải thay đổi mã hiện có tiêm phụ thuộc trực tiếp tất cả những gì bạn phải làm là thay đổi mã thiết lập (hoặc tệp cấu hình nếu bạn đang sử dụng khung DI). Do đó, đây là một vi phạm OCP.
Jules

1
@DocBrown GetService<T>()Phương thức cho phép các lớp tùy ý được yêu cầu: "Phương thức này làm gì là nhìn vào bộ chứa DI hiện tại của ứng dụng và cố gắng giải quyết sự phụ thuộc. Tôi nghĩ là chuẩn." . Tôi đã trả lời bình luận của bạn ở đầu câu trả lời này. Đây là 100% Công cụ định vị dịch vụ
AlexFoxGill

5

Những lý lẽ tốt nhất chống lại mô hình chống định vị dịch vụ được Mark Seemann nêu rõ ràng vì vậy tôi sẽ không đi sâu vào lý do tại sao đây là một ý tưởng tồi - đó là một hành trình học hỏi mà bạn phải dành thời gian để tự hiểu (Tôi cũng đề nghị cuốn sách của Mark ).

OK để trả lời câu hỏi - hãy nêu lại vấn đề thực tế của bạn :

Vì vậy, thay vì thêm nhiều tham số trong hàm tạo cho mỗi dịch vụ (vì điều này sẽ thực sự gây phiền nhiễu và tốn thời gian cho các nhà phát triển mở rộng ứng dụng) Tôi đang sử dụng phương pháp này để nhận dịch vụ.

Có một câu hỏi giải quyết vấn đề này trên StackOverflow . Như đã đề cập trong một trong những ý kiến ​​đó:

Nhận xét tốt nhất: "Một trong những lợi ích tuyệt vời của Con Contortor tiêm là nó làm cho việc vi phạm Nguyên tắc Trách nhiệm Đơn lẻ trở nên rõ ràng."

Bạn đang tìm sai chỗ cho giải pháp cho vấn đề của bạn. Điều quan trọng là phải biết khi một lớp học đang làm quá nhiều. Trong trường hợp của bạn, tôi nghi ngờ rằng không cần "Bộ điều khiển cơ sở". Trên thực tế, trong OOP hầu như không cần phải thừa kế chút nào . Sự khác biệt trong hành vi và chức năng được chia sẻ hoàn toàn có thể đạt được thông qua việc sử dụng các giao diện phù hợp, điều này thường dẫn đến mã được đóng gói và đóng gói tốt hơn - và không cần phải chuyển phụ thuộc vào các nhà xây dựng siêu lớp.

Trong tất cả các dự án tôi đã làm việc ở nơi có Bộ điều khiển cơ sở, nó được thực hiện hoàn toàn cho mục đích chia sẻ các thuộc tính và phương thức thuận tiện, chẳng hạn như IsUserLoggedIn()GetCurrentUserId(). DỪNG . Đây là sự lạm dụng khủng khiếp của thừa kế. Thay vào đó, hãy tạo một thành phần phơi bày các phương thức này và phụ thuộc vào nó ở nơi bạn cần. Bằng cách này, các thành phần của bạn sẽ vẫn có thể kiểm tra được và sự phụ thuộc của chúng sẽ rõ ràng.

Ngoài bất cứ điều gì khác, khi sử dụng mẫu MVC tôi luôn khuyên dùng các bộ điều khiển mỏng . Bạn có thể đọc thêm về điều này ở đây nhưng bản chất của mẫu rất đơn giản, rằng các bộ điều khiển trong MVC chỉ nên làm một việc: xử lý các đối số được truyền bởi khung MVC, ủy thác các mối quan tâm khác cho một số thành phần khác. Đây một lần nữa là Nguyên tắc Trách nhiệm duy nhất trong công việc.

Nó thực sự sẽ giúp biết trường hợp sử dụng của bạn để đưa ra phán đoán chính xác hơn, nhưng thực lòng tôi không thể nghĩ ra bất kỳ kịch bản nào trong đó một lớp cơ sở thích hợp hơn với các phụ thuộc được bao bọc kỹ lưỡng.


+1 - đây là một câu hỏi mới cho câu hỏi mà không câu trả lời nào khác thực sự giải quyết
Benjamin Hodgson

1
"Một trong những lợi ích tuyệt vời của Con Contortor tiêm là nó làm cho việc vi phạm Nguyên tắc Trách nhiệm duy nhất trở nên rõ ràng rõ ràng" Yêu điều đó. Câu trả lời thực sự tốt. Đừng đồng ý với tất cả. Trường hợp sử dụng của tôi là, đủ vui, không phải sao chép mã trong một hệ thống sẽ có hơn 100 bộ điều khiển (ít nhất). Tuy nhiên, lại SRP - mỗi dịch vụ được tiêm có một trách nhiệm duy nhất, cũng như bộ điều khiển sử dụng tất cả chúng - làm thế nào một người khúc xạ đó ??!
LiverpoolsNumber9

1
@ LiverpoolsNumber9 Chìa khóa là để chuyển chức năng từ BaseContoder thành các phần phụ thuộc, cho đến khi bạn không còn gì trong BaseContoder ngoại trừ các nhiệm vụ protectedmột lớp cho các thành phần mới. Sau đó, bạn có thể mất BaseContoder và thay thế các protectedphương thức đó bằng các cuộc gọi trực tiếp đến các phụ thuộc - điều này sẽ đơn giản hóa mô hình của bạn và làm cho tất cả các phụ thuộc của bạn rõ ràng (đó là một điều rất tốt trong một dự án với 100 bộ điều khiển!)
AlexFoxGill

@ LiverpoolsNumber9 - nếu bạn có thể sao chép một phần BaseContoder của mình sang pastebin, tôi có thể đưa ra một số gợi ý cụ thể
AlexFoxGill

-2

Tôi đang thêm một câu trả lời cho điều này dựa trên sự đóng góp của mọi người khác. Cảm ơn mọi người rất nhiều. Đầu tiên đây là câu trả lời của tôi: "Không, không có gì sai với nó".

Câu trả lời "Mặt tiền dịch vụ" của Doc Brown Tôi đã chấp nhận câu trả lời này bởi vì điều tôi đang tìm kiếm (nếu câu trả lời là "không") là một số ví dụ hoặc một số mở rộng dựa trên những gì tôi đang làm. Ông đã cung cấp điều này khi đề xuất rằng A) nó có tên và B) có lẽ có nhiều cách tốt hơn để làm điều này.

Câu trả lời "Trình định vị dịch vụ" của Benjamin Hodgson Nhiều như tôi đánh giá cao những kiến ​​thức tôi có được ở đây, những gì tôi có không phải là "Trình định vị dịch vụ". Đó là một "Mặt tiền dịch vụ". Tất cả mọi thứ trong câu trả lời này là chính xác nhưng không phải cho hoàn cảnh của tôi.

Câu trả lời của USR

Tôi sẽ giải quyết vấn đề này chi tiết hơn:

Bạn đang từ bỏ rất nhiều thông tin tĩnh theo cách này. Bạn đang trì hoãn các quyết định về thời gian chạy như nhiều ngôn ngữ động khác. Bằng cách đó, bạn mất khả năng xác minh tĩnh (an toàn), hỗ trợ tài liệu và công cụ (tự động hoàn tất, tái cấu trúc, tìm công dụng, luồng dữ liệu).

Tôi không mất bất kỳ dụng cụ nào và tôi sẽ không mất bất kỳ kiểu gõ "tĩnh" nào. Mặt tiền dịch vụ sẽ trả về những gì tôi đã cấu hình trong thùng chứa DI, hoặc default(T). Và những gì nó trả về là "đánh máy". Sự phản ánh được gói gọn.

Tôi không thấy lý do tại sao việc thêm các dịch vụ bổ sung làm đối số của nhà xây dựng là một gánh nặng lớn.

Nó chắc chắn không phải là "hiếm". Vì tôi đang sử dụng bộ điều khiển cơ sở , mỗi lần tôi cần thay đổi hàm tạo, tôi có thể phải thay đổi 10, 100, 1000 bộ điều khiển khác.

Nếu bạn sử dụng khung tiêm phụ thuộc, bạn thậm chí sẽ không phải truyền thủ công các giá trị tham số. Sau đó, một lần nữa bạn mất một số lợi thế tĩnh nhưng không nhiều.

Tôi đang sử dụng tiêm phụ thuộc. Đó là điểm.

Và cuối cùng, nhận xét của Jules về câu trả lời của Benjamin Tôi không mất đi sự linh hoạt. Đó là mặt tiền dịch vụ của tôi . Tôi có thể thêm bao nhiêu tham số GetService<T>mà tôi muốn phân biệt giữa các cài đặt khác nhau, giống như cách thực hiện khi định cấu hình bộ chứa DI. Vì vậy, ví dụ, tôi có thể thay đổi GetService<T>()để GetService<T>(string extraInfo = null)giải quyết "vấn đề tiềm năng" này.


Dù sao, một lần nữa cảm ơn mọi người. Nó thực sự hữu ích. Chúc mừng.


4
Tôi không đồng ý rằng bạn có Mặt tiền Dịch vụ ở đây. Các GetService<T>()phương pháp có thể (cố gắng) quyết tâm phụ thuộc tùy ý . Điều này làm cho nó trở thành một Trình định vị dịch vụ, không phải là Mặt tiền dịch vụ, như tôi đã giải thích trong phần chú thích câu trả lời của mình. Nếu bạn thay thế nó bằng một tập hợp GetServiceX()/ GetServiceY()phương thức nhỏ, như @DocBrown gợi ý, thì đó sẽ là Mặt tiền.
Benjamin Hodgson

2
Bạn nên đọc và chú ý đến phần cuối của bài viết này về Trình định vị dịch vụ trừu tượng , về cơ bản là những gì bạn đang làm. Tôi đã thấy toàn bộ dự án chống tham nhũng này - đặc biệt chú ý đến câu nói "nó sẽ khiến cuộc sống của bạn như một nhà phát triển bảo trì tồi tệ hơn bởi vì bạn sẽ cần phải sử dụng một lượng lớn năng lượng não để nắm bắt ý nghĩa của mọi thay đổi bạn thực hiện "
AlexFoxGill

3
Khi gõ tĩnh - bạn đang thiếu điểm. Nếu tôi cố gắng viết mã không cung cấp một lớp bằng DI với tham số hàm tạo mà nó cần, nó sẽ không biên dịch. Đó là sự an toàn, thời gian biên dịch chống lại các vấn đề. Nếu bạn viết mã, cung cấp cho nhà xây dựng của bạn một IContext chưa được cấu hình chính xác để cung cấp đối số mà nó thực sự cần, thì nó sẽ biên dịch và thất bại trong thời gian chạy
Ben Aaronson

3
@ LiverpoolsNumber9 Vậy thì tại sao lại có một ngôn ngữ được đánh máy mạnh mẽ? Lỗi trình biên dịch là dòng bảo vệ đầu tiên chống lại lỗi. Bài kiểm tra đơn vị là thứ hai. Bạn sẽ không được chọn bởi các bài kiểm tra đơn vị vì đó là sự tương tác giữa một lớp và sự phụ thuộc của nó, vì vậy bạn sẽ tham gia vào tuyến phòng thủ thứ ba: kiểm tra tích hợp. Tôi không biết bạn có thường xuyên chạy chúng không, nhưng giờ bạn đang nói về phản hồi theo thứ tự vài phút trở lên, thay vì mili giây mà IDE của bạn phải gạch chân một lỗi biên dịch.
Ben Aaronson

2
@ LiverpoolsNumber9 sai: các bài kiểm tra của bạn bây giờ phải điền IContextvà tiêm, và điều quan trọng là: nếu bạn thêm một phụ thuộc mới, mã sẽ vẫn biên dịch - ngay cả khi các bài kiểm tra sẽ thất bại trong thời gian chạy
AlexFoxGill vào
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.