如何正确模拟和单元测试 [英] How to properly mock and unit test

查看:76
本文介绍了如何正确模拟和单元测试的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我基本上是想教自己如何编码,我想遵循良好的做法.单元测试有明显的好处.在单元测试方面也有很多狂热者,而我更喜欢在编程和生活上更加务实的方法.作为上下文,我目前正在编写我的第一个真实"应用程序,它是使用asp.net MVC的无处不在的博客引擎.我通过自己的调整来大致遵循MVC Storefront架构.因此,这是我对模拟对象的第一次真正尝试.我将代码示例放在问题的末尾.

I'm basically trying to teach myself how to code and I want to follow good practices. There are obvious benefits to unit testing. There is also much zealotry when it comes to unit-testing and I prefer a much more pragmatic approach to coding and life in general. As context, I'm currently writing my first "real" application which is the ubiquitous blog engine using asp.net MVC. I'm loosely following the MVC Storefront architecture with my own adjustments. As such, this is my first real foray into mocking objects. I'll put the code example at the end of the question.

我非常感谢我可以用来增进我对测试和模拟基础知识的理解或外部资源.我在网上找到的资源通常用于模拟的方式",我需要更多地了解模拟的地点,原因和时间.如果这不是问这个问题的最佳地点,请给我指出一个更好的地方.

I'd appreciate any insight or outside resources that I could use to increase my understanding of the fundamentals of testing and mocking. The resources I've found on the net are typically geared towards the "how" of mocking and I need more understanding of the where, why and when of mocking. If this isn't the best place to ask this question, please point me to a better place.

我正试图了解从以下测试中获得的价值. UserService依赖于IUserRepository.服务层的价值是将逻辑与数据存储区分开,但是在这种情况下,大多数UserService调用都直接传递给IUserRepository.我没有太多可以测试的实际逻辑这一事实也可能引起我的担忧.我有以下担忧.

I'm trying to understand the value that I'm getting from the following tests. The UserService is dependent upon the IUserRepository. The value of the service layer is to separate your logic from your data storage, but in this case most of the UserService calls are just passed straight to IUserRepository. The fact that there isn't much actual logic to test could be the source of my concerns as well. I have the following concerns.

  • 感觉代码只是在测试模拟框架是否正常工作.
  • 为了模拟依赖关系,这使我的测试对IUserRepository实现有了太多的了解.这是必要的邪恶吗?
  • 我从这些测试中实际上获得了什么价值?被测试服务的简单性是否使我怀疑这些测试的价值.

我正在使用NUnit和Rhino.Mocks,但是我要完成的工作应该很明显.

I'm using NUnit and Rhino.Mocks, but it should be fairly obvious what I'm trying to accomplish.

    [SetUp]
    public void Setup()
    {
        userRepo = MockRepository.GenerateMock<IUserRepository>();
        userSvc = new UserService(userRepo);
        theUser = new User
        {
            ID = null,
            UserName = "http://joe.myopenid.com",
            EmailAddress = "joe@joeblow.com",
            DisplayName = "Joe Blow",
            Website = "http://joeblow.com"
        };
    }

    [Test]
    public void UserService_can_create_a_new_user()
    {
        // Arrange
        userRepo.Expect(repo => repo.CreateUser(theUser)).Return(true);

        // Act
        bool result = userSvc.CreateUser(theUser);

        // Assert
        userRepo.VerifyAllExpectations();
        Assert.That(result, Is.True, 
          "UserService.CreateUser(user) failed when it should have succeeded");
    }

    [Test]
    public void UserService_can_not_create_an_existing_user()
    {
        // Arrange
        userRepo.Stub(repo => repo.IsExistingUser(theUser)).Return(true);
        userRepo.Expect(repo => repo.CreateUser(theUser)).Return(false);
        // Act
        bool result = userSvc.CreateUser(theUser);

        // Assert
        userRepo.VerifyAllExpectations();
        Assert.That(result, Is.False, 
            "UserService.CreateUser() allowed multiple copies of same user to be created");
    }

推荐答案

基本上,您在此处测试的是方法被调用,而不是方法是否实际起作用.模拟应该做什么.他们只是检查方法是否被调用,而不是调用方法,并返回Return()语句中的内容.因此,在您的断言中:

Essentially what you are testing here is that the methods are getting called, not whether or not they actually work. Which is what mocks are supposed to do. Instead of calling the method, they just check to see if the method got called, and return whatever is in the Return() statement. So in your assertion here:

Assert.That(result, Is.False, "error message here");

此断言将始终成功,因为由于Return语句,您的期望将始终返回false:

This assertion will ALWAYS succeed because your expectation will ALWAYS return false, because of the Return statement:

userRepo.Expect(repo => repo.CreateUser(theUser)).Return(false);

我想这在这种情况下没什么用.

I'm guessing this isn't that useful in this case.

例如,当您想在代码中的某个位置进行数据库调用,但又不想实际调用数据库时,可以使用模拟.您想假装数据库被调用了,但是您想设置一些假数据以使其返回,然后(这是重要的部分)测试对模拟返回的假数据执行某些操作的逻辑.在上述示例中,您省略了最后一步.假设您有一个方法可以向用户显示一条消息,说明是否创建了新用户:

Where mocking is useful is when you want to, for example, make a database call somewhere in your code, but you don't want to actually call to the database. You want to pretend that the database got called, but you want to set up some fake data for it to return, and then (here's the important part) test the logic that does something with the fake data your mock returned. In the above examples you are omitting the last step. Imagine you had a method that displayed a message to the user that said whether the new user was created:

public string displayMessage(bool userWasCreated) {
    if (userWasCreated)
        return "User created successfully!";
    return "User already exists";
}

那么您的考试就是

userRepo.Expect(repo => repo.CreateUser(theUser)).Return(false);
Assert.AreEqual("User already exists", displayMessage(userSvc.CreateUser(theUser)))

现在这具有一定的价值,因为您正在测试一些实际的行为.当然,您也可以通过传递"true"或"false"直接进行测试.您甚至不需要该测试的模拟程序.测试期望值很好,但是我已经写了很多类似的测试,并且得出了您所要达到的相同结论-只是没有用.

Now this has some value, because you are testing some actual behavior. Of course, you could also just test this directly by passing in "true" or "false." You don't even need a mock for that test. Testing expectations is fine, but I've written plenty of tests like that, and have come to the same conclusion that you are reaching - it just isn't that useful.

简而言之,当您要抽象化外部性(例如数据库或Web服务调用等)并在此时注入已知值时,模拟非常有用.但是直接测试模拟并不总是有用的.

So in short, mocking is useful when you want to abstract away externalities, like databases, or webservice calls, etc, and inject known values at that point. But it's not often useful to test mocks directly.

这篇关于如何正确模拟和单元测试的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