Статьи

Написание чистых тестов — новый считается вредным

Довольно сложно найти хорошее определение чистого кода, потому что у каждого из нас есть собственное определение слова «чистый». Однако есть одно определение, которое кажется универсальным:

Чистый код легко читается.

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

  • Если наши тесты легко читаются, легко понять, как работает наш код.
  • Если наши тесты легко читаются, легко найти проблему, если тест не пройден (без использования отладчика).

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

Я тоже с этим боролся, и поэтому решил поделиться с вами своими выводами.

Это четвертая часть моего урока, которая описывает, как мы можем писать чистые тесты. На этот раз мы узнаем, почему мы не должны создавать объекты в наших тестовых методах, используя ключевое слово new . Мы также узнаем, как мы можем заменить новое ключевое слово фабричными методами и создателями тестовых данных.

Новое не новое черное

В этом руководстве мы провели рефакторинг модульного теста, который гарантирует, что метод registerNewUserAccount (RegistrationForm userAccountData) класса RepositoryUserService работает, как и ожидалось, когда создается новая учетная запись пользователя с использованием уникального адреса электронной почты и поставщика социального входа в систему.

Класс RegistrationForm является объектом передачи данных (DTO) , и наши модульные тесты устанавливают значения его свойств с помощью методов установки. Исходный код нашего модульного теста выглядит следующим образом (соответствующий код выделен):

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
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
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;
 
 
@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {
 
    private static final String REGISTRATION_EMAIL_ADDRESS = "[email protected]";
    private static final String REGISTRATION_FIRST_NAME = "John";
    private static final String REGISTRATION_LAST_NAME = "Smith";
    private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
    private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;
 
    private RepositoryUserService registrationService;
 
    @Mock
    private PasswordEncoder passwordEncoder;
 
    @Mock
    private UserRepository repository;
 
    @Before
    public void setUp() {
        registrationService = new RepositoryUserService(passwordEncoder, repository);
    }
 
 
    @Test
    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException       {
        RegistrationForm registration = new RegistrationForm();
        registration.setEmail(REGISTRATION_EMAIL_ADDRESS);
        registration.setFirstName(REGISTRATION_FIRST_NAME);
        registration.setLastName(REGISTRATION_LAST_NAME);
        registration.setSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
 
        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
 
        when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocation) throws Throwable {
                Object[] arguments = invocation.getArguments();
                return (User) arguments[0];
            }
        });
 
        User createdUserAccount = registrationService.registerNewUserAccount(registration);
 
        assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
        assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
        assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
        assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
        assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
        assertNull(createdUserAccount.getPassword());
 
        verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
        verify(repository, times(1)).save(createdUserAccount);
        verifyNoMoreInteractions(repository);
        verifyZeroInteractions(passwordEncoder);
    }
}

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

Если мы создадим новые объекты в методе test с помощью ключевого слова new , наши тесты станут труднее читать, потому что:

  1. Читатель должен знать различные состояния созданного объекта. Например, если мы подумаем о нашем примере, читатель должен знать, что если мы создадим новый объект RegistrationForm и установим значения свойств свойств email , firstName , lastName и signInProvider , это означает, что объект является регистрацией, которая является сделано с помощью социального входа в провайдера.
  2. Если созданный объект имеет много свойств, код, который его создает, засоряет исходный код наших тестов. Мы должны помнить, что хотя нам нужны эти объекты в наших тестах, мы должны сосредоточиться на описании поведения тестируемого метода / функции.

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

Давайте выясним, как мы можем сделать это с помощью фабричных методов.

Использование фабричных методов

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

Эти фабричные методы обычно добавляются в родительский класс объекта, потому что часто они полезны для нескольких тестовых классов. Однако, поскольку я хочу, чтобы все было просто, я добавлю их непосредственно в тестовый класс.

