Đơn vị thử nghiệm phương pháp riêng trong C #


291

Visual Studio cho phép kiểm tra đơn vị các phương thức riêng tư thông qua lớp truy cập được tạo tự động. Tôi đã viết một bài kiểm tra về một phương thức riêng tư biên dịch thành công, nhưng nó thất bại trong thời gian chạy. Một phiên bản khá tối thiểu của mã và thử nghiệm là:

//in project MyProj
class TypeA
{
    private List<TypeB> myList = new List<TypeB>();

    private class TypeB
    {
        public TypeB()
        {
        }
    }

    public TypeA()
    {
    }

    private void MyFunc()
    {
        //processing of myList that changes state of instance
    }
}    

//in project TestMyProj           
public void MyFuncTest()
{
    TypeA_Accessor target = new TypeA_Accessor();
    //following line is the one that throws exception
    target.myList.Add(new TypeA_Accessor.TypeB());
    target.MyFunc();

    //check changed state of target
}

Lỗi thời gian chạy là:

Object of type System.Collections.Generic.List`1[MyProj.TypeA.TypeA_Accessor+TypeB]' cannot be converted to type 'System.Collections.Generic.List`1[MyProj.TypeA.TypeA+TypeB]'.

Theo intellisense - và do đó tôi đoán trình biên dịch - đích là loại TypeA_Accessor. Nhưng trong thời gian chạy, nó là kiểu TypeA, và do đó danh sách thêm không thành công.

Có cách nào tôi có thể ngăn chặn lỗi này? Hoặc, có lẽ nhiều khả năng, những người khác có lời khuyên nào khác (tôi dự đoán có thể "không thử nghiệm phương pháp riêng tư" và "không có thử nghiệm đơn vị thao túng trạng thái của các đối tượng").


Bạn cần một người truy cập cho lớp TypeB riêng. Accessor TypeA_Accessor cung cấp quyền truy cập vào các phương thức riêng tư và được bảo vệ của TypeA. Tuy nhiên TypeB không phải là một phương pháp. Đó là một lớp học.
Dima

Accessor cung cấp quyền truy cập vào các phương thức, thành viên, thuộc tính và sự kiện riêng tư / được bảo vệ. Nó không cung cấp quyền truy cập vào các lớp riêng tư / được bảo vệ trong lớp của bạn. Và các lớp riêng tư / được bảo vệ (TypeB) chỉ được sử dụng cho các phương thức sở hữu lớp (TypeA). Vì vậy, về cơ bản, bạn đang cố gắng thêm lớp riêng (TypeB) từ bên ngoài TypeA vào "myList" là riêng tư. Vì bạn đang sử dụng accessor, không có vấn đề gì khi truy cập myList. Tuy nhiên, bạn không thể sử dụng TypeB thông qua accessor. Giải pháp khả thi sẽ là di chuyển TypeB ra ngoài TypeA. Nhưng nó có thể phá vỡ thiết kế của bạn.
Dima

Cảm thấy rằng việc kiểm tra các phương thức riêng tư nên được thực hiện bằng stackoverflow.com/questions/250692/ trên
nate_weldon

Câu trả lời:


274

Có, không Kiểm tra các phương thức riêng tư .... Ý tưởng của kiểm tra đơn vị là kiểm tra đơn vị bằng 'API' công khai.

Nếu bạn thấy bạn cần kiểm tra nhiều hành vi riêng tư, rất có thể bạn có một 'lớp' mới ẩn trong lớp bạn đang cố kiểm tra, giải nén nó và kiểm tra nó bằng giao diện công khai.

Một lời khuyên / Công cụ tư duy ..... Có một ý kiến ​​cho rằng không có phương pháp nào nên riêng tư. Có nghĩa là tất cả các phương thức nên sống trên một giao diện chung của một đối tượng .... nếu bạn cảm thấy bạn cần đặt nó ở chế độ riêng tư, rất có thể nó sẽ sống trên một đối tượng khác.

Lời khuyên này không thực sự hiệu quả trong thực tế, nhưng chủ yếu là lời khuyên tốt và thường nó sẽ thúc đẩy mọi người phân hủy các vật thể của họ thành các vật thể nhỏ hơn.


385
Tôi không đồng ý. Trong OOD, các phương thức và thuộc tính riêng là một cách nội tại để không lặp lại chính mình ( en.wikipedia.org/wiki/Don%27t numpeat_yourself ). Ý tưởng đằng sau việc lập trình và đóng gói hộp đen là để ẩn các chi tiết kỹ thuật khỏi người đăng ký. Vì vậy, thực sự cần thiết phải có các phương thức và thuộc tính riêng không tầm thường trong mã của bạn. Và nếu nó không tầm thường, nó cần phải được kiểm tra.
AxD

