Как выполнить модульное тестирование метода Create в MVC5? [Дубликат]

UPDATE2: более общая векторная функция, которая будет работать для нескольких normal и нескольких столбцов list

def explode(df, lst_cols, fill_value=''):
    # make sure `lst_cols` is a list
    if lst_cols and not isinstance(lst_cols, list):
        lst_cols = [lst_cols]
    # all columns except `lst_cols`
    idx_cols = df.columns.difference(lst_cols)

    # calculate lengths of lists
    lens = df[lst_cols[0]].str.len()

    if (lens > 0).all():
        # ALL lists in cells aren't empty
        return pd.DataFrame({
            col:np.repeat(df[col].values, lens)
            for col in idx_cols
        }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \
          .loc[:, df.columns]
    else:
        # at least one list in cells is empty
        return pd.DataFrame({
            col:np.repeat(df[col].values, lens)
            for col in idx_cols
        }).assign(**{col:np.concatenate(df[col].values) for col in lst_cols}) \
          .append(df.loc[lens==0, idx_cols]).fillna(fill_value) \
          .loc[:, df.columns]

Демо:

Несколько list столбцов - все list столбцы должны иметь одинаковые элементы из каждой строки:

In [36]: df
Out[36]:
   aaa  myid        num          text
0   10     1  [1, 2, 3]  [aa, bb, cc]
1   11     2     [1, 2]      [cc, dd]
2   12     3         []            []
3   13     4         []            []

In [37]: explode(df, ['num','text'], fill_value='')
Out[37]:
   aaa  myid num text
0   10     1   1   aa
1   10     1   2   bb
2   10     1   3   cc
3   11     2   1   cc
4   11     2   2   dd
2   12     3
3   13     4

Настройка:

df = pd.DataFrame({
 'aaa': {0: 10, 1: 11, 2: 12, 3: 13},
 'myid': {0: 1, 1: 2, 2: 3, 3: 4},
 'num': {0: [1, 2, 3], 1: [1, 2], 2: [], 3: []},
 'text': {0: ['aa', 'bb', 'cc'], 1: ['cc', 'dd'], 2: [], 3: []}
})

Столбец CSV:

In [46]: df
Out[46]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [47]: explode(df.assign(var1=df.var1.str.split(',')), 'var1')
Out[47]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

используя этот небольшой трюк, мы можем преобразовать CSV-подобный столбец в столбец list:

In [48]: df.assign(var1=df.var1.str.split(','))
Out[48]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

UPDATE: общий векторный подход (будет работать и для нескольких столбцов):

Original DF:

In [177]: df
Out[177]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

Решение:

сначала давайте преобразуем строки CSV в списки:

In [178]: lst_col = 'var1' 

In [179]: x = df.assign(**{lst_col:df[lst_col].str.split(',')})

In [180]: x
Out[180]:
              var1  var2 var3
0        [a, b, c]     1   XX
1  [d, e, f, x, y]     2   ZZ

Теперь мы можем это сделать:

In [181]: pd.DataFrame({
     ...:     col:np.repeat(x[col].values, x[lst_col].str.len())
     ...:     for col in x.columns.difference([lst_col])
     ...: }).assign(**{lst_col:np.concatenate(x[lst_col].values)})[x.columns.tolist()]
     ...:
Out[181]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

OLD answer:

Вдохновленный решением @AFinkelstein , я хотел сделать его более обобщенным, что может быть применено к DF с более чем два столбца и так же быстро, но почти так же быстро, как и решение Апинкельштейна):

In [2]: df = pd.DataFrame(
   ...:    [{'var1': 'a,b,c', 'var2': 1, 'var3': 'XX'},
   ...:     {'var1': 'd,e,f,x,y', 'var2': 2, 'var3': 'ZZ'}]
   ...: )

In [3]: df
Out[3]:
        var1  var2 var3
0      a,b,c     1   XX
1  d,e,f,x,y     2   ZZ

In [4]: (df.set_index(df.columns.drop('var1',1).tolist())
   ...:    .var1.str.split(',', expand=True)
   ...:    .stack()
   ...:    .reset_index()
   ...:    .rename(columns={0:'var1'})
   ...:    .loc[:, df.columns]
   ...: )
Out[4]:
  var1  var2 var3
0    a     1   XX
1    b     1   XX
2    c     1   XX
3    d     2   ZZ
4    e     2   ZZ
5    f     2   ZZ
6    x     2   ZZ
7    y     2   ZZ

54
задан ChaseMedallion 11 January 2012 в 13:08
поделиться

6 ответов

Тест блока управления должен проверять алгоритмы кода в ваших методах действий, а не на вашем уровне данных. Это одна из причин издеваться над этими службами данных. Контроллер ожидает получить определенные значения из репозиториев / сервисов / etc и действовать по-разному, когда получает от них различную информацию.

Вы пишете модульные тесты, чтобы утверждать, что контроллер ведет себя очень определенным образом в очень специфических сценариях / обстоятельствах. Уровень данных - это одна часть приложения, которая предоставляет эти обстоятельства методам управления / действия. Утверждение, что метод службы вызван контроллером, ценен, потому что вы можете быть уверены, что контроллер получает информацию из другого места.

Проверка типа возвращаемой модели просмотра является ценной, поскольку, если возвращается неверный тип viewmodel, MVC будет генерировать исключение во время выполнения. Вы можете предотвратить это в производстве, выполнив единичный тест. Если тест не удался, тогда представление может вызвать исключение в производстве.

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

Ответ на комментарий # 1

При изменении реализации метода-под-тестом вызовы для изменения / удаления метода издевательства нижнего уровня, тогда модульный тест должен также изменение. Однако это не должно происходить так часто, как вы можете подумать.

Типичный рабочий процесс red-green-refactor требует записи ваших модульных тестов перед написанием методов, которые они тестируют. (Это означает, что в течение короткого промежутка времени ваш тестовый код не будет компилироваться, и поэтому многие молодые / неопытные разработчики с трудом принимают красный зеленый рефактор.)

Если вы сначала пишете свои юнит-тесты, вы достигнет точки, когда вы знаете, что контроллеру необходимо получить информацию с более низкого уровня. Как вы можете быть уверены, что он пытается получить эту информацию? Избавьтесь от метода нижнего уровня, который предоставляет информацию, и утверждая, что метод нижнего уровня вызывается контроллером.

Возможно, у меня есть оговорка, когда я использовал термин «изменение реализации». Когда способ действия контроллера & amp; соответствующий модульный тест должен быть изменен для изменения или удаления метода издевательства, вы действительно меняете поведение контроллера. Рефакторинг, по определению, означает изменение реализации без изменения общего поведения и ожидаемых результатов.

Red-green-refactor - это подход обеспечения качества, который помогает предотвратить ошибки и amp; прежде чем они появятся. Обычно разработчики меняют реализацию, чтобы удалить ошибки после их появления. Поэтому, чтобы повторить, случаи, о которых вы беспокоитесь, не должны происходить так часто, как вы думаете.

43
ответ дан danludwig 26 August 2018 в 03:33
поделиться

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

Я предпочитаю тесты интеграции - я начинаю не с конкретного контроллера, а с URL-адреса, и проверяю, что возвращаемая модель имеет правильные значения. С помощью Ivonna тест может выглядеть так:

var response = new TestSession().Get("/Users/List");
Assert.IsInstanceOf<UserListModel>(response.Model);

var model = (UserListModel) response.Model;
Assert.AreEqual(1, model.Users.Count);

Я могу издеваться над доступом к базе данных, но я предпочитаю другой подход: настройте экземпляр в памяти SQLite и воссоздать его с каждым новым тестом вместе с требуемыми данными. Это делает мои тесты достаточно быстрыми, но вместо сложного насмешки я делаю их понятными, например. просто создайте и сохраните экземпляр пользователя, а не издевайтесь над файлом UserService (который может быть деталью реализации).

8
ответ дан Alex 26 August 2018 в 03:33
поделиться

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

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

24
ответ дан Bart 26 August 2018 в 03:33
поделиться

