简体   繁体   English

测试void方法的行为

[英]Testing the behavior of void method

Suppose I have the following service object 假设我有以下服务对象

public class UserService {

    @Autowired
    private UserDao dao;

    public void addUser(String username, String password) {
        if (username.length() < 8 ) {
            username = username  + "random" ; // add some random string
        }
        User user = new User(username, password);

        dao.save(user);
    }
}

I want to test the behaviour of the method "addUser" when username length is less 8 and when the username is more than 8 char. 我希望在用户名长度小于8且用户名大于8个字符时测试方法“addUser”的行为。 How do approach in unit test UserService.addUser(...), and verify it? 如何在单元测试UserService.addUser(...)中进行验证并验证它? I am aware using assert(), but the value "password" is not available outside the addUser(...) method. 我知道使用assert(),但值“password”在addUser(...)方法之外是不可用的。

I use JUnit and Mockito. 我使用JUnit和Mockito。

I came up a solution, after some re-visit the problem again after some months. 在几个月后再次重新访问问题后,我提出了一个解决方案。

The idea is to observed the object user that is being passed to UserDao. 我们的想法是观察传递给UserDao的对象用户。 We can inspect the value of the username by doing this, hence the unit test code: 我们可以通过执行此操作来检查用户名的值,因此单元测试代码:

@RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
    @Mock
    private UserDao dao;

    @InjectMock
    private UserService service;

    @Test
    public void testAddingUserWithLessThan8CharUsername () {
        final String username = "some";
        final String password = "user";
        doAnswer(new Answer<Object>() {
            @Override
            public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                Object[] args = invocationOnMock.getArguments();
                User toBeSaved = (User) args[0];
                Assert.assertEquals(username + "random", toBeSaved.getPassword());
                return null;
            }
        }).when(userDao).save(Matchers.any(User.class));
        service.addUser(username, password);
    }
}

Guillaume actually had the closest answer, but he answered using jMock. Guillaume实际上有最接近的答案,但他回答使用jMock。 However, he gave me the idea on how to accomplish this, so I think he deserves some credit too. 但是,他给了我关于如何实现这个目标的想法,所以我认为他也应该得到一些荣誉。

You are testing side-effects, but fortunately, everything you need is passed to the dao.save(). 您正在测试副作用,但幸运的是,您需要的所有内容都会传递给dao.save()。 First, create a UserDao (either with or without Mockito), then you can use ReflectionTestUtils to set the dao in the UserService, then you can test the values which are passed to dao.save(). 首先,创建一个UserDao(有或没有Mockito),然后你可以使用ReflectionTestUtils在UserService中设置dao,然后你可以测试传递给dao.save()的值。

Something like: 就像是:

private class TestUserDao extends UserDao {
    private User savedUser;
    public void save(User user) {
        this.savedUser = user;
    }
}

@Test public void testMethod() {
    UserService userService = new UserService();
    TestUserDao userDao = new TestUserDao();

    ReflectionTestUtils.setField(userService, "dao", userDao);

    userService.addUser("foo", "bar");

    assertEquals("foo", userDao.savedUser.username.substring(0, 3));
    assertEquals("bar", userDao.savedUser.password);
}

Or you can user Mockito to mock out the Dao if you want. 或者,如果你愿意,你可以使用Mockito模拟Dao。

Use a mocking framework. 使用模拟框架。 The example below uses JMock2 , but it would be similar with EasyMock , Mockito , etc. Also, you need to extract the username generation to something like UsernameGenmerator to be able to mock it. 下面的示例使用JMock2 ,但它与EasyMockMockito等类似。此外,您需要将用户名生成提取为UsernameGenmerator ,以便能够模拟它。 You need another specific test for the username generator. 您需要为用户名生成器进行另一项特定测试。

private final Mockery mockery = new Mockery();
private final UserDao mockDao = mockery.mock(UserDao.class);
private final UsernameGenerator mockUserNameGenerator = mockery.mock(UsernameGenerator.class);

