Tại sao khẳng định này lại ném ra một ngoại lệ định dạng khi so sánh các cấu trúc?


94

Tôi đang cố gắng khẳng định sự bình đẳng của hai System.Drawing.Sizecấu trúc và tôi nhận được một ngoại lệ về định dạng thay vì thất bại khẳng định như mong đợi.

[TestMethod]
public void AssertStructs()
{
    var struct1 = new Size(0, 0);
    var struct2 = new Size(1, 1);

    //This throws a format exception, "System.FormatException: Input string was not in a correct format."
    Assert.AreEqual(struct1, struct2, "Failed. Expected {0}, actually it is {1}", struct1, struct2); 

    //This assert fails properly, "Failed. Expected {Width=0, Height=0}, actually it is {Width=1, Height=1}".
    Assert.AreEqual(struct1, struct2, "Failed. Expected " + struct1 + ", actually it is " + struct2); 
}

Đây có phải là hành vi dự định? Tôi đang làm gì đó sai ở đây?


Bạn đã thử có Assert.AreEqual(struct1, struct2, string.Format("Failed expected {0} actually is {1}, struct1.ToString (), struct2.ToString ()))) chưa?
DiskJunky

Điều đó hoạt động tốt; tuy nhiên tôi tò mò là tại sao Assert.AreEqual () không thể định dạng một chuỗi với các kiểu cấu trúc.
Kyle

@Kyle Thật tò mò, đây không phải là phiên bản tương thích Silverlight của khung Unit Testing, phải không? Tôi có thể tái tạo nó với các tệp DLL đó (chưa thử phiên bản .NET framework đầy đủ) CHỈNH SỬA: nevermind, đã thử nghiệm với các tệp đầy đủ và vẫn không thành công. :)
Chris Sinclair

@ChrisSinclair nope, đây là sử dụng bất kỳ phiên bản mstest nào đi kèm với Visual Studio 2010 cuối cùng. Bản thân dự án thử nghiệm đang nhắm mục tiêu .NET Framework 4
Kyle

4
Không chắc bạn có quan tâm không, nhưng điều này hoạt động tốt trong NUnit. Tôi đã thấy nhiều "vấn đề" như thế này trong MStest. NUnit có vẻ trưởng thành hơn một chút (ít nhất là với tôi). +1 cho bài đăng
bas

Câu trả lời:


100

Tôi đã hiểu. Và vâng, đó là một lỗi.

Vấn đề là có hai cấp độ string.Formatđang diễn ra ở đây.

Các đầu tiên mức độ định dạng là một cái gì đó như:

string template  = string.Format("Expected: {0}; Actual: {1}; Message: {2}",
                                 expected, actual, message);

Sau đó, chúng tôi sử dụng string.Formatvới các thông số bạn đã cung cấp:

string finalMessage = string.Format(template, parameters);

(Rõ ràng là có các nền văn hóa đang được cung cấp, và một số loại vệ sinh ... nhưng không đủ.)

Điều đó có vẻ ổn - trừ khi bản thân các giá trị kỳ vọng và thực tế kết thúc bằng dấu ngoặc nhọn, sau khi được chuyển đổi thành một chuỗi - mà chúng thực hiện Size. Ví dụ: kích thước đầu tiên của bạn sẽ được chuyển đổi thành:

{Width=0, Height=0}

Vì vậy, mức định dạng thứ hai là như sau:

string.Format("Expected: {Width=0, Height=0}; Actual: {Width=1, Height=1 }; " +
              "Message = Failed expected {0} actually is {1}", struct1, struct2);

... và đó là những gì đang thất bại. Ôi chao.

Thật vậy, chúng tôi có thể chứng minh điều này thực sự dễ dàng bằng cách đánh lừa định dạng để sử dụng các tham số của chúng tôi cho các phần mong đợi và thực tế:

var x = "{0}";
var y = "{1}";
Assert.AreEqual<object>(x, y, "What a surprise!", "foo", "bar");

Kết quả là:

Assert.AreEqual failed. Expected:<foo>. Actual:<bar>. What a surprise!

Rõ ràng là bị hỏng, như chúng tôi không mong đợi foocũng như giá trị thực tế bar!

Về cơ bản, điều này giống như một cuộc tấn công SQL injection, nhưng trong bối cảnh ít đáng sợ hơn string.Format.

Để giải quyết vấn đề, bạn có thể sử dụng string.Formatnhư StriplingWarrior đề xuất. Điều đó tránh được mức định dạng thứ hai được thực hiện dựa trên kết quả của việc định dạng với các giá trị thực tế / mong đợi.


Cảm ơn vì câu trả lời chi tiết Jon! Tôi đã sử dụng StriplingWarriors làm việc xung quanh.
Kyle

1
Không có %*ntương đương? :(
Tom Hawtin - tackline

Có ai đã gửi một báo cáo lỗi cho điều này?
Kevin

@Kevin: Đúng vậy - mặc dù là nội bộ, vì vậy tôi không chắc liệu tiến trình có được hiển thị công khai cho đến khi nó được sửa hay không.
Jon Skeet

1
@Kevin Tôi cũng đã đưa một cái vào MS khi nó được xác nhận là có lỗi. connect.microsoft.com/VisualStudio/feedback/details/779528/… nếu bạn muốn theo dõi công khai.
Kyle

43

Tôi nghĩ rằng bạn đã tìm thấy một lỗi.

Điều này hoạt động (ném một ngoại lệ khẳng định):

var a = 1;
var b = 2;
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

Và điều này hoạt động (xuất ra thông báo):

var a = new{c=1};
var b = new{c=2};
Console.WriteLine(string.Format("Not equal {0} {1}", a, b));

Nhưng điều này không hoạt động (ném a FormatException):

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

Tôi không thể nghĩ ra bất kỳ lý do gì mà đây lại là hành vi được mong đợi. Tôi sẽ gửi một báo cáo lỗi. Trong thời gian chờ đợi, đây là một giải pháp:

var a = new{c=1};
var b = new{c=2};
Assert.AreEqual(a, b, string.Format("Not equal {0} {1}", a, b));

5

Tôi đồng ý với @StriplingWarrior rằng điều này thực sự có vẻ là một lỗi với phương thức Assert.AreEqual () trên ít nhất 2 lần quá tải. Như StiplingWarrior đã chỉ ra, phần sau không thành công;

var a = new { c = 1 };
var b = new { c = 2 };
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

Tôi đã thực hiện một thử nghiệm nhỏ về điều này hơn nữa về cách sử dụng mã rõ ràng hơn một chút. Sau đây cũng không hoạt động;

// specify variable data type rather than "var"...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual(a, b, "Not equal {0} {1}", a, b);

// specify variable data type and name the type on the generic overload of AreEqual()...no effect, still fails
Size a = new Size(0, 0);
Size b = new Size(1, 1);
Assert.AreEqual<Size>(a, b, "Not equal {0} {1}", a, b);

Điều này khiến tôi phải suy nghĩ. System.Drawing.Size là một cấu trúc. Còn đối tượng thì sao? Danh sách tham số không xác định rằng danh sách sau stringthông báo là params object[]. Về mặt kỹ thuật, có cấu trúc các đối tượng ... nhưng các loại đối tượng đặc biệt , tức là các loại giá trị. Tôi nghĩ rằng đây là nơi lỗi nằm. Nếu chúng ta sử dụng đối tượng riêng của chúng tôi với một cách sử dụng và cấu trúc tương tự Size, sau đây thực hiện công việc;

private class MyClass
{
    public MyClass(int width, int height)
        : base()
    { Width = width; Height = height; }

    public int Width { get; set; }
    public int Height { get; set; }
}

[TestMethod]
public void TestMethod1()
{
    var test1 = new MyClass(0, 0);
    var test2 = new MyClass(1, 1);
    Assert.AreEqual(test1, test2, "Show me A [{0}] and B [{1}]", test1, test2);
}

1
Vấn đề không phải là nó có classhay không struct, mà là liệu ToStringgiá trị có chứa dấu ngoặc nhọn giống như a hay không String.Format.
Jean Hominal

3

Tôi nghĩ khẳng định đầu tiên là không chính xác.

Sử dụng cái này thay thế:

Assert.AreEqual(struct1, 
                struct2, 
                string.Format("Failed expected {0} actually is {1}", struct1, struct2));

Theo tài liệu, tôi có thể gọi AreEqual bằng một chuỗi được định dạng. msdn.microsoft.com/en-us/library/ms243436%28v=vs.100%29.aspx , cụ thể là các tham số Loại: System.Object [] Một mảng các tham số để sử dụng khi định dạng thư.
Kyle
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.