Модульное тестирование драйверов устройств - PullRequest
27 голосов
/ 24 декабря 2009

У меня есть ситуация, когда мне нужно написать несколько модульных тестов для некоторых драйверов устройств для встроенного оборудования. Код довольно старый и большой и, к сожалению, не имеет много тестов. Прямо сейчас единственный вид тестирования, который возможен, это полностью скомпилировать ОС, загрузить ее на устройство, использовать в реальных сценариях и сказать, что «это работает». Нет способа проверить отдельные компоненты.

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

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

Одна вещь, которая приходит мне в голову, - это писать встроенные приложения, работающие в ОС, проверять код драйвера и затем передавать результаты обратно в тестовый комплект. Устройство имеет несколько интерфейсов, которые я могу использовать, чтобы, вероятно, запустить приложение с моего тестового ПК, чтобы я мог использовать код.

Буду очень признателен за любые другие предложения или идеи.


Обновление: хотя это может быть не совсем терминология, когда я говорю «модульное тестирование», я имел в виду возможность тестировать / выполнять код без необходимости компилировать все драйверы OS + и загружать его на устройство. Если бы мне пришлось это сделать, я бы назвал это интеграцией / тестированием системы.

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


Краткое описание

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

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

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

Спасибо всем за ваши ответы.

Ответы [ 6 ]

5 голосов
/ 24 декабря 2009

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

В вашей ситуации на ум приходит несколько подходов:

  • Добавление обработчиков ioctl: каждый код выполняет определенный модульный тест
  • При условной компиляции добавьте main () к драйверу, который проводит функциональные тесты модулей в драйвере и выводит результаты в stdout.
  • Для начальной простоты в отладке, возможно, это можно сделать многоплатформенным, чтобы вам не приходилось отлаживать на целевом оборудовании.
  • Возможно, условный код также может эмулировать устройство в стиле loopback.
4 голосов
/ 25 декабря 2009

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

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

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

Если вы можете подключить ПК к сети, то ПК может помочь с тестированием. Например. если вы пишете драйвер последовательного порта для встроенного устройства, то вы можете:

  • Запись тестового кода для встроенного устройства, которое отправляет различные известные потоки данных.
  • Подключите его к последовательному порту ПК, запустив тестовый код, который проверяет передаваемые потоки данных.
  • То же в другом направлении - ПК отправляет данные; встроенное устройство получает его, проверяет и уведомляет ПК о любых ошибках.
  • Тесты могут выполнять потоковую передачу данных на полной скорости и воспроизводить различные интервалы времени байтов (однажды я обнаружил кремниевую ошибку микроконтроллера UART, которая появлялась только в том случае, если байты отправлялись с задержкой ~ 5 мс между байтами).

Вы можете сделать то же самое с драйвером Ethernet, драйвером Wi-Fi.

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

2 голосов
/ 07 января 2010

У меня было именно это задание всего два месяца назад.

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

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

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

Ключевым моментом здесь является создание трех элементов: контроллера, абстракции и реализации этой абстракции с помощью адаптера. В Cpp, Java или C # вы должны создать базовый класс или интерфейс для представления этой абстракции. Я буду считать, что вы создали интерфейс. Вы разбиваете фрагменты на атомарные операции. Например, вы создаете метод с именем «start» и «add (параметр)» в интерфейсе. Вы помещаете свои фрагменты в адаптер устройства. Контроллер воздействует на адаптер через интерфейс.

Определите фрагменты логики внутри фрагментов, которые вы поместили в адаптер. Затем вам нужно решить, является ли эта логика низкоуровневой (детали обработки протокола и т. Д.) Или же эта логика должна принадлежать контроллеру.

Затем вы можете выполнить тестирование в два этапа: * Имейте простое тестовое приложение панели, которое действует на конкретный адаптер. Это используется для подтверждения того, что адаптер действительно работает. Это начинается, когда вы нажимаете «Пуск». Например, если вы последовательно нажмете «перейти в автономный режим», «передать (192)» и «подключиться к сети», устройство ответит так, как ожидается. Это ваш интеграционный тест.

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

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

Затем вы пишете модульные тесты, чтобы подтвердить, что, например, метод «Add (1)» фактически вызывает «Go offline», затем «Transmit (1)» и затем «Go online» на макете абстракции.

Задача состоит в том, чтобы провести различие между адаптером и контроллером. Что идет куда? То, что мне помогло, - это сначала создать вышеупомянутую тестовую панель, а затем управлять устройством через нее.

