【问题标题】:Challenges of refactoring unit-tests to be maintainable and readable when dealing with List<T> objects在处理 List<T> 对象时,将单元测试重构为可维护和可读的挑战
【发布时间】:2010-09-26 06:22:38
【问题描述】:

The Art of Unit Testing 一书中,它谈到想要创建可维护和可读的单元测试。在第 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);
        }

【问题讨论】:

    标签: unit-testing list refactoring code-readability code-maintainability


    【解决方案1】:

    我经常有不止一个断言。如果这都是测试一个逻辑工作单元的一部分,我认为这没有任何问题。

    现在,我确实同意,如果您有一个覆盖 Equals 的类型,那么这会使测试比您的第二种形式简单得多。但是在您的第一个测试中,看起来您真的只想断言生成的集合等于预期的集合。我认为这在逻辑上是一个断言 - 只是目前您正在执行多个迷你断言来测试它。

    一些单元测试框架具有测试两个集合是否相等的方法 - 如果您使用的集合不相等,您可以轻松编写一个。我最近在我的"reimplementing LINQ to Objects" blog series 中做到了这一点,因为尽管 NUnit 提供了一个辅助方法,但它的诊断并不是很有帮助。基本上,我对MoreLINQ 的代码进行了非常轻微的重构。

    【讨论】:

    • 说“NUnit 提供了一个辅助方法”,你指的是 Is.EquivlentTo 吗?但是,是的,我正在尝试查看这两个列表是否相同(即所有元素都存在于两者中,并且这些元素在相互比较时是相等的)。我唯一不关心的是订购。
    • @Matt:我指的是 CollectionAssert.AreEqual。我没有意识到您对订购不感兴趣 - 您可能希望在问题中说明这一点。
    【解决方案2】:

    这是我目前正在使用的重构。我覆盖了 CSVMaterial 的 ToString() 方法并添加了更有用的断言消息。所以我认为这有助于代码的可读性和可维护性。它还使单元测试值得信赖(由于有用的诊断消息)。

    还有 Jon,感谢您对逻辑工作单元的思考。我重构的代码与之前的迭代做的事情大致相同。两者仍然测试一件合乎逻辑的事情。此外,我将不得不研究 MoreLINQ 的东西。如果它在您的 C# InDepth 第 2 版书中,我会在从 Manning 购买 MEAP 版本时遇到它。感谢您的帮助。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-21
      相关资源
      最近更新 更多