Почему это утверждение вызывает исключение формата при сравнении структур?

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 Из любопытства, это не в совместимой с Silverlight версии фреймворка модульного тестирования, не так ли? Я могу воспроизвести его с помощью этих DLL (еще не пробовал полную версию .NET framework) 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.
Кайл
1
Нет %*nэквивалента? :(
Том Хотин - tackline
Кто-нибудь отправил отчет об ошибке для этого?
Кевин
@Kevin: Ага - хотя внутренне, поэтому я не уверен, будет ли прогресс виден публично, пока он не будет исправлен.
Джон Скит,
1
@Kevin Я тоже поставил его в MS, как только была подтверждена ошибка. connect.microsoft.com/VisualStudio/feedback/details/779528/…, если вы хотите отслеживать его публично.
Кайл
43

Я думаю, вы нашли ошибку.

Это работает (выдает исключение assert):

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

Но это не работает (бросает 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 () как минимум при двух перегрузках. Как уже указывал 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[]. Технически структуры yes - это объекты ... но особые виды объектов, то есть типы значений. Я думаю, в этом и заключается ошибка. Если мы используем наш собственный объект с подобным использованием и структурами на 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);
}
DiskJunky
источник
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 [] Массив параметров, используемых при форматировании сообщения.
Кайл