Чому це твердження видає виняток формату при порівнянні структур?


94

Я намагаюся стверджувати рівність двох System.Drawing.Sizeструктур, і я отримую виняток формату замість очікуваної помилки затвердження.

[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); 
}

Це передбачувана поведінка? Я тут роблю щось не так?


ви пробували мати Assert.AreEqual(struct1, struct2, string.Format("Failed expected {0} actually is {1}, struct1.ToString (), struct2.ToString ())) `?
DiskJunky

Це чудово працює; однак мені цікаво, чому Assert.AreEqual () не може форматувати рядок із типами структур.
Kyle

@Kyle З цікавості це не стосується сумісної з Silverlight версії модульного тестування, правда? Я можу відтворити його за допомогою цих бібліотек DLL (ще не пробував повну версію фреймворка .NET) EDIT: nevermind, протестовано також із повними і все ще не вдалося. :)
Кріс Сінклер

@ChrisSinclair ні, ми використовуємо будь-яку версію mstest, яка поставляється з Visual Studio 2010 ultimate. Сам тестовий проект націлений на .NET Framework 4
Кайл,

4
Не впевнений, що вас болить, але це чудово працює в NUnit. Я бачив більше подібних "проблем" у MStest. NUnit здається трохи зрілішим (принаймні для мене). +1 за пост
bas

Відповіді:


100

Я отримав його. І так, це помилка.

Проблема в тому, що тут відбувається два рівні string.Format.

Перший рівень форматування що - щось на кшталт:

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

Потім ми використовуємо string.Formatз параметрами, які ви вказали:

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

(Очевидно, що там є культури, і якась санітарія ... але недостатньо.)

Це виглядає нормально - якщо очікувані та фактичні значення самі не закінчаться фігурними дужками після перетворення в рядок - що вони і роблять Size. Наприклад, ваш перший розмір перетворюється на:

{Width=0, Height=0}

Отже, другий рівень форматування приблизно такий:

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

... і ось що не вдається. Ой.

Дійсно, ми можемо це довести дуже легко, обдуривши форматування, використовуючи наші параметри для очікуваної та фактичної частин:

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

Результат:

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

Явно зламаний, оскільки ми не очікували, як fooі не було фактичної вартості bar!

В основному це схоже на атаку ін'єкції SQL, але в досить менш страшному контексті string.Format.

Як обхідний шлях ви можете використовувати те, string.Formatяк пропонує StriplingWarrior. Це дозволяє уникнути другого рівня форматування, що виконується в результаті форматування з фактичними / очікуваними значеннями.


Дякую за детальну відповідь Джон! Я в кінцевому підсумку використовував StriplingWarriors навколо.
Kyle

1
Немає %*nеквівалента? :(
Tom Hawtin - tackline

Хто-небудь подавав звіт про помилку щодо цього?
Кевін

@Kevin: Так - хоча всередині, тому я не впевнений, чи буде прогрес загальнодоступним, доки його не буде виправлено.
Джон Скіт,

1
@Kevin Я також додав його до MS, як тільки було підтверджено помилку. connect.microsoft.com/VisualStudio/feedback/details/779528/…, якщо ви хочете відстежувати його загальнодоступно.
Кайл

43

Я думаю, ви знайшли помилку.

Це працює (видає виняток із твердження):

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

І це працює (виводить повідомлення):

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

Але це не працює (кидає a FormatException):

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

Я не можу придумати жодної причини, що це може бути очікуваною поведінкою. Я б подав звіт про помилку. Тим часом, ось обхідний шлях:

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

5

Я погоджуюсь з @StriplingWarrior, що це справді видається помилкою методу Assert.AreEqual () принаймні 2 перевантажень. Як уже зазначав StiplingWarrior, наступне не вдається;

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

Я трохи експериментував над цим, щоб трохи чіткіше використовувати код. Наведене також не працює;

// 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);

Це змусило мене задуматися. System.Drawing.Size - це структура. А як щодо об’єктів? У списку парів чи вказати , що список після stringповідомлення є params object[]. Технічно так конструкції - це об’єкти ... але особливі види об’єктів, тобто типи цінностей. Я думаю, що тут криється помилка. Якщо ми використовуємо наш власний об'єкт з подібним використанням та структурами на Sizeнаступному фактично робить роботу;

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
Проблема полягає в тому, чи не є він classчи struct, але то чи ToStringмістить значення фігурні дужки , які виглядають як String.Format.
Jean Hominal

3

Я думаю, що перше твердження є неправильним.

Використовуйте замість цього:

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

Згідно з документацією, я повинен мати можливість викликати AreEqual із відформатованим рядком. msdn.microsoft.com/en-us/library/ms243436%28v=vs.100%29.aspx , зокрема параметри Тип: System.Object [] Масив параметрів, які слід використовувати під час форматування повідомлення.
Kyle
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.