Имя первого фабричного метода — newRegistrationViaSocialSignIn () , и у него нет параметров метода. После того, как мы добавили этот метод фабрики в наш тестовый класс, источник нашего модульного теста выглядит следующим образом (соответствующие части выделены):

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
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
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;
 
 
@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {
 
    private static final String REGISTRATION_EMAIL_ADDRESS = "[email protected]";
    private static final String REGISTRATION_FIRST_NAME = "John";
    private static final String REGISTRATION_LAST_NAME = "Smith";
    private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
    private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;
 
    private RepositoryUserService registrationService;
 
    @Mock
    private PasswordEncoder passwordEncoder;
 
    @Mock
    private UserRepository repository;
 
    @Before
    public void setUp() {
        registrationService = new RepositoryUserService(passwordEncoder, repository);
    }
 
 
    @Test
    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
        RegistrationForm registration = newRegistrationViaSocialSignIn();
 
        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
 
        when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocation) throws Throwable {
                Object[] arguments = invocation.getArguments();
                return (User) arguments[0];
            }
        });
 
        User createdUserAccount = registrationService.registerNewUserAccount(registration);
 
        assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
        assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
        assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
        assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
        assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
        assertNull(createdUserAccount.getPassword());
 
        verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
        verify(repository, times(1)).save(createdUserAccount);
        verifyNoMoreInteractions(repository);
        verifyZeroInteractions(passwordEncoder);
    }
    
    private RegistrationForm newRegistrationViaSocialSignIn() {
        RegistrationForm registration = new RegistrationForm();
    
        registration.setEmail(REGISTRATION_EMAIL_ADDRESS);
        registration.setFirstName(REGISTRATION_FIRST_NAME);
        registration.setLastName(REGISTRATION_LAST_NAME);
        registration.setSignInProvider(SOCIAL_SIGN_IN_PROVIDER);
 
        return registration;
    }
}

Первый фабричный метод имеет следующие последствия:

  • Часть нашего тестового метода, которая создает новый объект RegistrationForm , намного чище, чем раньше, и имя фабричного метода описывает состояние созданного объекта RegistrationForm .
  • Сложнее прочитать конфигурацию нашего фиктивного объекта, потому что значение свойства email «скрыто» внутри нашего фабричного метода.
  • Наши утверждения труднее читать, потому что значения свойств созданного объекта RegistrationForm «скрыты» внутри нашего фабричного метода.

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

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

Посмотрим, сможет ли второй фабричный метод устранить эти недостатки.

Имя второго фабричного метода — newRegistrationViaSocialSignIn () , и в качестве параметров метода он принимает адрес электронной почты, имя, фамилию и идентификатор социального входа в качестве поставщика. После того, как мы добавили этот метод фабрики в наш тестовый класс, источник нашего модульного теста выглядит следующим образом (соответствующие части выделены):

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
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
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;
 
 
@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {
 
    private static final String REGISTRATION_EMAIL_ADDRESS = "[email protected]";
    private static final String REGISTRATION_FIRST_NAME = "John";
    private static final String REGISTRATION_LAST_NAME = "Smith";
    private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
    private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;
 
    private RepositoryUserService registrationService;
 
    @Mock
    private PasswordEncoder passwordEncoder;
 
    @Mock
    private UserRepository repository;
 
    @Before
    public void setUp() {
        registrationService = new RepositoryUserService(passwordEncoder, repository);
    }
 
 
    @Test
    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
        RegistrationForm registration = newRegistrationViaSocialSignIn(REGISTRATION_EMAIL_ADDRESS,
                                                                REGISTRATION_FIRST_NAME,
                                                                REGISTRATION_LAST_NAME,
                                                                SOCIAL_MEDIA_SERVICE
        );
 
        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
 
        when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocation) throws Throwable {
                Object[] arguments = invocation.getArguments();
                return (User) arguments[0];
            }
        });
 
        User createdUserAccount = registrationService.registerNewUserAccount(registration);
 
        assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
        assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
        assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
        assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
        assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
        assertNull(createdUserAccount.getPassword());
 
        verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
        verify(repository, times(1)).save(createdUserAccount);
        verifyNoMoreInteractions(repository);
        verifyZeroInteractions(passwordEncoder);
    }
    
    private RegistrationForm newRegistrationViaSocialSignIn(String emailAddress, String firstName, String lastName, SocialMediaService signInProvider) {
        RegistrationForm registration = new RegistrationForm();
    
        registration.setEmail(emailAddress);
        registration.setFirstName(firstName);
        registration.setLastName(lastName);
        registration.setSignInProvider(signInProvider);
 
        return registration;
    }
}