Да, вы должны полностью протестировать базу данных. Время, в которое вы насмехаетесь, меньше, и ценность, которую вы получаете от насмешек, очень меньше (80% вероятных ошибок в вашей системе нельзя отбросить из-за насмешек).

Когда вы проверяете весь путь от контроллера к БД или веб-службе, это не называется модульным тестированием, а интеграционным тестированием. Я лично верю в интеграционное тестирование, а не в модульное тестирование. И я могу успешно выполнить тестовое развитие.

Вот как это работает для нашей команды. Каждый тестовый класс в начале восстанавливает БД и заполняет / сортирует таблицы с минимальным набором данных (например, роли пользователя). На основе потребностей контроллеров мы заполняем БД и проверяем, выполняет ли контроллер его задачу. Это спроектировано таким образом, что поврежденные DB данные, оставленные другими методами, никогда не завершится. За исключением времени, которое нужно выполнить, почти все качества модульного теста (хотя это и есть теория) являются gettable.

В моей карьере было всего 2% ситуаций (или очень редко), когда меня заставили использовать mocks / stubs, так как не удалось создать более реалистичный источник данных. Но во всех других ситуациях интеграционные тесты были возможны.

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

============================================

Отредактировано после комментария ниже: Демо

Интеграционный тест или функциональный тест должен иметь дело непосредственно с БД. Никаких насмешек. Итак, это шаги. Вы хотите проверить getEmployee (). все эти 5 шагов ниже выполняются одним методом испытаний.

  1. Drop DB
  2. Создать БД и заполнить роли и другие данные
  3. Создать запись сотрудника с идентификатором
  4. Использовать это ID и вызов getEmployee ()
  5. Now Assert () / Проверить правильность возвращаемых данных. Это доказывает, что работает getEmployee (). Шаги до 3 требуют, чтобы код использовался только тестовым проектом. Шаг 4 вызывает код приложения. То, что я имел в виду, это создание сотрудника (шаг 2), должно выполняться кодом кода проекта, а не кодом приложения. Если для создания сотрудника существует код приложения (например: CreateEmployee ()), это не должно использоваться. Точно так же, когда мы тестируем CreateEmployee (), тогда код приложения GetEmployee () не должен использоваться. Мы должны иметь тестовый код проекта для извлечения данных из таблицы.

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

Специальный совет: на шаге 5, если getEmployee () возвращает объект employee. Если позднее разработчик удаляет или изменяет имя поля, тест прерывается, потому что поля проверяются. Что делать, если разработчик добавит новое поле позже? И он / она забывает добавить тест для него (утверждать)? Решение состоит в том, чтобы всегда добавлять проверку количества полей. например: Объект Employee имеет 4 поля (Имя, Фамилия, Обозначение, Пол). Таким образом, количество полей объекта сотрудника - 4. И наш тест завершится неудачно из-за подсчета и напомнит разработчику добавить поле подтверждения для вновь добавленного поля. А также наш тестовый код добавит это новое поле в БД и получит его и проверит.

И это отличная статья, в которой обсуждаются преимущества тестирования интеграции над модульным тестированием , потому что «модульное тестирование убивает!» (говорит он)

8
ответ дан Blue Clouds 26 August 2018 в 03:33
поделиться

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

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

1
ответ дан Joachim Isaksson 26 August 2018 в 03:33
поделиться

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

Поэтому в случае методов Controller, возвращающих ActionResults, очень полезно проверить значение возвращаемого ActionResult.

Посмотрите раздел «Создание модульных тестов для контроллеров» ' здесь для некоторых очень четких примеров с использованием Moq.

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

[TestMethod]
public void CreateInvalidContact()
{
    // Arrange
    var contact = new Contact();
    _service.Expect(s => s.CreateContact(contact)).Returns(false);
    var controller = new ContactController(_service.Object);

    // Act
    var result = (ViewResult)controller.Create(contact);

    // Assert
    Assert.AreEqual("Create", result.ViewName);
}
9
ответ дан Loren Paulsen 26 August 2018 в 03:33
поделиться
Другие вопросы по тегам:

Похожие вопросы: