Подтвердить что ты не робот

Несколько утверждений в Unit Test

Я только что закончил читать Роя Ошерове "Искусство модульного тестирования", и я стараюсь придерживаться лучших практик, которые он излагает в книге. Одна из этих лучших практик заключается в том, чтобы не использовать несколько утверждений в методе тестирования. Причина этого правила достаточно ясна для меня, но это заставляет меня задуматься...

Если у меня есть метод вроде:

public Foo MakeFoo(int x, int y, int z)
{
     Foo f = new Foo();
     f.X = x;
     f.Y = y;
     f.Z = z;

     return f;
}

Должен ли я действительно писать отдельные модульные тесты, чтобы утверждать, что каждое отдельное свойство Foo инициализируется с предоставленным значением? Неужели это редкость использовать несколько утверждений в методе тестирования?

FYI: Я использую MSTest.

EDIT: Спасибо за все ответы. Думаю, я закончу с несколькими утверждениями. В моей ситуации проверяемое поведение заключается в том, что MakeFoo делает правильный Foo. Поэтому утверждать, что каждое свойство получает ожидаемое значение, должно быть достаточно. Однако, если бы существовало условие для настройки одного из свойств, я бы тестировал каждый отдельный результат отдельно.

Мне все равно это не нравится.... Причина, по которой мне нравится идея утверждать, что вы проверяете, что вы знаете точную причину неудачи теста. Если вы исправите проблему, тест пройдет. С несколькими утверждениями у вас нет такой же гарантии. Если вы исправите проблему, на которую ссылается неудавшееся утверждение, нет ничего, чтобы остановить другое утверждение позже в тесте после сбоя. Если утверждения были разделены, то вы могли бы узнать об обоих неудачах с самого начала.

И, наконец, причина, по которой я не просто использую .Equals(), потому что в моей ситуации Foo - это объект LINQ-To-SQL, который вводит некоторые сложности, которые не стоит вдаваться сюда.

Еще раз спасибо.

4b9b3361

Ответ 1

Гораздо важнее проверить только одну концепцию на unit test.

Для проверки концепции может потребоваться более одного утверждения, поэтому не беспокойтесь о количестве утверждений. Конечно, если вы получите большое количество утверждений, вы можете сделать шаг назад и подумать о том, что вы действительно тестируете.

Ответ 2

В подобных ситуациях, если я пытаюсь быть строгим в отношении одного утверждения за тест, я буду утверждать равенство на Foo, а не его компоненты. Это заставляет меня писать Foo.equals(), и это обычно хорошо и само по себе. Но я не всегда строг в отношении одного утверждения за тест. Посмотрите, что вам нравится.

Ответ 3

AFAI, принцип, лежащий в основе практики, был

  • проверить одну связную/логическую вещь на тест; что приводит к кратким и легко читаемым тестам.
  • чтобы лучше тестировать изоляцию. Один тест не должен прерываться по одной из n причин. Если это так, то не сразу видно, как это исправить; без предварительного выяснения причин, по которым он потерпел неудачу. Единственное изменение не должно вызывать нагрузку на кажущиеся несвязанные тесты.
  • правило стартера из правила для начинающих TDD; так что они могут усвоить процесс.

точка не в том, что вам нужен один "NUNit" / "xUnit" для каждого теста; скорее у вас есть одно логическое утверждение за тест.