8
không phải là nội tại, một số ngôn ngữ OO không có phương thức riêng tư, các thuộc tính riêng tư có thể chứa các đối tượng có giao diện công cộng có thể được kiểm tra.
Keith Nicholas

7
Quan điểm của lời khuyên này là nếu đối tượng của bạn làm một việc và là KHÔ, thì thường có rất ít lý do để có các phương thức riêng tư. Thông thường các phương thức riêng tư làm một cái gì đó mà đối tượng không thực sự chịu trách nhiệm nhưng khá hữu ích, nếu không tầm thường, thì nó thường là một đối tượng khác vì nó có khả năng vi phạm SRP
Keith Nicholas

28
Sai lầm. Bạn có thể muốn sử dụng các phương thức riêng tư để tránh sao chép mã. Hoặc để xác nhận. Hoặc cho nhiều mục đích khác mà thế giới công cộng không nên biết.
Jorj

37
Khi bạn đã bị bỏ rơi trên một cơ sở mã OO được thiết kế khủng khiếp và được yêu cầu "cải thiện dần dần", thật đáng thất vọng khi thấy tôi không thể có một số thử nghiệm đầu tiên cho các phương pháp riêng tư. Yeh, có lẽ trong sách giáo khoa, những phương pháp này sẽ không có ở đây, nhưng trong thế giới thực, chúng ta có những người dùng có yêu cầu về sản phẩm. Tôi không thể thực hiện tái cấu trúc "Nhìn vào mã sạch" mà không cần thực hiện một số thử nghiệm trong dự án. Cảm thấy giống như một ví dụ khác về việc buộc các lập trình viên thực hành vào những con đường ngây thơ có vẻ tốt, nhưng không tính đến chuyện bừa bộn thực sự.
dune.rocks

666

Bạn có thể sử dụng lớp PrivateObject

Class target = new Class();
PrivateObject obj = new PrivateObject(target);
var retVal = obj.Invoke("PrivateMethod");
Assert.AreEqual(expectedVal, retVal);

25
Đây là câu trả lời đúng, bây giờ Microsoft đã thêm PrivateObject.
Zoey

4
Câu trả lời hay nhưng xin lưu ý rằng PrivateMethod cần được "bảo vệ" thay vì "riêng tư".
HerbalMart

23
@HerbalMart: Có lẽ tôi hiểu nhầm bạn, nhưng nếu bạn gợi ý rằng PrivateObject chỉ có thể truy cập các thành viên được bảo vệ và không phải là riêng tư, bạn đã nhầm.
kmote

17
@JeffPearce Đối với các phương thức tĩnh, bạn có thể sử dụng "PrivateType pt = new PrivateType (typeof (MyClass));", sau đó gọi InvokeStatic trên đối tượng pt như bạn sẽ gọi Invoke trên một đối tượng riêng tư.
Steve Hibbert

12
Trong trường hợp bất kỳ ai thắc mắc về MSTest.TestFramework v1.2.1 - các lớp PrivateObject và PrivateType không khả dụng cho các dự án nhắm mục tiêu .NET Core 2.0 - Có một vấn đề github cho việc này: github.com/Microsoft/testfx/issues/366
shiitake

98

Không có gì gọi là tiêu chuẩn hay thực tiễn tốt nhất, có lẽ chúng chỉ là những ý kiến ​​phổ biến.

Điều này cũng đúng cho cuộc thảo luận này.

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

Tất cả phụ thuộc vào những gì bạn nghĩ là một đơn vị, nếu bạn nghĩ UNIT là một lớp thì bạn sẽ chỉ đánh vào phương thức công khai. Nếu bạn nghĩ rằng UNIT là dòng mã đánh vào các phương thức riêng tư sẽ không khiến bạn cảm thấy có lỗi.