@Test 
public void addUserUsesDaoToSaveUser() {
    final String username = "something";
    final String generatedUsername = "siomething else";
    final String password = "a password";
    mockery.checking(new Expectations() {{
        oneOf(mockUsernameGenerator).generateUsername(username);
        will(returnValue(generatedUsername));
        oneOf(mockDao).save(new User(generatedUsername, password)); // assumes your User class has a "natueral" equals/hashcode
    }});

    UserService userService = new UserService();
    userService.addUser(username, password);
}

And for UsernameGenerator you need test on length of the returned username: 对于UsernameGenerator您需要测试返回的用户名的长度:

@Test 
public void leavesUsernameUnchangedIfMoreThanEightChars() {
    final String username = "123456789";
    final UsernameGenerator usernameGenerator = new UsernameGenerator();
    assertEquals(username, userGenerator.generateUsername(username));
}

@Test 
public void addsCharactersToUsernameIfLessThanEightChars() {
    final String username = "1234567";
    final UsernameGenerator usernameGenerator = new UsernameGenerator();
    assertEquals(8, userGenerator.generateUsername(username).length());
}

Of course, depending on your "random" method, you may want to test its specific behaviour too. 当然,根据您的“随机”方法,您可能也想测试其特定行为。 Apart from that, the above provide sifficient coverage for your code. 除此之外,上面提供了对您的代码的有效覆盖。

It would all depend on how your DAO's save method is implemented. 这完全取决于DAO的保存方法是如何实现的。

If you are actually storing to a hard-coded repository, then you will probably need to query the repository itself for the values you are intereseted in. 如果您实际存储到硬编码存储库,那么您可能需要查询存储库本身以获取您所参与的值。

If you have an underlying interface which is called, then you should be able to set up a callback method and retrieve the actual value which is being saved. 如果您有一个被调用的底层接口,那么您应该能够设置一个回调方法并检索正在保存的实际值。

I have never used Mockito so I couldn't give you exact code which does this article should address that: 我从未使用过Mockito所以我无法给出确切的代码,本文应该解决这个问题:

Using Mockito, how do I intercept a callback object on a void method? 使用Mockito,如何拦截void方法上的回调对象?

Consider extracting user name generation logic as dependency from UserService. 考虑从UserService中提取用户名生成逻辑作为依赖项。

interface UserNameGenerator {
    Strign generate();
}

Wire UserNameGenerator same as UserDao . Wire UserNameGeneratorUserDao相同。 And change the code to: 并将代码更改为:

public class UserService {

    @Autowired
    private UserDao dao;
    @Autowired
    private UserNameGenerator nameGenerator;

    public void addUser(String username, String password) {
        if (username.length() < 8 ) {
            username = nameGenerator.generate();
        }
        User user = new User(username, password);

        dao.save(user);
    }
}

Next create the default implementation of UserNameGenerator and move name generating logic there. 接下来创建UserNameGenerator的默认实现,并在那里移动名称生成逻辑。

Now you can easily check behavior by mocking UserNameGenerator and UserDao . 现在,您可以通过UserDao UserNameGeneratorUserDao轻松检查行为。

To check use case when username is length is less than 8 用户名长度小于8时检查用例

String username = "123";
String password = "pass";

String generatedName = "random";

// stub generator
when(nameGenerator.generate()).thenReture(generatedName);

// call the method
userService.addUser(username, password);

// verify that generator was called
verify(nameGenerator).generate();

verify(userDao).save(new User(generatedName, password));

To check use case when username is length is greater than 8 用户名长度大于8时检查用例

String username = "123456789";
String password = "pass";

String generatedName = "random";

// call the method
userService.addUser(username, password);

// verify that generator was never called
verify(nameGenerator, never()).generate();

verify(userDao).save(new User(username, password));

Easiest way is to extract the part where you have the user name correction logic 最简单的方法是提取具有用户名更正逻辑的部分

if (username.length() < 8 ) {
    username = username  + "random" ; // add some random string
}

into a method and test the return value of that method. 进入方法并测试该方法的返回值。

public string GetValidUsername(string userName){
    if (username.length() < 8 ) {
        return username  + "random" ; // add some random string
    }
    return username;
}

with this you can pass different types of username and test the behavior of your code. 通过这种方式,您可以传递不同类型的用户名并测试代码的行为。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM