Как бы вы применили модульное тестирование в этой ситуации?

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

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

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

До сих пор я придумал такой тест (есть и другие тесты, которые являются «правильными» и проверяют и другие вещи)

[Test]
public void _WriteToSmartCard()
{
 //start print job
 printer = new DataCardPrinter();
 reader = new SCMSmartCardReader();
 di = DataCardPrinter.InitializeDI();
 printer.CreateHDC();
 Assert.AreNotEqual(printer.Hdc, 0, "Creating HDC Failed");
 Assert.Greater(di.cbSize, 0);

 int res = ICE_API.SetInteractiveMode(printer.Hdc, true);
 Assert.Greater(res, 0, "Interactive Mode Failed");

 res = ICE_API.StartDoc(printer.Hdc, ref di);
 Assert.Greater(res, 0, "Start Document Failed");

 res = ICE_API.StartPage(printer.Hdc);
 Assert.Greater(res, 0, "Start Page Failed");

 res = ICE_API.RotateCardSide(printer.Hdc, 1);
 Assert.Greater(res, 0, "RotateCardSide Failed");

 res = ICE_API.FeedCard(printer.Hdc, ICE_API.ICE_SMARTCARD_FRONT + ICE_API.ICE_GRAPHICS_FRONT);
 Assert.Greater(res, 0, "FeedCard Failed");

 bool bRes = reader.EstablishContext();
 Assert.True(bRes, "EstablishContext Failed");

 bRes = reader.ConnectToCard();
 Assert.True(bRes, "Connect Failed");

 bRes = reader.WriteToCard("123456");
 Assert.True(bRes, "Write To Card Failed");

 string read = reader.ReadFromCard();
 Assert.AreEqual("123456", read, "Read From Card Failed");

 bRes = reader.DisconnectFromCard();
 Assert.True(bRes, "Disconnect Failde");

 res = ICE_API.SmartCardContinue(printer.Hdc, ICE_API.ICE_SMART_CARD_GOOD);
 Assert.Greater(res, 0, "SmartCardContinue Failed");

 res = ICE_API.EndPage(printer.Hdc);
 Assert.Greater(res, 0, "End Page Failed");

 res = ICE_API.EndDoc(printer.Hdc);
 Assert.Greater(res, 0, "End Document Failed");
}

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


person Evgeny    schedule 24.07.2009    source источник
comment
Есть ли шанс получить доступ к оболочке С#? Мы делаем то же самое, но прибегаем к коду VB6 и C++. Хотелось бы иметь возможность делать все в лучшей среде IDE и структуре.   -  person fulvio    schedule 22.08.2012


Ответы (3)


Ваш тестовый код часто называют интеграционным тестом. Короче говоря, интеграционные тесты часто определяются как тесты, которые проверяют интеграцию между компонентами системы. В то время как, как упоминает Дэвид Рейс, модульные тесты часто проверяют отдельные методы.

Оба класса тестов полезны. Интеграционные тесты, такие как ваши, проверяют систему от начала до конца, чтобы убедиться, что все работает вместе. Но они медленные и часто имеют внешние зависимости (например, кард-ридер). Юнит-тесты меньше по размеру, быстрее и очень целенаправленны, но трудно увидеть лес за деревьями, если все, что у вас есть, — это модульные тесты.

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

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

Предположим, ваш метод чтения выглядит примерно так (в псевдокоде)

boolean WriteToCard(String data){
   // do something to data here
   return ICE_API.WriteToCard(ICE_API.SOME_FLAG, data)
}

Ну, вы должны быть в состоянии изменить это на что-то вроде:

    ICE_API api = null

    ICE_API setApi(ICE_API api) {
         this.api = api
    }

    ICE_API getApi() {
      if (api == null) {
        api = new ICE_API()
      }
    }

    boolean WriteToCard(String data){
        // do something to data here    
        return getApi().WriteToCard(ICE_API.SOME_FLAG, data)
    }

Затем в вашем тесте для WriteToCard в настройках вы должны сделать

void setup()
  _mockAPI = new Mock(ICE_API)
  reader.setApi(_mockAPI)

void testWriteToCard()
  reader.writeToCard("12345")
   // assert _mockAPI.writeToCard was called with expected data and flags.
person case nelson    schedule 24.07.2009
comment
Извините, я не знаю С#, у него могут быть лучшие способы сделать DI. Кроме того, в java выполнение DI со статическими методами отстойно, вы можете обернуть статические вызовы в метод, а затем в своем тесте заглушить эти методы-оболочки для выполнения ваших утверждений. - person case nelson; 24.07.2009
comment
Насмешки здесь не кажутся полезными: я не могу проверить запись на карту, если я «издеваюсь» над позиционированием карты. Карта должна быть там физически. Поэтому я полагаюсь на то, что принтер включен, карта правильно подается и т. д., прежде чем я смогу действительно протестировать «WriteToCard». Думаю, я назову этот тест интеграционным тестом и отделю его от остальных. - person Evgeny; 30.07.2009

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

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

Для модульного тестирования я предпочитаю использовать сайт Миско Хевери. Надеюсь, поможет!

person David Reis    schedule 24.07.2009
comment
Вот о чем был мой вопрос - на самом деле это не модульный тест! У меня есть другие тесты, которые больше похожи на модульные тесты, т.е. , 0x31, 0x32, 0x33, 0x34, 0x35, 0x36 }; byte[] result = reader.StringToHexArray(1234512345123456zzzzzzzzzzzzzzzzzzzzzzzzzzzz); Assert.AreEqual(ожидаемый результат, ошибка преобразования длинной строки в байтовый массив); } Но большую разделить непросто Спасибо за ссылку, кстати, очень полезная - person Evgeny; 30.07.2009

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

Один из способов исправить это — создать процедуру инициализации теста (с использованием атрибута [SetUp] в вашем классе [TestFixture]), которая приводит систему в известное состояние перед выполнением одного теста.

Также обратите внимание, что этот сценарий не совсем подходит для модульного тестирования, поскольку он требует потенциальных ручных действий вне программного обеспечения. Модульные тесты по своей сути лучше подходят для тестирования программных модулей, которые не взаимодействуют ни с чем невоспроизводимым. Возможно, вы захотите сделать операции над API-интерфейсом читателя абстрактными (создав интерфейс для необходимых операций и класс, который передает эти вызовы фактическому API), а затем вы можете использовать фиктивный объект, чтобы притвориться reader, чтобы вы могли протестировать основную логику вашего класса (классов), не полагаясь на аппаратное обеспечение.

Затем вы можете реализовать тестирование фактического реального API либо в модульном тесте, либо во что-то еще, что требует минимального человеческого взаимодействия... в основном вы будете инкапсулировать человека в свой процесс тестирования;)

person jerryjvl    schedule 24.07.2009