2
\$\begingroup\$

I want to properly test the strong exception guarantee of a method.

The method in question is SetFiles. It does not have side-effects (it only modifies the object it is called upon). It is expected to change some object fields and to modify the elements of FileList field - a collection. In case the argument is invalid then it throws ArgumentException and it is expected to not modify the object.

This is what I have:

[TestClass]
public class UnitTest_Files
{
    [TestMethod]
    public void TestFiles1()
    {
        var fs = new Files();

        // 1st call to SetFiles. valid

        fs.SetFiles(new string[]
            {
                @"C:\work\file1.txt",
                @"C:\work\file2.avi",
                @"C:\work\file3",
            });
        Assert.AreEqual(@"C:\work", fs.ParentFolder);
        Assert.AreEqual(3, fs.FileList.Count);

        Assert.AreEqual(@"C:\work\file1.txt", fs.FileList[0].Path);
        Assert.AreEqual(@"C:\work\file2.avi", fs.FileList[1].Path);
        Assert.AreEqual(@"C:\work\file3", fs.FileList[2].Path);

        // 2nd call to SetFiles. valid

        fs.SetFiles(new string[]
            {
                @"C:\muzică\Diabulus in Musica\poze\membri\Zuberoa Aznárez.jpg",
                @"C:\muzică\Diabulus in Musica\poze\membri\Gorka Elso.jpg",
            });

        Assert.AreEqual(@"C:\muzică\Diabulus in Musica\poze\membri", fs.ParentFolder);
        Assert.AreEqual(2, fs.FileList.Count);

        Assert.AreEqual(@"C:\muzică\Diabulus in Musica\poze\membri\Zuberoa Aznárez.jpg", fs.FileList[0].Path);
        Assert.AreEqual(@"C:\muzică\Diabulus in Musica\poze\membri\Gorka Elso.jpg", fs.FileList[1].Path);

        // 3rd call to SetFiles. Invalid

        // set to invalid structure (files not in same folder)
        // and expect throw
        // and no change
        try
        {
            fs.SetFiles(new string[]
                {
                    @"C:\muzică\folder1\file2.txt",
                    @"C:\muzică\folder2\file2.txt"
                });

            Assert.Fail("expected exception");
        }
        catch
        {
        }

        // test that fs was not modified

        Assert.AreEqual(@"C:\muzică\Diabulus in Musica\poze\membri", fs.ParentFolder);
        Assert.AreEqual(2, fs.FileList.Count);

        Assert.AreEqual(@"C:\muzică\Diabulus in Musica\poze\membri\Zuberoa Aznárez.jpg", fs.FileList[0].Path);
        Assert.AreEqual(@"C:\muzică\Diabulus in Musica\poze\membri\Gorka Elso.jpg", fs.FileList[1].Path);
    }
}

This is my first dive into C# unit testing and the code I wrote seems very cumbersome. I particularly don't like the copy-pasted last tests. The object is not clonable/copyable and I don't have any other reason to implement that. So I don't know how other way to test that object after is the same as before. Also the entire test doesn't seem very scalable.

\$\endgroup\$
-1
\$\begingroup\$

Do not attempt to put all the test criteria in one test. Make seperate (small) tests, which each cover one specific scenario.

If all tests pass, you know it is ok. But make sure the tests do not depend on each other: that they can run in any order.

For the exception assert, it is better to use an Attribute on the test method:

Update:

As Roland Illig mentioned, it is good to check that nothing was modified.

Updated code fragment:

[TestMethod]
[ExpectedException(typeof(ArgumentException))]
public void SetFiles_FilesNotInTheSameFolder_ExpectedException()
{
    // 3rd call to SetFiles. Invalid

    // set to invalid structure (files not in same folder)
    // and expect throw
    // and no change

    var validParentFolder = @"c:\C:\muzică\folder1";
    var validFile1 = @"c:\C:\muzică\folder1\file1.txt";

    var invalidFile1 = @"C:\muzică\folder1\file2.txt";
    var invalidFile2 = @"C:\muzică\folder2\file2.txt";

    var fs = new Files();
    fs.SetFiles(new string[]
    {
        validFile1
    });


    fs.SetFiles(new string[]
        {
            invalidFile1,
            invalidFile2
        });

    // test that fs was not modified

    Assert.AreEqual(1, fs.FileList.Count);
    Assert.AreEqual(validParentFolder, fs.ParentFolder);

    Assert.AreEqual(validFile1, fs.FileList[0].Path);
}

Last remark, if this is working with real files, try to use stubs or mocks.

Update:

Below is compete test file, with the separated tests

[TestClass]
public class UnitTest_Files
{
    private string expectedFile1;

    [TestMethod]
    public void SetFiles_Valid()
    {
        var fs = new Files();

        // 1st call to SetFiles. valid

        var expectedParentFolder = @"C:\work";
        var expectedFile1 = $@"{expectedParentFolder}\file1.txt";
        var expectedFile2 = $@"{expectedParentFolder}\file2.avi";
        var expectedFile3 = $@"{expectedParentFolder}\file3";

        fs.SetFiles(new string[]
            {
                expectedFile1,
                expectedFile2,
                expectedFile3
            });

        Assert.AreEqual(3, fs.FileList.Count);
        Assert.AreEqual(@"C:\work", fs.ParentFolder);
        Assert.AreEqual(expectedFile1, fs.FileList[0].Path);
        Assert.AreEqual(expectedFile2, fs.FileList[1].Path);
        Assert.AreEqual(expectedFile3, fs.FileList[2].Path);
    }    

    [TestMethod]
    public void SetFiles_Specialchars_Valid()
    {
        // 2nd call to SetFiles. valid
        var expectedParentFolder = @"C:\muzică\Diabulus in Musica\poze\membri";
        var expectedfile1 = $@"{expectedParentFolder}\Zuberoa Aznárez.jpg";
        var expectedfile2 = $@"{expectedParentFolder}\Gorka Elso.jpg";

        var fs = new Files();
        fs.SetFiles(new string[]
            {
                expectedfile1,
                expectedfile2
            });

        Assert.AreEqual(expectedParentFolder, fs.ParentFolder);
        Assert.AreEqual(2, fs.FileList.Count);

        Assert.AreEqual(expectedFile1, fs.FileList[0].Path);
        Assert.AreEqual(expectedFile2, fs.FileList[1].Path);
    }    

    [TestMethod]
    [ExpectedException(typeof(ArgumentException))]
    public void SetFiles_FilesNotInTheSameFolder_ExpectedException()
    {
        // 3rd call to SetFiles. Invalid

        // set to invalid structure (files not in same folder)
        // and expect throw
        // and no change

        var validParentFolder = @"c:\C:\muzică\folder1";
        var validFile1 = @"c:\C:\muzică\folder1\file1.txt";

        var invalidFile1 = @"C:\muzică\folder1\file2.txt";
        var invalidFile2 = @"C:\muzică\folder2\file2.txt";

        var fs = new Files();
        fs.SetFiles(new string[]
        {
            validFile1
        });


        fs.SetFiles(new string[]
            {
                invalidFile1,
                invalidFile2
            });

        // test that fs was not modified

        Assert.AreEqual(1, fs.FileList.Count);
        Assert.AreEqual(validParentFolder, fs.ParentFolder);

        Assert.AreEqual(validFile1, fs.FileList[0].Path);
    }
}
| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ The code from this answer does not explain how to check that in the exception case, the object state is not modified at all. That is an important part of the original question. \$\endgroup\$ – Roland Illig Apr 14 '18 at 18:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.