Адаптер должен скрывать детали, которые вам придется изменить только при изменении устройства.

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

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

1 голос
/ 26 декабря 2009

У меня была похожая проблема два или три года назад. Я портировал драйвер устройства с VxWorks на Integrity. Мы изменили только зависимые от операционной системы части драйвера, но это был проект, критически важный для безопасности, поэтому все модульные тесты, интеграционные тесты переделаны. Для наших модульных тестов мы использовали инструмент автоматического тестирования под названием LDRA testbed. 99% наших модульных тестов выполняются на машинах Windows с компиляторами Microsoft. Теперь я объясню, как это сделать

Прежде всего, когда вы проводите модульное тестирование, вы тестируете программное обеспечение. Когда вы включаете реальное устройство в свои тесты, вы также тестируете устройство. Иногда могут возникнуть проблемы с оборудованием или документацией к оборудованию. Когда вы разрабатываете программное обеспечение, если вы четко описали поведение каждой функции, очень легко выполнить модульное тестирование, например, подумайте о функции;

readMessageTime(int messageNo, int* time); 
//This function calculates the message location, if the location is valid, 
//it reads    the time information 
address=calculateMessageAddr(messageNo); 
if(address!=NULL) { 
    read(address+TIME_OFFSET,time); 
    return success; 
} 
else { 
return failure; 
} 

Что ж, здесь вы просто тестируете, выполняет ли readMessageTime то, что он должен делать. Вам не нужно проверять, вычисляет ли правильныйMesageAddr правильный результат или read читает правильный адрес. Это ответственность некоторых других модульных тестов. Итак, вам нужно написать заглушки для CalculateMessageAddr и read (функция OS) и проверить, вызывает ли он функции с правильными параметрами. Это тот случай, если вы не обращаетесь к памяти напрямую из вашего драйвера. Вы можете протестировать любой код драйвера без какой-либо ОС или устройства с этим менталитетом.

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

Существует также еще одна проблема, которую разработчики всегда путают в модульном тестировании драйверов, например:

 readMessageTime(int messageNo, int* time); 
 //This function calculates the message location, if the location is valid,
 //it does some jobs to make the device ready to read then 
 //it reads the time information 
 address=calculateMessageAddr(messageNo); 
 if(address!=NULL) { 
      do_smoething(); // Get the device ready to read!    
      do_something_else() // do some other stuff so you can read the result in 3us.
      status=NOT_READY;
      while(status==NOT_READY) // mustn't be longer than 3us.
           status=read(address+TIME_OFFSET,time); 
      return success; 
  } else 
  { 
  return failure; 
  } 

Здесь do_something и do_something_else выполняют некоторые работы на устройстве, чтобы подготовить его к чтению. Разработчики всегда задают себе вопрос: «Что, если устройство не будет готово вечно, а мой код зашёл в тупик?», И они склонны тестировать подобные вещи на устройстве.

Ну, вы должны доверять производителю устройства и техническому автору. Если они говорят, что устройство будет готово через 1-2us, вам не нужно беспокоиться об этом. Если ваш код потерпит неудачу, вы должны сообщить об этом производителю устройства, а не ваша задача - найти обходной путь для решения этой проблемы. Вы видели мою точку зрения?

Надеюсь, это поможет ...

1 голос
/ 24 декабря 2009

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

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

Что касается именования, это можно назвать тестированием компонентов.

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

0 голосов
/ 24 декабря 2009

Словарь

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

Затем вы выходите из модульного тестирования. Может быть, вы могли бы вместо этого использовать одно из этих выражений?

  • Автоматизированное тестирование: тестирование происходит без ввода пользователя (в отличие от Ручное Тестирование).
  • Интеграция тестирование: тестирование нескольких компонентов вместе (в отличие от Unit тестирование).
    В более широком масштабе, если вы тестируете целую систему, а не только несколько компонентов вместе, это называется System testing.

ДОБАВЛЕНО после комментариев и обновлений в вопросе:

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

Преимущества "настоящего" модульного тестирования

С помощью интеграционного (или системного, или компонентного) тестирования, безусловно, интересно получить некоторую обратную связь, например тестовое покрытие. Это, безусловно, полезно сделать.

Но очень трудно (читай "очень дорого") добиться прогресса за какой-то момент, поэтому
Я предлагаю вам использовать дополнительные подходы, такие как добавление некоторых реальных модульных тестов . Зачем? :

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