0

The Art of Unit Testingでは、保守可能で読みやすい単体テストを作成したいということが述べられています。204 ページあたりで、1 つのテストで複数のアサートを避けるようにし、オーバーライドされた Equals メソッドでオブジェクトを比較する必要があると述べています。これは、期待される結果と実際の結果を比較するオブジェクトが 1 つしかない場合にうまく機能します。しかし、上記のオブジェクトのリスト (またはコレクション) がある場合はどうでしょう。

以下のテストを検討してください。複数の主張があります。実際、アサートを呼び出す 2 つの別個のループがあります。この場合、アサーションは 5 つになります。1 つのリストの内容が別のリストに存在することを確認する 2 と、その逆の 2 です。リスト内の要素の数を比較する 5 番目。

このテストを改善するための提案があれば、私はすべて耳にします。現在は MSTest を使用していますが、MSTest の Assert を流暢な API (Assert.That) 用の NUnits に置き換えました。

現在のリファクタリングされたコード:

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

2 に答える 2

1

私はしばしば複数の主張を持っています。すべてが 1 つの論理的な作業単位のテストの一部である場合、問題はないと思います。

さて、もしあなたが をオーバーライドする型を持っているなら、それはあなたの 2 番目の形式よりもテストをはるかに単純にすることに同意します。Equalsしかし、最初のテストでは、結果のコレクションが期待されるものと等しいことを主張したいだけのように見えます。これは論理的には 1 つのアサーションだと思います。現在、テストのために複数のミニ アサーションを実行しているだけです。

一部の単体テスト フレームワークには、2 つのコレクションが等しいかどうかをテストするメソッドがあり、使用しているコレクションが等しくない場合は、簡単に記述できます。私は最近、「LINQ to Objects の再実装」ブログ シリーズでまさにこれを行いました。なぜなら、NUnit はヘルパー メソッドを提供しますが、その診断はあまり役に立たないからです。基本的に、 MoreLINQのコードをごくわずかにリファクタリングしました。

于 2010-09-26T07:00:32.303 に答える
0

これは私が現在使用しているリファクタリングです。CSVMaterialのToString()メソッドを上書きし、より便利な assert メッセージを追加しました。したがって、これはコードの可読性と保守性に役立つと思います。また、単体テストが信頼できるものになります (診断メッセージが役立つため)。

Jon さん、作業の論理単位について考えてくれてありがとう。私のリファクタリングされたコードは、前の反復とほぼ同じことを行います。どちらも、1 つの論理的なことをテストします。また、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());
}
于 2010-09-27T02:25:53.843 に答える