[Test]
public void MakeFoo_SeedsInstanceWithSpecifiedAttributes()
{
  Assert.AreEqual( new Foo(1,2,3), Foo.MakeFoo(1,2,3), "Objects should have been equal );
}

Здесь у меня есть одно утверждение NUnit (поддерживая уверенность полиции): Однако он тестирует три вещи за кулисами (в Foo.Equals я бы проверял, равны ли все переменные).
Руководством является предотвращение таких тестов, как (например, тест интеграции, маскирующий как unit test)

[Test]
public void ITestEverything()
{
  // humongous setup - 15 lines
  payroll.ReleaseSalaries();
  // assert that the finance department has received a notification
  // assert employee received an email
  // assert ledgers have been updated
  // statements have been queued to the printqueue
  // etc.. etc..
}

Мораль истории: Это милая цель, для которой нужно стремиться. Попробуйте составить все, что вы хотите протестировать, в один логический/сплоченный Assert с хорошим именем. например Assert.True(AreFooObjectsEqual(f1,f2). Если вы обнаружите, что у вас есть трудное время, называя сплоченную проверку/утверждение - возможно, вам нужно просмотреть свой тест и посмотреть, нужно ли его разделить.

Ответ 4

На самом деле я написал NUnit addin, чтобы помочь с этим. Попробуйте в http://www.rauchy.net/oapt

Ответ 5

Обычно для тестирования используется несколько утверждений, но я не чувствую, что это "лучшая практика".

Я бы сказал, что да, вы хотите использовать несколько тестов для вышеуказанной функции, чтобы вы могли видеть, где проблема. Недавно я работал с довольно большим набором тестов, в котором есть какие-то странные неудачи в нем, которые мне еще предстоит отслеживать, и проблема, с которой я столкнулся, заключается в том, что все тестовые примеры делают слишком много. Я разделил их, и теперь я могу отключить конкретные, которые не работают, чтобы я мог вернуться к ним, когда у меня есть такая возможность.

Тем не менее, вы все равно можете использовать прибор, чтобы разделить общность вашей установки и срыва. Я не могу говорить с MSTest, но в UnitTest ++ вы бы сделали что-то вроде:

class FooFixture
{
public:
   FooFixture() : f(MakeFoo(kX, kY, kZ)) { }

   static const int kX = 1;
   static const int kY = 2;
   static const int kZ = 3;

   Foo f;
};

TEST_FIXTURE(FooFixture, IsXInitializedCorrectly)
{
   CHECK_EQUAL(kX, f.X);
}

// repeat for Y and Z

Это не тонна больше набора текста, особенно учитывая вырезание и вставку. Черт, в vim это просто esc y ctrl-{ pp, а затем небольшие изменения. Тем не менее, при этой настройке вы можете увидеть, не выпало ли только одно поле, а затем вникнуть, чтобы понять, почему.

Ответ 6

Обычно я обнаруживаю, что имею несколько утверждений в методе, но обычно они связаны с тестом под рукой. Я иногда заставляю утверждать, чтобы проверить предварительное условие, которое, как я знаю, нарушит тест, потому что я не хочу, чтобы тест был успешным случайно.

[Test] // NUnit style test.
public void SearchTypeAndInventory() 
{
    var specification = new WidgetSearchSpecification 
                    {
                        WidgetType = Widget.Screw,
                        MinimumInventory = 10
                    }; 
var widgets = WidgetRepository.GetWidgets(specification);
if( !widgets.Any() ) 
    Assert.Inconclusive("No widgets were returned.");
Assert.IsTrue( 
    widgets.All( o => o.WidgetType == Widget.Screw), 
    "Not all returned widgets had correct type");
Assert.IsTrue( 
    widgets.All( o => o.InventoryCount >= 10), 
    "Not all returned widgets had correct inventory count.");

* Хотя я мог бы объединить Asserts, я считаю более полезным отделить то, что пошло не так.


Я не думаю, что придерживаться жесткого правила 1-assert-per-test очень полезно. Что еще более важно, так это то, что тест проверяет только одно. Я видел много uber-тестов, которые являются одним огромным методом, который проверяет все о классе. Эти uber-тесты хрупки и трудно поддерживать.

Вам следует подумать о том, что ваши тесты важны и хорошо написаны в качестве кода, который они тестируют. Например. применяйте те же методы рефакторинга, чтобы убедиться, что тест делает одну вещь и делает это хорошо, и не является дымоходной трубой, которая проверяет все в поле зрения.

Ответ 7

Как я читаю книги, вы должны делать "Красный, Зеленый, Рефактор". В разделе "Refactor" вы должны реорганизовать как тестируемый код, так и модульные тесты.

Я не вижу ничего плохого в следующем рефакторинге:

Оригинальные

[TestMethod]
public void TestOne()
{
    LetsDoSomeSetup();
    AssertSomething();
}

[TestMethod]
public void TestTwo()
{
    LetsDoSomeSetup(); // Same setup
    AssertSomethingElse();
}

рефакторинга

[TestMethod]
public void TestOneTwo()
{
    LetsDoSomeSetup();
    AssertSomething();
    AssertSomethingElse();
}

Конечно, это предполагает, что два утверждения связаны и, конечно, полагаются на один и тот же сценарий.