在单元测试中,您是否验证并断言?

时间:2013-12-31 21:06:51

标签: unit-testing moq

我在单元测试项目中使用Moq。我在网上看到的大多数单元测试示例以someMock.VerifyAll();结尾我想知道在VerifyAll()之后断言是否可行。例如,

//Arrange
var student = new Student{Id = 0, Name="John Doe", IsRegistered = false};
var studentRepository = new Mock<IStudentRepository>();
var studentService= new StudentService(studentRepository.Object);

//Act
studentService.Register(student);  //<-- student.IsRegistered = true now.

//Verify and assert
studentRepository.VerifyAll();
Assert.IsTrue(student.IsRegistered);

有什么想法?谢谢。

4 个答案:

答案 0 :(得分:27)

在大多数情况下,你不应该同时使用(总有例外)。原因是你应该只测试测试中的一个问题,即可维护性,可读性和其他一些原因。因此,在测试中应该是Verify(VerifyAll)或Assert,并相应地命名测试。

看看Roy Osherove关于它的文章:

http://osherove.com/blog/2005/4/3/a-unit-test-should-test-only-one-thing.html

VerifyAll用于确保调用某些方法以及调用多少次。您可以使用mocks

Assert用于验证从您正在测试的方法返回的结果。您可以使用Stubs

Martin fowler有一篇很棒的文章解释了模拟和存根之间的区别。如果你了解它,你会更好地了解它们。

http://martinfowler.com/articles/mocksArentStubs.html

更新:使用Moq模拟vs存根的示例,如下面的评论所示。我使用过验证,但您也可以使用VerifyAll。

using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
... 

[TestClass]
public class UnitTest1
{
    /// <summary>
    /// Test using Mock to Verify that GetNameWithPrefix method calls 
    /// Repository GetName method once when Id is greater than Zero
    /// </summary>
    [TestMethod]
    public void GetNameWithPrefix_IdIsTwelve_GetNameCalledOnce()
    {
        // Arrange 
        var mockEntityRepository = new Mock<IEntityRepository>();
        mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));

        var entity = new EntityClass(mockEntityRepository.Object);
        // Act 
        var name = entity.GetNameWithPrefix(12);
        // Assert
        mockEntityRepository.Verify(
            m => m.GetName(It.IsAny<int>()), Times.Once);
    }

    /// <summary>
    /// Test using Mock to Verify that GetNameWithPrefix method 
    /// doesn't calls Repository GetName method when Id is Zero
    /// </summary>
    [TestMethod]
    public void GetNameWithPrefix_IdIsZero_GetNameNeverCalled()
    {
        // Arrange 
        var mockEntityRepository = new Mock<IEntityRepository>();
        mockEntityRepository.Setup(m => m.GetName(It.IsAny<int>()));
        var entity = new EntityClass(mockEntityRepository.Object);
        // Act 
        var name = entity.GetNameWithPrefix(0);
        // Assert
        mockEntityRepository.Verify(
            m => m.GetName(It.IsAny<int>()), Times.Never);
    }

    /// <summary>
    /// Test using Stub to Verify that GetNameWithPrefix method
    /// returns Name with a Prefix
    /// </summary>
    [TestMethod]
    public void GetNameWithPrefix_IdIsTwelve_ReturnsNameWithPrefix()
    {
        // Arrange 
        var stubEntityRepository = new Mock<IEntityRepository>();
        stubEntityRepository.Setup(m => m.GetName(It.IsAny<int>()))
            .Returns("Stub");
        const string EXPECTED_NAME_WITH_PREFIX = "Mr. Stub";
        var entity = new EntityClass(stubEntityRepository.Object);
        // Act 
        var name = entity.GetNameWithPrefix(12);
        // Assert
        Assert.AreEqual(EXPECTED_NAME_WITH_PREFIX, name);
    }
}

public class EntityClass
{
    private IEntityRepository _entityRepository;
    public EntityClass(IEntityRepository entityRepository)
    {
        this._entityRepository = entityRepository;
    }
    public string Name { get; set; }
    public string GetNameWithPrefix(int id)
    {
        string name = string.Empty;
        if (id > 0)
        {
            name = this._entityRepository.GetName(id);
        }
        return "Mr. " + name;
    }
}

public interface IEntityRepository
{
    string GetName(int id);
}

public class EntityRepository:IEntityRepository
{
    public string GetName(int id)
    {
        // Code to connect to DB and get name based on Id
        return "NameFromDb";
    }
}

答案 1 :(得分:7)

是的,你应该调用断言。

VerifyAll()将声明所有SetUp()次调用都已实际调用。

VerifyAll()不会确认您的学生对象已注册。由于您的测试用例中没有SetUp()次调用,我认为VerifyAll()未验证任何内容。

答案 2 :(得分:5)

我绝对希望在单元测试中看到VerifyAssert并排使用。断言用于验证已正确设置被测系统的属性,而Verify用于确保已正确调用被测系统所接受的任何依赖项。使用Moq时,我倾向于明确地验证设置而不是使用VerifyAll catch-all。这样你就可以使测试的目的更加清晰。

我在上面的代码中假设您对学生资料库的调用返回一个布尔值来表明该学生已注册?然后你在student对象上设置该值?在这种情况下,需要添加一个有价值的设置,您有效地说,当调用学生存储库方法时,它将返回true。然后Assertstudent.IsRegistered,以确保您已从存储库返回值正确设置属性,并Verify使用您期望的输入调用存储库方法。

答案 3 :(得分:1)

在模拟测试中断言和验证都没有任何本质上的错误,尽管依赖于被调用的实际方法的断言可能会失败,因为mock方法与真实方法没有相同的效果。

在您的示例中,它可能很好,因为只有存储库被模拟,并且学生状态的更改可能是在服务中完成的。

验证和断言是否应该在同一测试中完成在某种程度上是品味问题。实际上,验证是检查是否正确调用了存储库,并且断言正在检查是否对该实体进行了正确的更改。由于这些是不同的问题,我会把它们放在单独的测试中,但那可能只是我。