Статьи

Введение, тестирование Groovy / Spock, почему вы должны рассмотреть это

Как разработчик, тестирование очень важно. Некоторые разработчики думают: «Мех, я пишу код, тестирование — это работа QAs», что довольно плохо. Разработчику гораздо лучше тестировать свой код, как правило, если он хорошо принят, он производит код лучшего качества, и, конечно, чем раньше будут выявлены проблемы, тем дешевле они будут устранены.

Большинство разработчиков Java, которые  следят за  TDD, вероятно, используют mockito или powermock вместе с JUnit. Я никогда не был большим поклонником этих комбинаций, так как считаю, что они содержат слишком много стандартного кода, и тестовый код часто становится более подробным и сложным в обслуживании, чем сам производственный код.

После знакомства со  Spock и тестирования в  Groovy в  прошлом году я полностью продал его и впоследствии использовал его в нескольких других проектах.

В этой статье я буду основывать его на классе обслуживания, который делает некоторые вещи с объектом домена через уровень доступа к данным, к которому большинство корпоративных разработчиков могут относиться.

Вот класс домена:

public class User {

    private int id;
    private String name;
    private int age;

    // Accessors omitted
}

Вот интерфейс DAO:

public interface UserDao {

    public User get(int id);

}

И наконец сервис:

public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User findUser(int id){
        return null;
    }
}

Ничего слишком сложного, чтобы упомянуть здесь. Класс, который мы собираемся проверить, — это сервис. Вы можете видеть, что сервис зависит от UserDao, который передается в конструктор. Это хорошая практика проектирования, потому что вы заявляете, что для того, чтобы иметь UserService, он должен быть создан с UserDao. Это также становится полезным позже при использовании каркасов внедрения зависимостей, таких как Spring, так что вы можете пометить их как Components и Autowire аргументы конструктора, но увы.

Давайте продолжим и создадим тестовый класс для сервиса (команда + shift + t, если используется IntelliJ на Mac).

class UserServiceTest extends Specification {

    UserService service
    UserDao dao = Mock(UserDao)

    def setup(){
        service = new UserService(dao)
    }

    def "it gets a user by id"(){
        given:
        def id = 1

        when:
        def result = service.findUser(id)

        then:
        1 * dao.get(id) >> new User(id:id, name:"James", age:27)
        result.id == 1
        result.name == "James"
        result.age == 27
    }
}

Здесь мы идем, прямо в глубине, позвольте мне объяснить, что здесь происходит. Во-первых, мы используем groovy, поэтому, хотя он выглядит как Java (я полагаю, что в некотором смысле это так, поскольку он все равно компилируется в байт-код Java), синтаксис немного легче, например, нет точек с запятой для завершения операторов, нет необходимости для общедоступного метода доступа, поскольку все по умолчанию является общедоступным, строки для имен методов. Если вы хотите узнать больше о Groovy, ознакомьтесь с их документацией  здесь .

Как вы видите, тестовый класс происходит от spock.lang.Specification, это базовый класс Spock и позволяет нам использовать заданные, когда и затем блоки в нашем тесте.

Тогда вы увидите предмет теста, сервис. Я предпочитаю определять это как поле и назначать его в настройке, но другие предпочитают создавать его в данном блоке каждого теста, я полагаю, что это на самом деле просто личное предпочтение.

Создавать макеты с помощью Спока легко, просто используйте Mock (Class). Затем я передаю ложную зависимость DAO в userService в методе установки. Установка запускается перед каждым тестом (аналогично, cleanup () запускается после завершения каждого теста). Это отличный шаблон для тестирования, поскольку вы можете смоделировать все зависимости и определить их поведение, поэтому вы буквально просто тестируете класс обслуживания.

Отличительной особенностью groovy является то, что вы можете использовать литералы String для именования ваших методов, это значительно упрощает чтение и анализ тестов, а не называет их «public void testItGetsAUserById ()»

Given, when, then

Spock is a behaviour driven development (BDD) testing framework, which is where it gets the given, when and then patterns from (amongst others). The easiest way I can explain it as follows:

Given some parts, when you do something, then you expect certain things to happen.

It’s probably easier to explain my test. We’re given an id of 1, you can think of this as a variable for the test. The when block is where the test starts, this is the invocation, we’re saying that when we call findUser() on the service passing in an id, we’ll get something back and assign it to the result.

The then block are your assertions, this is where you check the outcomes. The first line in the then block looks a little scary, but actually it’s very simple, lets dissect it.