Nếu bạn muốn gọi các phương thức riêng tư, bạn có thể sử dụng lớp "PrivateObject" và gọi phương thức gọi. Bạn có thể xem video youtube độc ​​lập này ( http://www.youtube.com/watch?v=Vq6Gcs9LrPQ ) cho biết cách sử dụng "PrivateObject" và cũng thảo luận xem việc thử nghiệm phương pháp riêng tư có hợp lý hay không.


55

Một suy nghĩ khác ở đây là mở rộng thử nghiệm cho các lớp / phương thức "nội bộ", mang lại cảm giác hộp trắng hơn cho thử nghiệm này. Bạn có thể sử dụng InternalsVisibleToAttribution trên cụm để hiển thị những thứ này cho các mô-đun thử nghiệm đơn vị riêng biệt.

Kết hợp với lớp niêm phong, bạn có thể tiếp cận việc đóng gói như vậy mà phương thức kiểm tra chỉ hiển thị từ việc lắp ráp không chính xác các phương thức của bạn. Hãy xem xét rằng phương thức được bảo vệ trong lớp niêm phong là thực tế riêng tư.

[assembly: InternalsVisibleTo("MyCode.UnitTests")]
namespace MyCode.MyWatch
{
    #pragma warning disable CS0628 //invalid because of InternalsVisibleTo
    public sealed class MyWatch
    {
        Func<DateTime> _getNow = delegate () { return DateTime.Now; };


       //construktor for testing purposes where you "can change DateTime.Now"
       internal protected MyWatch(Func<DateTime> getNow)
       {
           _getNow = getNow;
       }

       public MyWatch()
       {            
       }
   }
}

Và kiểm tra đơn vị:

namespace MyCode.UnitTests
{

[TestMethod]
public void TestminuteChanged()
{
    //watch for traviling in time
    DateTime baseTime = DateTime.Now;
    DateTime nowforTesting = baseTime;
    Func<DateTime> _getNowForTesting = delegate () { return nowforTesting; };

    MyWatch myWatch= new MyWatch(_getNowForTesting );
    nowforTesting = baseTime.AddMinute(1); //skip minute
    //TODO check myWatch
}

[TestMethod]
public void TestStabilityOnFebruary29()
{
    Func<DateTime> _getNowForTesting = delegate () { return new DateTime(2024, 2, 29); };
    MyWatch myWatch= new MyWatch(_getNowForTesting );
    //component does not crash in overlap year
}
}

Tôi không chắc là tôi hiểu. InternalsVisibleToAttribution làm cho các phương thức và thuộc tính được đánh dấu là "nội bộ" có thể truy cập được, nhưng các trường và phương thức của tôi là "riêng tư". Bạn đang đề nghị tôi thay đổi mọi thứ từ riêng tư sang nội bộ? Tôi nghĩ rằng tôi hiểu lầm.
Junichiro

2
Vâng, đó là những gì tôi đề nghị. Đó là một chút "hacky", nhưng ít nhất họ không "công khai".
Jeff

27
Đây là một câu trả lời tuyệt vời chỉ vì nó không nói "không thử nghiệm các phương pháp riêng tư" nhưng vâng, nó khá "hacky". Tôi ước có một giải pháp. IMO thật tệ khi nói "phương pháp riêng tư không nên thử nghiệm" bởi vì theo cách tôi thấy: nó tương đương với "phương pháp riêng tư không nên chính xác".
MasterMastic

5
ya ken, tôi cũng bối rối bởi những người cho rằng các phương pháp riêng tư không nên được thử nghiệm trong bài kiểm tra đơn vị. API công cộng là đầu ra, nhưng đôi khi thực hiện sai cũng cho đầu ra đúng. Hoặc việc triển khai đã tạo ra một số tác dụng phụ xấu, ví dụ như giữ các tài nguyên không cần thiết, tham chiếu các đối tượng ngăn không cho nó được thu thập bởi gc ... vv. Trừ khi họ cung cấp thử nghiệm khác có thể bao gồm các phương thức riêng tư hơn là thử nghiệm đơn vị, nếu không tôi sẽ xem xét rằng họ không thể duy trì mã được kiểm tra 100%.
mr.Pony

Tôi đồng ý với MasterMastic. Đây phải là câu trả lời được chấp nhận.
XDS

27

Một cách để kiểm tra các phương pháp riêng là thông qua sự phản ánh. Điều này cũng áp dụng cho NUnit và XUnit:

MyObject objUnderTest = new MyObject();
MethodInfo methodInfo = typeof(MyObject).GetMethod("SomePrivateMethod", BindingFlags.NonPublic | BindingFlags.Instance);
object[] parameters = {"parameters here"};
methodInfo.Invoke(objUnderTest, parameters);

call methods tĩnh không tĩnh ?
Kiquenet

2
Nhược điểm của các phương thức dựa vào phản xạ là chúng có xu hướng bị phá vỡ khi bạn đổi tên các phương thức bằng R #. Nó có thể không phải là một vấn đề lớn đối với các dự án nhỏ nhưng trên cơ sở mã lớn, nó trở nên khó chịu khi các bài kiểm tra đơn vị phá vỡ theo kiểu như vậy và sau đó phải đi xung quanh và sửa chữa nhanh chóng. Theo nghĩa này, tiền của tôi đi vào câu trả lời của Jeff.
XDS

2
@XDS Tên xấu quá () không hoạt động để lấy tên của một phương thức riêng tư từ bên ngoài lớp của nó.
Gabriel Morin

12

Ermh ... Đến đây với cùng một vấn đề chính xác: Thử nghiệm một phương pháp riêng đơn giản nhưng quan trọng . Sau khi đọc chủ đề này, nó có vẻ như "Tôi muốn khoan lỗ đơn giản này trong miếng kim loại đơn giản này và tôi muốn đảm bảo chất lượng đáp ứng thông số kỹ thuật", và sau đó đến "Được rồi, điều này không dễ dàng. Trước hết, không có công cụ thích hợp để làm như vậy, nhưng bạn có thể xây dựng đài quan sát sóng hấp dẫn trong khu vườn của mình. Đọc bài viết của tôi tại http://foobar.brigther-than-einstein.org/ Đầu tiên, tất nhiên, bạn phải tham gia một số khóa học vật lý lượng tử nâng cao, sau đó bạn cần hàng tấn nitơium cực kỳ mát mẻ, và dĩ nhiên, cuốn sách của tôi có sẵn tại Amazon "...

Nói cách khác...

Không, điều đầu tiên đầu tiên.

Mỗi và mọi phương pháp, có thể nó tin, nội bộ, bảo vệ, công chúng được kiểm chứng. Phải có một cách để thực hiện các thử nghiệm như vậy mà không cần phải quảng cáo như đã được trình bày ở đây.

Tại sao? Chính xác là do các đề cập kiến ​​trúc được thực hiện cho đến nay bởi một số người đóng góp. Có lẽ một sự lặp lại đơn giản của các nguyên tắc phần mềm có thể làm sáng tỏ một số sai lầm.

Trong trường hợp này, các nghi phạm thông thường là: OCP, SRP và, như mọi khi, KIS.

Nhưng đợi một chút. Ý tưởng làm cho mọi thứ có sẵn công khai là ít chính trị hơn và một loại thái độ. Nhưng. Khi nói đến mã, ngay cả trong Cộng đồng mã nguồn mở, đây không phải là giáo điều. Thay vào đó, "ẩn" một cái gì đó là cách thực hành tốt để làm quen dễ dàng hơn với một API nhất định. Ví dụ, bạn sẽ ẩn các tính toán cốt lõi của khối xây dựng nhiệt kế kỹ thuật số mới trên thị trường của mình - không che giấu toán học đằng sau đường cong đo thực cho các trình đọc mã tò mò, nhưng để ngăn mã của bạn bị phụ thuộc vào một số, có lẽ đột nhiên những người dùng quan trọng không thể cưỡng lại việc sử dụng mã riêng tư, nội bộ, được bảo vệ trước đây của bạn để thực hiện ý tưởng của riêng họ.

Tôi đang nói về cái gì vậy?

đôi riêng DịchMeas muaIntoLinear (gấp đôi thực tế mua sắm);

Thật dễ dàng để tuyên bố Thời đại Bảo Bình hoặc ngày nay được gọi là Thời đại, nhưng nếu phần cảm biến của tôi đạt từ 1.0 đến 2.0, việc triển khai Dịch ... có thể thay đổi từ một phương trình tuyến tính đơn giản dễ hiểu và "tái hiện" có thể sử dụng được "cho mọi người, với một phép tính khá phức tạp sử dụng phân tích hoặc bất cứ điều gì, và vì vậy tôi sẽ phá vỡ mã của người khác. Tại sao? Bởi vì họ không hiểu được những dự đoán về mã hóa phần mềm, thậm chí là KIS.

Để làm cho câu chuyện cổ tích này ngắn lại: Chúng ta cần một cách đơn giản để kiểm tra các phương pháp riêng tư - không cần quảng cáo.

Đầu tiên: Chúc mọi người năm mới vui vẻ!

Thứ hai: Xem lại bài học kiến ​​trúc sư của bạn.

Thứ ba: Công cụ sửa đổi "công khai" là tôn giáo, không phải là một giải pháp.


5

Một tùy chọn khác chưa được đề cập là chỉ tạo lớp kiểm tra đơn vị là con của đối tượng mà bạn đang kiểm tra. Ví dụ về NUnit:

[TestFixture]
public class UnitTests : ObjectWithPrivateMethods
{
    [Test]
    public void TestSomeProtectedMethod()
    {
        Assert.IsTrue(this.SomeProtectedMethod() == true, "Failed test, result false");
    }
}

Điều này sẽ cho phép thử nghiệm dễ dàng các phương thức riêng tư và được bảo vệ (nhưng không được kế thừa riêng tư) và nó sẽ cho phép bạn tách tất cả các thử nghiệm của mình khỏi mã thực để bạn không triển khai các cụm thử nghiệm vào sản xuất. Chuyển đổi các phương thức riêng tư của bạn sang các phương thức được bảo vệ sẽ được chấp nhận trong rất nhiều đối tượng được kế thừa và đó là một thay đổi khá đơn giản để thực hiện.

TUY NHIÊN...

Mặc dù đây là một cách tiếp cận thú vị để giải quyết vấn đề về cách kiểm tra các phương thức ẩn, tôi không chắc chắn rằng tôi sẽ ủng hộ rằng đây là giải pháp chính xác cho vấn đề trong mọi trường hợp. Có vẻ hơi kỳ quặc khi thử nghiệm nội bộ một đối tượng và tôi nghi ngờ có thể có một số tình huống mà phương pháp này sẽ thổi vào bạn. (Ví dụ, các đối tượng không thay đổi, có thể thực hiện một số bài kiểm tra thực sự khó).

Trong khi tôi đề cập đến phương pháp này, tôi sẽ đề nghị rằng đây là một gợi ý động não hơn là một giải pháp hợp pháp. Mang nó theo một hạt muối.

EDIT: Tôi thấy thật vui khi mọi người bỏ phiếu cho câu trả lời này, vì tôi mô tả rõ ràng đây là một ý tưởng tồi. Điều đó có nghĩa là mọi người đồng ý với tôi? Tôi bối rối quá .....


Đó là một giải pháp sáng tạo nhưng hơi khó tin.
shinzou

3

Từ cuốn sách Làm việc hiệu quả với Mã kế thừa :

"Nếu chúng ta cần thử nghiệm một phương thức riêng tư, chúng ta nên công khai nó. Nếu làm cho nó công khai làm phiền chúng ta, trong hầu hết các trường hợp, điều đó có nghĩa là lớp chúng ta đang làm quá nhiều và chúng ta nên sửa nó."

Cách để sửa nó, theo tác giả, là bằng cách tạo một lớp mới và thêm phương thức như public.

Tác giả giải thích thêm:

"Thiết kế tốt là có thể kiểm tra được, và thiết kế không thể kiểm tra là xấu."

Vì vậy, trong các giới hạn này, tùy chọn thực sự duy nhất của bạn là tạo phương thức public, trong lớp hiện tại hoặc lớp mới.


2

TL; DR: Trích xuất phương thức riêng sang lớp khác, kiểm tra trên lớp đó; đọc thêm về nguyên tắc SRP (Nguyên tắc trách nhiệm duy nhất)

Có vẻ như bạn cần trích xuất privatephương thức sang một lớp khác; trong này nên public. Thay vì thử kiểm tra privatephương thức, bạn nên kiểm tra publicphương thức của lớp khác này.

Chúng tôi có kịch bản sau đây:

Class A
+ outputFile: Stream
- _someLogic(arg1, arg2) 

Chúng ta cần kiểm tra logic của _someLogic; nhưng dường như Class Acó nhiều vai trò hơn mức cần thiết (vi phạm nguyên tắc SRP); chỉ tái cấu trúc thành hai lớp

Class A1
    + A1(logicHandler: A2) # take A2 for handle logic
    + outputFile: Stream
Class A2
    + someLogic(arg1, arg2) 

Theo cách này someLogiccó thể được thử nghiệm trên A2; trong A1 chỉ cần tạo một số A2 giả sau đó tiêm vào hàm tạo để kiểm tra rằng A2 được gọi đến hàm có tên someLogic.


0

Trong VS 2005/2008, bạn có thể sử dụng trình truy cập riêng để kiểm tra thành viên riêng, nhưng cách này đã biến mất trong phiên bản sau của VS


1
Câu trả lời tốt trong năm 2008 đến có lẽ đầu năm 2010. Bây giờ, vui lòng tham khảo các lựa chọn thay thế PrivateObject và Reflection (xem một số câu trả lời ở trên). VS2010 có lỗi truy cập, MS không dùng nó trong VS2012. Trừ khi bạn bị buộc phải ở lại VS2010 trở lên (công cụ xây dựng> 18 tuổi), hãy tiết kiệm thời gian của bạn bằng cách tránh những người truy cập riêng. :-).
Zephan Schroeder
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.