Второй фабричный метод имеет следующие последствия:

  • Часть нашего тестового метода, которая создает новый объект RegistrationForm , немного сложнее, чем тот же код, который использует первый метод фабрики. Тем не менее, он все еще чище, чем исходный код, потому что имя фабричного метода описывает состояние созданного объекта.
  • Кажется, он устраняет недостатки первого фабричного метода, поскольку значения свойств созданного объекта не «скрыты» внутри фабричного метода.

Кажется круто, верно?

Было бы очень легко думать, что в раю все хорошо, но это не так. Хотя мы видели, что фабричные методы могут сделать наши тесты более читабельными, дело в том, что они являются хорошим выбором только при соблюдении следующих условий:

  1. У фабричного метода не слишком много параметров метода. Когда число параметров метода растет, наши тесты становятся труднее писать и читать. Очевидный вопрос: сколько параметров метода может иметь фабричный метод? К сожалению, трудно дать точный ответ на этот вопрос, но я думаю, что использование фабричного метода является хорошим выбором, если фабричный метод имеет только несколько параметров метода.
  2. Тестовые данные не слишком сильно отличаются. Проблема использования фабричных методов заключается в том, что один фабричный метод обычно подходит для одного варианта использования. Если нам нужно поддерживать N вариантов использования, нам нужно иметь N фабричных методов. Это проблема, потому что с течением времени наши фабричные методы становятся раздутыми, грязными и трудными в обслуживании (особенно если мы используем шаблон матери-объекта).

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

Использование тестовых построителей данных

Построитель тестовых данных — это класс, который создает новые объекты с использованием шаблона компоновщика . Шаблон компоновщика, описанный в Effective Java, имеет много преимуществ , но наша основная мотивация — обеспечить свободный API для создания объектов, используемых в наших тестах.

Мы можем создать тестовый класс построителя данных, который создает новые объекты RegistrationForm , выполнив следующие действия:

  1. Создайте класс RegistrationFormBuilder .
  2. Добавьте поле RegistrationForm в созданный класс. Это поле содержит ссылку на созданный объект.
  3. Добавьте конструктор по умолчанию в созданный класс и реализуйте его, создав новый объект RegistrationForm .
  4. Добавьте методы, которые используются для установки значений свойств созданного объекта RegistrationForm . Каждый метод устанавливает значение свойства, вызывая правильный метод установки и возвращает ссылку на объект RegistrationFormBuilder . Помните, что имена методов этих методов могут создавать или разрушать наш DSL .
  5. Добавьте метод build () в созданный класс и реализуйте его, возвращая созданный объект RegistrationForm .

Исходный код нашего класса построителя тестовых данных выглядит следующим образом:

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
public class RegistrationFormBuilder {
 
    private RegistrationForm registration;
 
    public RegistrationFormBuilder() {
        registration = new RegistrationForm();
    }
 
    public RegistrationFormBuilder email(String email) {
        registration.setEmail(email);
        return this;
    }
 
    public RegistrationFormBuilder firstName(String firstName) {
        registration.setFirstName(firstName);
        return this;
    }
 
    public RegistrationFormBuilder lastName(String lastName) {
        registration.setLastName(lastName);
        return this;
    }
 
    public RegistrationFormBuilder isSocialSignInViaSignInProvider(SocialMediaService signInProvider) {
        registration.setSignInProvider(signInProvider);
        return this;
    }
 