1 * dao.get(id) >> new User(id:id, name:"James", age:27)

This line is setting an expectation on the mocked dao. We’re saying that we expect 1 (and only 1) invocation on the dao.get() method, that invocation must be passed id (which we defined as 1 earlier). Still with me? Good, we’re half way.

The double chevron “>>” is a spock feature, it means “then return”. So really this line reads as “we expect 1 hit on the mocked dao get(), and when we do, return a new User object”

You can also see that I’m using named parameters in the constructor of the User object, this is another neat little feature of groovy.

The rest of the then block are just assertions on the result object, not really required here as we’re doing a straight passthrough on the dao, but gives an insight as to what you’d normally want to do in more complex examples.

The implementation.

If you run the test, it’ll fail, as we haven’t implemented the service class, so lets go ahead and do that right now, its quite simple, just update the service class to the following:

public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public User findUser(int id){
        return userDao.get(id);
    }
}

Run the test again, it should pass this time.

Stepping it up

That was a reasonably simple example, lets look at creating some users. Add the following into the UserService:

public void createUser(User user){
        // check name

        // if exists, throw exception

        // if !exists, create user
    }

Then add these methods into the UserDao

public User findByName(String name);
    public void createUser(User user);

Then start with this test

def "it saves a new user"(){
        given:
        def user = new User(id: 1, name: 'James', age:27)

        when:
        service.createUser(user)

        then:
        1 * dao.findByName(user.name) >> null

        then:
        1 * dao.createUser(user)
    }

This time, we’re testing the createUser() method on the service, you’ll notice that there is nothing returned this time.

You may be asking “why are there 2 then blocks?”, if you group everything into a single then block, Spock just asserts that they all happen, it doesn’t care about ordering. If you want ordering on assertions then you need to split into separate then blocks, spock then asserts them in order. In our case, we want to firstly find by user name to see if it exists, THEN we want to create it.

Run the test, it should fail. Implement with the following and it’ll pass

    public void createUser(User user){
        User existing = userDao.findByName(user.getName());

        if(existing == null){
            userDao.createUser(user);
        }
    }

Thats great for scenarios where the user doesn’t already exist, but what if it does? Lets write so co…NO! Test first!

def "it fails to create a user because one already exists with that name"(){
        given:
        def user = new User(id: 1, name: 'James', age:27)

        when:
        service.createUser(user)

        then:
        1 * dao.findByName(user.name) >> user

        then:
        0 * dao.createUser(user)

        then:
        def exception = thrown(RuntimeException)
        exception.message == "User with name ${user.name} already exists!"
    }

This time, when we call findByName, we want to return an existing user. Then we want 0 interactions with the createUser() mocked method.

The third then block grabs hold of the thrown exception by calling thrown() and asserts the message. Note that groovy has a neat feature called GStrings that allow you to put arguments inside quoted strings.

Run the test, it will fail. Implement with the following at it’ll pass.

public void createUser(User user){
        User existing = userDao.findByName(user.getName());

        if(existing == null){
            userDao.createUser(user);
        } else{
            throw new RuntimeException(String.format("User with name %s already exists!", user.getName()));
        }
    }

I’ll leave it there, that should give you a brief intro to Spock, there is far more that you can do with it, this is just a basic example.

Snippets of wisdom

  • Read the spock documentation!
  • You can name spock blocks such as given:”Some variables”, this is useful if its not entirely clear what your test is doing.
  • You can use _ * mock.method() when you don’t care how many times a mock is invoked.
  • You can use underscores to wildcard methods and classes in the then block, such as 0 * mock._ to indicate you expect no other calls on the mock, or 0 * _._ to indicate no calls on anything.
  • I often write the given, when and then blocks, but then I start from the when block and work outwards, sounds like an odd approach but I find it easier to work from the invocation then work out what I need (given) and then what happens(then).
  • The expect block is useful for testing simpler methods that don’t require asserting on mocks.
  • You can wildcard arguments in the then block if you don’t care what gets passed into mocks.
  • Embrace groovy closures! They can be you’re best friend in assertions!
  • You can override setupSpec and cleanupSpec if you want things to run only once for the entire spec.

Conclusion

Having used Spock (and groovy for testing) on various work and hobby projects I must admit I’ve become quite a fan. Test code is there to be an aid to the developer, not a hinderance. I find that groovy has many shortcuts (collections API to name but a few!) that make writing test code much nicer.

You can view the full Gist here https://gist.github.com/jameselsey/8096211