Сложности рефакторинга юнит-тестов, которые можно обслуживать и читать при работе с объектами List <T> - PullRequest
0 голосов
/ 26 сентября 2010

В книге Искусство модульного тестирования говорится о желании создать поддерживаемые и читаемые модульные тесты. На странице 204 упоминается, что в одном тесте следует избегать нескольких утверждений и, возможно, сравнивать объекты с помощью переопределенного метода Equals. Это прекрасно работает, когда у нас есть только один объект для сравнения ожидаемых и фактических результатов. Однако что, если у нас есть список (или коллекция) указанных объектов.

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

Если у кого-то есть предложения по улучшению этого теста, я весь в ушах. В настоящее время я использую MSTest, хотя я заменил MSTest Assert на NUnits для свободного API (Assert.That).

Текущий код рефакторинга:

        [TestMethod]
#if !NUNIT 
        [HostType("Moles")]
#else
        [Moled]
#endif
        public void LoadCSVBillOfMaterials_WithCorrectCSVFile_ReturnsListOfCSVBillOfMaterialsThatMatchesInput()
        {
            //arrange object(s)            
            var filePath = "Path Does Not Matter Because of Mole in File object";
            string[] csvDataCorrectlyFormatted = { "1000, 1, Alt 1, , TBD, 1, 10.0, Notes, , Description, 2.50, ,A",
                                                   "1001, 1, Alt 2, , TBD, 1, 10.0, Notes, , Description, 2.50, ,A" };

            var materialsExpected = new List<CSVMaterial>();
            materialsExpected.Add(new CSVMaterial("1000", 1, "Alt 1", "TBD", 1m, 10.0m, "Notes", "Description", 2.50m,"A"));
            materialsExpected.Add(new CSVMaterial("1001", 1, "Alt 2", "TBD", 1m, 10.0m, "Notes", "Description", 2.50m,"A"));

            //by-pass actually hitting the file system and use in-memory representation of CSV file
            MFile.ReadAllLinesString = s => csvDataCorrectlyFormatted;

            //act on object(s)                        
            var materialsActual = modCSVImport.LoadCSVBillOfMaterials(filePath);

            //assert something happended            
            Assert.That(materialsActual.Count,Is.EqualTo(materialsExpected.Count));
            materialsExpected.ForEach((anExpectedMaterial) => Assert.That(materialsActual.Contains(anExpectedMaterial)));
            materialsActual.ForEach((anActualMaterial) => Assert.That(materialsExpected.Contains(anActualMaterial)));
        }

Оригинальный модульный тест с несколькими активами:

 ...
            //1st element
            Assert.AreEqual("1000", materials[0].PartNumber);
            Assert.AreEqual(1, materials[0].SequentialItemNumber);
            Assert.AreEqual("Alt 1", materials[0].AltPartNumber);
            Assert.AreEqual("TBD", materials[0].VendorCode);
            Assert.AreEqual(1m, materials[0].Quantity);
            Assert.AreEqual(10.0m, materials[0].PartWeight);
            Assert.AreEqual("Notes", materials[0].PartNotes);
            Assert.AreEqual("Description", materials[0].PartDescription);
            Assert.AreEqual(2.50m, materials[0].UnitCost);
            Assert.AreEqual("A", materials[1].Revision);
            //2nd element
            Assert.AreEqual("1001", materials[1].PartNumber);
            Assert.AreEqual(1, materials[1].SequentialItemNumber);
            Assert.AreEqual("Alt 2", materials[1].AltPartNumber);
            Assert.AreEqual("TBD", materials[1].VendorCode);
            Assert.AreEqual(1m, materials[1].Quantity);
            Assert.AreEqual(10.0m, materials[1].PartWeight);
            Assert.AreEqual("Notes", materials[1].PartNotes);
            Assert.AreEqual("Description", materials[1].PartDescription);
            Assert.AreEqual(2.50m, materials[1].UnitCost);
            Assert.AreEqual("A", materials[1].Revision);
        }

Ответы [ 2 ]

1 голос
/ 26 сентября 2010

У меня часто есть более одного утверждения.Если это все часть тестирования одной логической единицы работы, я не вижу никаких проблем с этим.

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

В некоторых инфраструктурах модульных тестов есть методы для проверки, равны ли две коллекции - и еслииспользуя не, вы можете легко написать один.Недавно я сделал именно это в своей серии «переопределение LINQ to Objects» , потому что, хотя NUnit предоставляет вспомогательный метод, его диагностика не очень полезна.Я немного переработал код из MoreLINQ , в основном.

0 голосов
/ 27 сентября 2010

Это рефакторинг, который я сейчас использую. Я переопределил метод ToString() в CSVMaterial и добавил более полезное сообщение об утверждении. Поэтому я думаю, что это помогает с читаемостью кода и ремонтопригодностью. Это также повышает надежность модульного теста (благодаря полезному диагностическому сообщению).

И Джон, спасибо за мысль о логической единице работы. Мой рефакторинговый код делает примерно то же самое, что и предыдущая итерация. Оба все еще проверяют одну логическую вещь. Кроме того, мне придется изучить материал MoreLINQ. Если он есть в вашей книге C # InDepth 2nd edition, я наткнусь на нее, когда купил версию MEAP у Manning. Спасибо за вашу помощь.

public void LoadCSVBillOfMaterials_WithCorrectCSVFile_ReturnsListOfCSVBillOfMaterialsThatMatchesInput()
{
    //arrange object(s)            
    var filePath = "Path Does Not Matter Because of Mole in File object";
    string[] csvDataCorrectlyFormatted = { "1000, 1, Alt 1, , TBD, 1, 10.0, Notes, , Description, 2.50, ,A",
                                           "1001, 1, Alt 2, , TBD, 1, 10.0, Notes, , Description, 2.50, ,A" };            

    var materialsExpected = new List<CSVMaterial>();
    materialsExpected.Add(new CSVMaterial("1001", 1, "Alt 1", "TBD", 1m, 10.0m, "Notes", "Description", 2.50m,"A"));
    materialsExpected.Add(new CSVMaterial("1001", 1, "Alt 2", "TBD", 1m, 10.0m, "Notes", "Description", 2.50m,"A"));           

    //by-pass actually hitting the file system and use in-memory representation of CSV file
    MFile.ReadAllLinesString = s => csvDataCorrectlyFormatted;

    //act on object(s)                        
    var materialsActual = modCSVImport.LoadCSVBillOfMaterials(filePath);

    //assert something happended            

    //Setup message for failed asserts
    var assertMessage = new StringBuilder();
    assertMessage.AppendLine("Actual Materials:");
    materialsActual.ForEach((m) => assertMessage.AppendLine(m.ToString()));
    assertMessage.AppendLine("Expected Materials:");
    materialsExpected.ForEach((m) => assertMessage.AppendLine(m.ToString()));

    Assert.That(materialsActual, Is.EquivalentTo(materialsExpected),assertMessage.ToString());
}
...