Вопросы о Philisophical о разработке через тестирование

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

  • ЦП, поскольку необходимые операции могут быть легче или более трудными на различных типах ЦП
  • системное ядро как различные ядра, должен будет выполнить различные операции на каждом переключателе

, Другие факторы включают, как переключатель происходит. Переключатель может произойти, когда

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

  2. поток был вытеснен. Это происходит, когда другой поток требуется процессорное время и имеет более высокий приоритет. Например, поток, который обрабатывает мышь/ввод с клавиатуры, может быть таким потоком. Неважно, то, что распараллеливает , владеет ЦП прямо сейчас, когда пользователь вводит что-то или нажимает что-то, он не хочет ожидать, пока квант времени текущих потоков не был израсходован полностью, он хочет видеть, что система сразу реагирует. Таким образом некоторые системы заставят текущий поток сразу остановиться и управление возвратом к некоторому другому потоку с более высоким приоритетом.

  3. потоку больше не требуется процессорное время, потому что это блокируется на некоторой операции или просто названное сном () (или подобное), чтобы прекратить работать.

Эти 3 сценария могли бы иметь различное время переключения потока в теории. Например, я ожидал бы, что последний будет самым медленным, так как вызов для сна () означает, что ЦП отдан к ядру, и ядро должно установить призыв к действию, который удостоверится, что поток разбужен, после приблизительно количество времени это запросило спать, это тогда должно вынуть поток из процесса планирования, и как только поток разбужен, это должно добавить поток снова к процессу планирования. Все, что они погружают, займут некоторое количество времени. Таким образом, фактический вызов сна мог бы быть более длинным, чем время, которое требуется для переключения на другой поток.

я думаю, хотите ли Вы знать наверняка, необходимо сравнить. Проблема состоит в том, что обычно необходимо будет или поместить потоки для сна, или необходимо синхронизировать их использующий взаимные исключения. Сон или Блокировка/Разблокирование взаимных исключений имеют самостоятельно издержки. Это означает, что Ваш сравнительный тест будет включать эти издержки также. Не имея мощного профилировщика, трудно позже сказать, сколько процессорного времени использовалось для фактического переключателя и сколько для sleep/mutex-call. С другой стороны, в реальном сценарии, Ваши потоки будут или спать или синхронизироваться через блокировки также. Сравнительный тест, который просто измеряет время контекстного переключения, искусственно сравнительный тест, поскольку это не моделирует реального сценария. Сравнительные тесты намного более "реалистичны", если они базируются на реальных сценариях. Из какого использование является сравнительным тестом GPU, который говорит мне, что мой GPU может в теории обрабатывать 2 миллиарда полигонов в секунду, если этот результат никогда не может достигаться в реальном 3D приложении? Не было бы намного более интересно знать, сколько полигонов реальное 3D приложение может иметь дескриптор GPU секунда?

, К сожалению, я не знаю ничего из программирования Windows. Я мог записать приложение для Windows в Java или возможно в C#, но C/C++ в Windows заставляет меня кричать. Я могу только предложить Вам некоторый исходный код для POSIX.

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/time.h>
#include <unistd.h>

uint32_t COUNTER;
pthread_mutex_t LOCK;
pthread_mutex_t START;
pthread_cond_t CONDITION;

void * threads (
    void * unused
) {
    // Wait till we may fire away
    pthread_mutex_lock(&START);
    pthread_mutex_unlock(&START);

    pthread_mutex_lock(&LOCK);
    // If I'm not the first thread, the other thread is already waiting on
    // the condition, thus Ihave to wake it up first, otherwise we'll deadlock
    if (COUNTER > 0) {
        pthread_cond_signal(&CONDITION);
    }
    for (;;) {
        COUNTER++;
        pthread_cond_wait(&CONDITION, &LOCK);
        // Always wake up the other thread before processing. The other
        // thread will not be able to do anything as long as I don't go
        // back to sleep first.
        pthread_cond_signal(&CONDITION);
    }
    pthread_mutex_unlock(&LOCK); //To unlock
}

int64_t timeInMS ()
{
    struct timeval t;

    gettimeofday(&t, NULL);
    return (
        (int64_t)t.tv_sec * 1000 +
        (int64_t)t.tv_usec / 1000
    );
}


int main (
    int argc,
    char ** argv
) {
    int64_t start;
    pthread_t t1;
    pthread_t t2;
    int64_t myTime;

    pthread_mutex_init(&LOCK, NULL);
    pthread_mutex_init(&START, NULL);   
    pthread_cond_init(&CONDITION, NULL);

    pthread_mutex_lock(&START);
    COUNTER = 0;
    pthread_create(&t1, NULL, threads, NULL);
    pthread_create(&t2, NULL, threads, NULL);
    pthread_detach(t1);
    pthread_detach(t2);
    // Get start time and fire away
    myTime = timeInMS();
    pthread_mutex_unlock(&START);
    // Wait for about a second
    sleep(1);
    // Stop both threads
    pthread_mutex_lock(&LOCK);
    // Find out how much time has really passed. sleep won't guarantee me that
    // I sleep exactly one second, I might sleep longer since even after being
    // woken up, it can take some time before I gain back CPU time. Further
    // some more time might have passed before I obtained the lock!
    myTime = timeInMS() - myTime;
    // Correct the number of thread switches accordingly
    COUNTER = (uint32_t)(((uint64_t)COUNTER * 1000) / myTime);
    printf("Number of thread switches in about one second was %u\n", COUNTER);
    return 0;
}

Вывод

Number of thread switches in about one second was 108406

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

18
задан Bill 19 September 2009 в 01:43
поделиться

8 ответов

Как вы справляетесь с большими изменениями?

По мере необходимости.

Иногда рефакторинги имеют большую поверхность, но тривиальны в деталях. Это можно сделать довольно большими шагами. Приложить слишком много усилий для их разбиения будет напрасной тратой.

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

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

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

Пробное тестирование

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

Весь имитационный подход можно рассматривать как модель коммуникации, подобную модели OSI . Когда слой X получает вызов с параметром x, он вызывает слой Z с параметрами a и b. В вашем тесте указывается этот протокол связи.

Насколько может быть полезен имитационный тест, проверьте с их помощью как можно меньше функций. Лучшим вариантом являются тесты на основе состояний: установка приспособлений, вызов тестируемой системы, проверка состояния тестируемой системы и чисто функциональные (как в функциональном программировании) тесты: вызов с x возвращает a.

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

Интеграционные тесты не позволяют протестировать вашу систему. Их следует использовать только для тестирования тех аспектов системы, которые могут выйти из строя при интеграции нескольких устройств. Если вы попытаетесь протестировать свою систему с помощью интеграционных тестов, вы попадете в казино перестановок.

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

Базы данных немного мутят воду. Вы не можете имитировать базу данных, если не собираетесь заново реализовывать поведение каждой базы данных, которую хотите поддерживать. Но это не модульный тест, поскольку вы интегрируете внешнюю систему. Я примирился с этой концептуальной проблемой и считаю DAO и базу данных одним неразделимым элементом, который можно протестировать с помощью подхода к тестированию на основе состояний. (К сожалению, этот модуль ведет себя иначе, когда у него есть день оракула, по сравнению с днем ​​mysql. И он может сломаться посередине и сказать вам, что не может разговаривать сам с собой.)

4
ответ дан 30 November 2019 в 09:28
поделиться

При тестировании кода графического интерфейса или базы данных с издевается, что ты на самом деле тестируешь? Моки созданы, чтобы возвращать ответь ты хочешь, так откуда ты знаешь что ваш код будет работать с реальная база данных? Что это преимущество автоматизированных тестов для этого Такие вещи? Повышает уверенность немного, но а) это не дает вам такой же уровень уверенности, что и завершить модульный тест, и b) чтобы в определенной степени, разве вы не просто проверка того, что ваши предположения работают с вашим кодом, а не с код работает с БД или графическим интерфейсом?

Это мой подход: для уровня доступа к базе данных (DAL) я не использую mock для моего модульного теста. Вместо этого я запускаю тесты в реальной базе данных, хотя и другой, чем производственная база данных. В этом смысле можно сказать, что я не запускаю модульный тест для базы данных. Для приложений NHibernate я поддерживаю две базы данных с одинаковой схемой, но с разными типами ( ORM упрощает это). Я использую sqlite для автоматического тестирования и настоящую базу данных MySQL или SQL-сервера для специального тестирования.

Только однажды я использовал mock для модульного тестирования DAL; и тогда я использовал строго типизированный набор данных в качестве ORM (большая ошибка!). Я сделал это так, чтобы Typemock вернул мне фиктивную копию полной таблицы, чтобы я мог выполнить на ней select * . Позже, оглядываясь назад, я пожалел, что никогда не делаю этого, но это было давно, и я пожалел, что использовал правильный ORM.

Что касается GUI, возможно модульное тестирование взаимодействия GUI. Я сделал это с помощью паттерна MVP для разделения модели, представления и презентатора. На самом деле для этого типа приложений я тестирую только Presenter и Model, в которых я использую Typemock (или внедрение зависимостей ), чтобы изолировать разные уровни, так что я могу одновременно сосредоточиться только на одном слое. Я не тестирую представление, но я часто тестирую Presenter (где происходит большая часть взаимодействия и ошибок).

3
ответ дан 30 November 2019 в 09:28
поделиться

Мои 2 цента ...

  1. если ваши тесты ломаются из-за переключения типа синтаксического анализатора XML - это означает, что тесты хрупкие . Тесты должны указывать что, а не как . Это означает, что в этом случае тесты каким-то образом знают, что вы используете механизм синтаксического анализа SAX (деталь реализации); чего они не должны. Устраните эту проблему, и вам станет лучше с крупными изменениями.
  2. Когда вы абстрагируетесь от графических интерфейсов или макетов от тестов через интерфейс, вы гарантируете, что ваш испытуемый, который использует макеты (как двойники для реальных сотрудников), работает как предполагалось. Вы можете изолировать ошибки в своем коде от ошибок в ваших соавторах. Моки помогают ускорить выполнение набора тестов. Вам также нужны тесты, которые подтверждают, что ваш реальный соавтор также соответствует интерфейсу И проверяет, что ваши настоящие соавторы «подключены» правильно.
2
ответ дан 30 November 2019 в 09:28
поделиться
  1. Как вы справляетесь с большими изменениями?

    • Пошаговое руководство. Я работал над довольно большим количеством нетривиальных программ и всегда мог разбить вещи на небольшие изменения (требующие часов, а может быть, дней). Например, при переписывании веб-сайта 30Mpv приходилось делать по одной странице за раз - это переход с одного языка на другой, написание (небольших) тестов по ходу работы, поддержка сайта частыми развертываниями. В другом проекте мы превратили веб-приложение с графическим интерфейсом в автономный сервер. Это включало в себя множество мелких шагов через месяц или два работы, а затем в конечном итоге отбрасывание большей части веб-кода. Но мы смогли сохранить все тесты в рабочем состоянии. Мы сделали это не потому, что пытались что-то доказать, а потому, что это был лучший способ повторно использовать код и тесты.

    • Большим шагам могут помочь тесты с более широким охватом. Например, ваш пример SAX-> DOM будет иметь тест интеграции высокого уровня, который подтвердит окончательное поведение. Однако, когда я делал что-то подобное, я писал гораздо более мелкие поведенческие тесты для различных типов обработки узлов, и преобразование их можно было выполнять один за другим.

  2. При тестировании графического интерфейса или кода базы данных с помощью макетов, кто вы на самом деле тестирование?

    • Вы всегда должны быть уверены, что пишете ценные тесты. Это может быть сложно. Легко, даже если вы думаете, написать несколько довольно избыточных тестов.
    • Моки не имеют смысла, когда вы пытаетесь тестировать запросы к базе данных. Они полезны, когда вы пытаетесь "имитировать" уровень ниже того, что вы тестируете ... Для тестирования запросов к базе данных вам необходимо загрузить в базу данных соответствующие приспособления, которые будут проверять то, что вы пытаетесь сделать. Это можно сделать с помощью приспособлений или кода тщательной настройки теста. Это требует некоторых размышлений, поэтому хорошо иметь хорошо спроектированный набор фиксированных данных, которые позволят вам писать хорошие тесты запросов к базе данных, охватывающие как можно больше важных случаев.

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

1
ответ дан 30 November 2019 в 09:28
поделиться

С точки зрения обработки больших изменений ... цель TDD - проверить поведение вашего кода и то, как он взаимодействует со службами, от которых он зависит. Если вы хотели использовать TDD, и вы переходили от парсера DOM к парсеру SAX, и вы сами писали парсер sax, тогда вы должны написать тесты, которые проверяют поведение парсера SAX на основе известного ввода, то есть документа XML. Синтаксический анализатор SAX может зависеть от набора вспомогательных объектов, которые фактически могут быть изначально имитированы для целей тестирования поведения анализатора SAX. Когда вы будете готовы написать код реализации для вспомогательных объектов, вы сможете написать тесты на основе их ожидаемого поведения на основе известных входных данных. В примере парсера SAX вы должны написать отдельные классы для реализации этого поведения, чтобы не мешать существующему коду, который у вас есть, который зависит от парсера DOM. Фактически, вы могли бы создать интерфейс IXMLParser, который реализует парсер DOM и синтаксический анализатор SAX, чтобы вы могли отключать их по своему желанию.

Что касается использования mock-ов или заглушек, то причина, по которой вы используете Mock или Stub заключается в том, что вы не заинтересованы в тестировании внутренней работы макета или заглушки, но заинтересованы в тестировании внутренней работы того, что зависит от макета или заглушки, и именно это вы на самом деле тестируете с точки зрения модуля. Если вы заинтересованы в написании интеграционных тестов, вам следует писать интеграционные тесты, а не модульные тесты. Я считаю, что написание кода в стиле TDD полезно для того, чтобы помочь мне определить структуру и организацию моего кода в соответствии с поведением, которое меня просят предоставить.

Я не знаком ни с какими примерами из практики, но уверен они там.

1
ответ дан 30 November 2019 в 09:28
поделиться

При тестировании графического интерфейса или код базы данных с mocks, что вы на самом деле тестируете?

Обычно я стараюсь разделить свою бизнес-логику, логику отображения и доступ к базе данных. Большинство моих модульных тестов GUI связаны с бизнес-логикой. Вот пример псевдокода:

// Production code in class UserFormController:

void changeUserNameButtonClicked() {
  String newName = nameTextBox.getText();
  if (StringUtils.isEmpty(newName)) {
    errorBox.showError("User name may not be empty !");
  } else {
    User user = engine.getCurrentUser();
    user.name = newName;
    engine.saveUser(user);
  }
}

// Test code in UserFormControllerTest:

void testValidUserNameChange() {
  nameTextBox = createMock(TextBox.class);
  expect(nameTextBox.getText()).andReturn("fred");
  engine = createMock(Engine.class);
  User user = createMock(user);
  user.setName("fred");
  expectLastCall();
  expect(engine.getCurrentUser()).andReturn(user);
  engine.saveUser(user);
  expectLastCall();
  replay(user, engine, nameTextBox);

  UserFormController controller = new UserFormController();
  controller.setNameTextBox(nameTextBox);
  controller.setEngine(engine);
  controller.changeUserNameButtonClicked();  

  verify(user, engine, nameTextBox);
}

void testEmptyUserNameChange() {
  nameTextBox = createMock(TextBox.class);
  errorBox = createMock(ErrorBox.class);
  expect(nameTextBox.getText()).andReturn("");
  errorBox.showError("User name may not be empty !");
  expectLastCall();
  replay(nameTextBox, errorBox);

  UserFormController controller = new UserFormController();
  controller.setNameTextBox(nameTextBox);
  controller.setErrorBox(errorBox);
  controller.changeUserNameButtonClicked();  

  verify(nameTextBox, errorBox);
}

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

Но в конечном итоге, как вы сказали, эти модульные тесты не дадут вам полной картины. Чтобы получить это, вам нужно сделать то, что предлагали другие: создать настоящую базу данных с «золотым набором» данных и запустить для нее интеграционные / функциональные тесты. Но, IMO, такие тесты выходят за рамки TDD,

1
ответ дан 30 November 2019 в 09:28
поделиться

Что касается угла базы данных, как упомянул Нгу Сун Хуэй, вы должны (ИМХО) использовать что-то вроде DBUnit , который настроит базу данных в известной конфигурации (так вы можете проверить ожидаемые результаты), но он использует реальную базу данных, которую будет использовать реальное приложение.

Для больших изменений я бы рекомендовал создать ветвь и позволить тестам завершиться ошибкой. Это даст вам список TODO областей, которые необходимо изменить, и можно утверждать, что именно здесь TDD действительно выделяется, даже больше, чем с небольшими изолированными функциями.

0
ответ дан 30 November 2019 в 09:28
поделиться

Обработка больших изменений

По моему опыту, они относительно нечасты. Когда они действительно случаются, обновление тестов становится незначительной проблемой. Уловка состоит в том, чтобы выбрать правильную степень детализации для тестов. Если вы протестируете общедоступный интерфейс, обновления будут происходить быстро. Если вы протестируете частный код реализации, переход с SAX на парсер DOM займет много времени, и вы почувствуете dom. ; -)

Тестирование кода графического интерфейса

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

Тестирование кода базы данных

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

Очень возможно злоупотребление mock-объектами и заглушками, написанием тестов, которые ничего не делают, кроме тестовых mock-объектов. Чтобы написать хорошие тесты, нужен большой опыт; Я все еще учусь.

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

Непрерывная интеграция действительно помогает при тестировании, поскольку она обеспечивает выполнение тестов и делает неработающие тесты видимыми. Настоятельно рекомендуется.

РЕДАКТИРОВАТЬ: Честно говоря, во многих случаях у меня возникают проблемы с разделением кода доступа к данным, и в итоге я использую базы данных тестовой колоды. Даже подобные интеграционные тесты оказались ценными, хотя они медленнее и хрупче. Как я уже сказал, я все еще учусь.

0
ответ дан 30 November 2019 в 09:28
поделиться
Другие вопросы по тегам:

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