Красивый Soft Assert

Пользовались ли вы когда-либо ассертами? Автоматизаторы не понаслышке знают, что это такое и как им пользоваться. Я уже писал о библиотеках, которые упрощают написание ассертов и делают их более читабельными.

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

Вот, скажем, пример SoftAssert и TestNG:

import org.testng.annotations.Test;
import org.testng.asserts.SoftAssert;

public class SoftAsert
{
    @Test
    public void test()
    {
        SoftAssert asert=new SoftAssert();
        asert.assertEquals(false, true,"failed");
        asert.assertEquals(0, 1,"brokedown");
        asert.assertAll();
    }
}

Вроде как работает, но выглядит ужасно. К тому же, каждый раз нужно создавать объект и в конце писать assertAll(). И это не прихоть одной конкретной библиотеки, такой принцип у всех.

Хочу поделиться своим видением ситуации. Если вам что-то не нравится, попробуйте изменить ситуацию. Я так и сделал и заменил Java на Groovy. Закоренелые читатели блога это заметили давно. Почему? Потому, что Groovy позволяет мне быть эффективнее в 2.5-3 раза. Код выглядит читабельнее и проще.

Стоит посмотреть на page object. Ну или на REST Client.

До этого момента в Groovy был очень крутой assert, а soft assert не было.

Теперь есть, и выглядит он так!

softAsserts {
    expect "foo", equalTo("bar")
    expect 1, equalTo(1)
}

Круто, не правда ли? Мне такой подход к делу нравится больше. К тому же, в этом коде везде работает автодополнение, а сами ассерты являются оберткой над Hamcrest.

Реализация этого счастья выглядит так:

class SoftAsserts {
    def static failedAssertions = []

    static softAsserts(Closure closure) {
        new SoftAsserts().bundleAsserts(closure)
    }

    private bundleAsserts(Closure closure) {
        closure.resolveStrategy = Closure.DELEGATE_ONLY
        closure.delegate = this
        closure()

        if (failedAssertions) {
            throw new AssertionError("${failedAssertions.size()}
            failed assertions found:\n ${failedAssertions.
            message.join('\n')}".toString())
        }
    }

    public static <T> void expect(T actual, Matcher<? super T> matcher) {
        expect("", actual, matcher)
    }

    public static <T> void expect(String reason, T actual, Matcher<? super T> matcher) {
        try {
            if (!matcher.matches(actual)) {
                Description description = new StringDescription();
                description.appendText(reason)
                        .appendText("\nExpected: ")
                        .appendDescriptionOf(matcher)
                        .appendText("\n     but: ");
                matcher.describeMismatch(actual, description);

                throw new AssertionError(description.toString());
            }
        } catch (AssertionError e) {
            failedAssertions << e
        }
    }

    public static void expect(String reason, boolean assertion) {
        if (!assertion) {
            try {
                throw new AssertionError(reason);
            } catch (AssertionError e) {
                failedAssertions << e
            }
        }
    }

    def invokeMethod(String name, args) {
        try {
            def actual = args[0]
            def expected = args[1]
            assert actual == expected
        } catch (AssertionError e) {
            failedAssertions << e
        }
    }
}

Все предельно просто - один класс, в котором реализована вся логика.

Тесты с применением этих ассертов становятся очень красивыми и понятными:

def endpoint = 'http://swapi.co/api/'
def client = new RESTClient(endpoint)
def response = client.get(path:'people/1/',accept: ContentType.JSON,headers:['User-agent':'firefox'])

softAsserts {
    expect response.statusCode, is(200)
    expect response.json.name, equalTo('Luke Skywalker')
}