对没有可观察到的状态变化的方法进行单元测试

发布于 2024-09-14 16:29:43 字数 1487 浏览 3 评论 0原文

(C#、Rhino Mocks、MbUnit)。

我有一个名为 AccountManager 的类,它有一个 RegisterUser() 方法。此方法返回 void,但会针对任何错误引发异常。 AccountManager 调用 IDataRepository 并调用其 AddUser() 方法来执行数据库插入。

我使用 Rhino Mock 来模拟 IDataRepository,并针对一组给定的参数抛出异常,模拟存储库中引发的异常。

[Test]
    public void RegisterKnownUser()
    {
        MockRepository mocks = new MockRepository();
        IDataRepository dataRepository = mocks.StrictMock<IDataRepository>();

        using (mocks.Record())
        {
            Expect.Call(() => dataRepository.AddUser("abc", "abc", "[email protected]", "a", "bc")).Throw(
                new InvalidOperationException());
        }

        using (mocks.Playback())
        {
            AccountManager manager = new AccountManager(dataRepository);
            Assert.Throws(typeof (InvalidOperationException), () => manager.RegisterUser("abc", "abc", "[email protected]", "a", "bc"));
        }
    }

这个测试效果很好。

我的问题是,如果提供给 RegisterUser 的参数正确且有效,该怎么办。真正的 IDataRepository 不会返回任何内容,也不会抛出任何异常。简而言之,AccountManager 的状态不会改变。这是否意味着我不需要测试 AccountManager.RegisterUser ,因为它会导致我无法在测试的类和方法中直接观察到任何内容。在模拟中测试状态对我来说有点味道。我认为只要我单独测试 IDataRepository.AddUser ,那么我就不需要测试 AccountManager.RegisterUser 的输入,这会导致类中没有任何可观察到的内容。

提前致谢。

(C#, Rhino Mocks, MbUnit).

I have a class called AccountManager that has a RegisterUser() method. This method returns void but does throw an exception for any errors. AccountManager calls into an IDataRepository calling its AddUser() method to do a database insert.

I'm mocking the IDataRepository using a Rhino Mock and throwing and exception for a given set of arguments simulating the exception being raised in the repository.

[Test]
    public void RegisterKnownUser()
    {
        MockRepository mocks = new MockRepository();
        IDataRepository dataRepository = mocks.StrictMock<IDataRepository>();

        using (mocks.Record())
        {
            Expect.Call(() => dataRepository.AddUser("abc", "abc", "[email protected]", "a", "bc")).Throw(
                new InvalidOperationException());
        }

        using (mocks.Playback())
        {
            AccountManager manager = new AccountManager(dataRepository);
            Assert.Throws(typeof (InvalidOperationException), () => manager.RegisterUser("abc", "abc", "[email protected]", "a", "bc"));
        }
    }

This test works fine.

My question is what to do about the situation where the args supplied to RegisterUser are correct and valid. The real IDataRepository would not return anything nor would it thrown any exceptions. So in short AccountManager's state would not have changed. Does this mean I don't need to test AccountManager.RegisterUser when it would result in nothing I can observe directly in the class and method under test. Testing against state in the mock smells a bit to me. I think as long as I test IDataRepository.AddUser seperately then I shouldn't need to test AccountManager.RegisterUser for inputs that would result in nothing observable in the class.

Thanks in advance.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

战皆罪 2024-09-21 16:29:43

如果 AccountManager 调用 DataPrepository,那么您的测试用例仍然会验证某些内容。这里的记录/回放验证了是否对其进行了调用。如果不进行调用,测试用例将失败。如果它被创建两次/使用错误的参数,它将失败。

这可能是一个非常基本的测试用例,但它仍然是一个很好的测试用例,并且不需要您将状态放置在模拟对象中。

If AccountManager calls into DataPrepository, then your test case still validates something. The record/playback here validates that a call is made into it. If the call is not made, the test case will fail. If it is made twice/with the wrong args, it will fail.

This may be a very basic test case, but it is still a good one, and doesn't require you to place state in the mock object.

清风无影 2024-09-21 16:29:43

您可能需要使用有效参数测试该方法,以确保它不会引发任何异常。换句话说,您无法观察状态更改或使用返回值(因为它是空的),但您可以观察到该方法运行时没有异常。

顺便说一句,如果该方法不返回值也不更改 AccountManager 状态,它确实会更改其他内容(如果没有,那么您可能应该从代码中删除该方法它什么都不做)。
例如,它可能会影响DataRepository。或者在数据库中添加一条记录。这种情况下,至少可以测试一下数据是否改变或者记录是否添加成功。或者它可能会记录一个事件,表明新用户已注册,因此您将能够在测试中检查日志事件是否位于此处。

我认为只要我单独测试 IDataRepository.AddUser 那么我就不需要测试 AccountManager.RegisterUser 的输入,这会导致类中没有任何可观察到的内容

如果 AccountManager.RegisterUser 不向 添加任何内容IDataRepository.AddUser 除了参数强制之外,是的,如果您已经测试了 IDataRepository.AddUser,则不必测试它。如果它检查参数,调用AddUser并执行其他操作,那么最好检查它所做的是否正确。

假设您有:

public void AddUser(string userName, string userMail, string passwordHash)
{
    // [...] Add a user to the database.
}

public void RegisterUser(string userName, string userMail, string passwordHash)
{
    if (string.IsNullOrEmpty(userName)) throw new ArgumentNullException(...);
    if (string.IsNullOrEmpty(userMail)) throw new ArgumentNullException(...);
    if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentNullException(...);
    if (!Checks.IsValidMail(userMail)) throw new ArgumentException(...);

    this.AddUser(userName, userMail, passwordHash);

    this.SaveToLog(LogEvent.UserRegistered, userName, this.IPAddress);
}

RegisterUser 中,您通过传递错误的参数并期望出现异常来测试前四行。不得测试第五行,因为您已经测试了 AddUser。最后,必须测试第六行,以确保当您使用有效参数调用 RegisterUser 时,会创建日志条目。

You may want to test the method with valid arguments to ensure it does not throw any exception. In other words, you cannot observe state change or use a return value (since it's void), but you can observe that the method ran without exceptions.

By the way, if the method does not return a value nor change AccountManager state, it does change something otherwise (if not, than you should probably remove from your code the method which does nothing at all).
For example, it may affect DataRepository. Or add a record in the database. In this case, you can test at least if the data is changed or if the record is added successfully. Or it may log an event saying that the new user was registered, so you will be able, in your tests, to check if the log event is here.

I think as long as I test IDataRepository.AddUser seperately then I shouldn't need to test AccountManager.RegisterUser for inputs that would result in nothing observable in the class

If AccountManager.RegisterUser adds nothing to IDataRepository.AddUser except arguments enforcement, than yes, you don't have to test it if you already tested IDataRepository.AddUser. If it checks arguments, calls AddUser and does something else, it will be good to check if what it does is correct.

Let's say you have:

public void AddUser(string userName, string userMail, string passwordHash)
{
    // [...] Add a user to the database.
}

public void RegisterUser(string userName, string userMail, string passwordHash)
{
    if (string.IsNullOrEmpty(userName)) throw new ArgumentNullException(...);
    if (string.IsNullOrEmpty(userMail)) throw new ArgumentNullException(...);
    if (string.IsNullOrEmpty(passwordHash)) throw new ArgumentNullException(...);
    if (!Checks.IsValidMail(userMail)) throw new ArgumentException(...);

    this.AddUser(userName, userMail, passwordHash);

    this.SaveToLog(LogEvent.UserRegistered, userName, this.IPAddress);
}

In RegisterUser, you test the first four lines by passing wrong arguments and expecting an exception. The fifth line must not be tested, since you already tested AddUser. Finally, the sixth line must be tested to ensure that when you call RegisterUser with valid arguments, the log entry is created.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文