Статьи

Все дело в тестах — часть 3

В предыдущих двух постах я обсуждал в основном философию и отношение разработки с тестированием. В этом посте я даю несколько советов и примеров инструментов для тестирования.

инструменты

JUnit

http://junit.org/
Есть также TestNG, который является отличным инструментом. Но у меня гораздо больше опыта работы с JUnit, поэтому я опишу эту структуру.

  1. Используйте последнюю версию.
  2. Знай своего инструмента тестирования!
  • @Бежать с
    Это аннотация класса. Он говорит JUnit работать с разными бегунами (бегуны мокито и спрингеры — самые распространенные бегуны, которых я использую)

    1
    2
    3
    4
    5
    6
    import org.mockito.runners.MockitoJUnitRunner;
    ...
    @RunWith(MockitoJUnitRunner.class)
    public class MyClassTest {
      ...
    }
    1
    2
    3
    4
    5
    6
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = { "/META-INF/app-context.xml","classpath:anotherContext.xml" })
    public class MyClassTest {
      ...
    }
    // You can inherit AbstractJUnit4SpringContextTests instead of using runner
  • @Rule
    вид АОП. Наиболее распространенное готовое правило — правило TemporaryFolder. Это позволяет вам использовать файловую систему, не беспокоясь об открытии и закрытии файлов. Пример правил можно найти здесь .
  • Параметризованный бегун
    Действительно классный инструмент. Это позволяет вам запускать один и тот же тест с разным вводом и разным ожидаемым выводом. Это может быть злоупотреблено и сделать тест нечитаемым.
  • Советы по подготовке и обслуживанию тестовых данных

Hamcrest

http://hamcrest.org/JavaHamcrest/
Эта библиотека является «расширением» JUnit. Я не могу работать без этого! Библиотека Hamcrest дает нам нестандартные совпадения. Сопоставители используются со assertThat(...,Matcher) . Я почти всегда использую этот аромат. (В предыдущем посте кто-то предложил мне не использовать assertTrue (…), а вместо этого использовать assertThat). Существует множество типов сопоставлений: Вы можете проверить существующие объекты в порядке игнорирования коллекции. Вы можете проверить больше, чем. Тест более читабелен, используя assertThat + matcher.

1
2
3
4
5
assertThat(mapAsCache.containsKey(new CacheKey("valA", "valB")), is(true));
assertThat(cachPairs.size(), is(2));
assertThat(enity.getSomething(), nullValue(Double.class));
assertThat(event.getType(), equalTo(Type.SHOWN));
assertThat(bits, containsInAnyOrder(longsFromUsIndexOne, longsFromUsIndexZero));

Вы можете создать свой собственный Matcher. Это очень легко. Вот пример сопоставителей, которые проверяют регулярные выражения. https://github.com/eyalgo/junit-additions

Mockito

https://code.google.com/p/mockito/
Это вторая библиотека, без которой я не могу работать. Это позволяет вам смоделировать зависимости тестируемого класса. Используя mockito, вы издеваетесь над зависимостью. Затем вы «говорите» фиктивному объекту, как вести себя при определенных входах. Вы говорите ему, что возвращать, если какой-то ввод введен Вы можете проверить входные аргументы вызываемого метода. Вы можете проверить, что был вызван определенный метод (один раз, никогда, 3 раза и т. Д.). Вы можете проверить порядок вызовов метода / макетов.

Проверь это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
package eyalgo;
  
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
  
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
  
//The RunWith automatically instantiate fields with @Mock annotation
//and injects to the tested class @InjectMocks
@RunWith(MockitoJUnitRunner.class)
public class NameConnectorTest {
    @Mock
    private NameConvention nameConventionAsMockField;
     
    @InjectMocks
    private NameConnector connector;
     
    private NameConvention nameConventionAsMockOther;
     
    @Before
    public void setup() {
        //This is another way to inject mocks (instead of the annotations above)
        MockitoAnnotations.initMocks(this);
        nameConventionAsMockOther = mock(NameConvention.class);
        NameConnector otherConnector = new NameConnector(nameConventionAsMockOther);
    }
     