    public RegistrationForm build() {
        return registration;
    }
}

После того, как мы изменили наш модульный тест для использования нового класса построителя тестовых данных, его исходный код выглядит следующим образом (соответствующая часть выделена):

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
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;
 
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
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;
 
@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {
 
    private static final String REGISTRATION_EMAIL_ADDRESS = "[email protected]";
    private static final String REGISTRATION_FIRST_NAME = "John";
    private static final String REGISTRATION_LAST_NAME = "Smith";
    private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;
    private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;
 
    private RepositoryUserService registrationService;
 
    @Mock
    private PasswordEncoder passwordEncoder;
 
    @Mock
    private UserRepository repository;
 
    @Before
    public void setUp() {
        registrationService = new RepositoryUserService(passwordEncoder, repository);
    }
 
 
    @Test
    public void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException {
        RegistrationForm registration = new RegistrationFormBuilder()
            .email(REGISTRATION_EMAIL_ADDRESS)
            .firstName(REGISTRATION_FIRST_NAME)
            .lastName(REGISTRATION_LAST_NAME)
            .isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER)
            .build();
 
        when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);
 
        when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {
            @Override
            public User answer(InvocationOnMock invocation) throws Throwable {
                Object[] arguments = invocation.getArguments();
                return (User) arguments[0];
            }
        });
 
        User createdUserAccount = registrationService.registerNewUserAccount(registration);
 
        assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());
        assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());
        assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());
        assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());
        assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());
        assertNull(createdUserAccount.getPassword());
 
        verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);
        verify(repository, times(1)).save(createdUserAccount);
        verifyNoMoreInteractions(repository);
        verifyZeroInteractions(passwordEncoder);
    }
}

Как мы видим, сборщики тестовых данных имеют следующие преимущества:

  • Код, который создает новые объекты RegistrationForm, легко читать и писать. Я большой поклонник свободно работающих API и считаю, что этот код красив и элегантен.
  • Шаблон компоновщика гарантирует, что изменение, обнаруженное в наших тестовых данных, больше не является проблемой, поскольку мы можем просто добавить новые методы в класс построителя тестовых данных.
  • Конфигурация нашего фиктивного объекта и наших утверждений легко читаются, потому что константы видны в нашем методе тестирования, а наш DSL подчеркивает значение каждого значения свойства.

Итак, мы должны использовать шаблон строителя для всего?

НЕТ!

Мы должны использовать тестовые сборщики данных только тогда, когда это имеет смысл. Другими словами, мы должны использовать их, когда:

  1. Мы установили больше, чем несколько значений свойств.
  2. Наши тестовые данные имеют много вариаций.

Шаблон строителя — идеальный выбор, если выполняется одно из этих условий. Причина этого заключается в том, что мы можем создать предметно-ориентированный язык , назвав сеттер-подобные методы класса построителя. Это облегчает чтение и запись наших тестов, даже если бы мы создали много разных объектов и установили много значений свойств.

В этом сила патента строителя.

Если вы хотите узнать больше о свободно распространяемых API, вам следует прочитать следующие статьи:

Это все на сегодня. Давайте продолжим и подведем итог тому, что мы узнали из этого блога.

Резюме

Мы узнали, почему создавать объекты в тестовом методе — это плохая идея, используя ключевое слово new , и мы узнали о двух разных способах создания объектов, которые используются в наших тестах.

Чтобы быть более конкретным, этот пост научил нас трем вещам:

  • Неправильно создавать необходимые объекты в методе test с помощью ключевого слова new, потому что оно делает наши тесты грязными и трудными для чтения.
  • Если нам нужно установить только несколько значений свойств, и наши тестовые данные не имеют большой разброс, мы должны создать требуемый объект, используя фабричный метод.
  • Если нам нужно установить много значений свойств и / или наши тестовые данные сильно различаются, мы должны создать требуемый объект с помощью построителя тестовых данных.