    @Test
    public void showSomeMockitoExamples() {
        NameConvention nameConventionAsMock = mock(NameConvention.class, "Name for this mock");
         
        // Stub and tell your mock to do something
        when(nameConventionAsMock.bigBangConvention("INPUT")).thenReturn("Some output");
         
        // Throw exception for some input
        when(nameConventionAsMock.bigBangConvention("Other INPUT")).thenThrow(new RuntimeException("oops"));
         
        // Do more complicated stuff in the "when"
        Answer answer = new Answer() {
  
            @Override
            public String answer(InvocationOnMock invocation) throws Throwable {
                //do something really complicated
                return "some output";
            }
        };
        //Show also hamcrest matchers
        when(nameConventionAsMock.bigBangConvention(argThat(equalTo("my name is Inigo Montoya")))).then(answer);
         
        // Run the test..
         
        //Verify some calls
        verify(nameConventionAsMock).bigBangConvention("INPUT");
        verify(nameConventionAsMock, times(10)).bigBangConvention("wow");
         
        // Verify that the method was never called. With any input
        verify(nameConventionAsMock, never()).bigBangConvention(anyString());
         
        verifyNoMoreInteractions(nameConventionAsMock);
        verifyZeroInteractions(nameConventionAsMockField);
         
        //Check order of calls
        InOrder order = inOrder(nameConventionAsMock, nameConventionAsMockOther);
        order.verify(nameConventionAsMock).bigBangConvention("INPUT");
        order.verify(nameConventionAsMock).bigBangConvention("other INPUT");
         
    }
}

Другие насмешные инструменты

  • PowerMock и EasyMock
    Эти два очень полезны при работе с устаревшим кодом. Они позволяют вам тестировать частные методы, статические методы и другие вещи, которые вы обычно не можете. Я думаю, что если они вам нужны, значит что-то не так с дизайном. Однако иногда вы используете внешние библиотеки с синглетами и / или статическими методами. Иногда вы работаете над устаревшим кодом, который плохо подходит для тестирования.
    В этих типах сценариев эти вспомогательные библиотеки могут помочь https://code.google.com/p/powermock/
    http://easymock.org/
  • JMockit http://jmockit.github.io/
  • jMock http://jmock.org/

JBehave

http://jbehave.org/
Для юнит-тестов используются JUnit, mockito, hamcrest. JBehave не совсем то же самое. Это инструмент для управляемой поведением разработки (BDD). Вы пишете истории, которые поддерживаются кодом (Java), а затем запускаете их. JBehave может использоваться для тестов более высокого уровня, таких как функциональные тесты. Используя JBehave, проще протестировать поток в системе. Это следует за заданной, когда, затем последовательностью. Если вы перейдете к следующему шагу, это может быть отличным инструментом для общения. Владелец продукта может написать сценарии, и если к концу итерации все будет зеленым, то мы передадим определение готового . огурец является еще одним инструментом BDD.

Внедрение зависимости

Для того, чтобы иметь тестируемый код, помимо прочего, вам нужно попрактиковаться в DI (внедрение зависимостей). Причина проста: если вы создаете экземпляр зависимости в конструкторе (или методе) тестируемого класса, то как вы можете его высмеять? Если вы не можете издеваться над зависимостью, то вы обязаны к ней привязаться. И вы не можете моделировать разные случаи. Во многих приложениях Spring используется в качестве контейнера DI, но меньшее количество разработчиков используют преимущество использования инъекции для тестирования.

метрика

Используйте SONAR в вашей среде CI. Проверьте покрытие кода, используя cobertura или другие инструменты. Используйте инструмент Jenkins / Hudson / Other CI для автоматизации.

IDE

Ваша IDE может помочь вам в написании тестов.
Для затмения у меня есть две рекомендации:

    1. MoreUnit — это крутой плагин, который помогает быстрее писать тесты.
    2. В eclipse CTRL + Пробел может дать вам подсказки и заполнить импорт. Но не статический импорт. Большинство (все?) Библиотеки используют статический импорт. Таким образом, вы можете добавить тестовые библиотеки в избранное и тогда Eclipse заполнит их для вас.
затмение фаворитов

затмение фаворитов

POM

Вот часть POM для тестирования библиотек.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <configuration>
                    <archive>
                        <addMavenDescriptor>false</addMavenDescriptor>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-source-plugin</artifactId>
                <executions>
                    <execution>
                        <goals>
                            <goal>jar-no-fork</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.startapp.CouchRunner.GetUserProfile</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
            </plugin>
        </plugins>
    </build>

Вы можете проверить порядок вызовов метода / макетов.

Вы можете использовать профили для разделения модульного тестирования на интеграционные тесты.