как реализовать / собрать / создать базу данных в памяти для моего модульного теста - PullRequest
2 голосов
/ 29 апреля 2010

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

Итак, реализован Unity для внедрения поддельного слоя базы данных, но я, конечно, хочу сохранить некоторые данные, и основное мнение было: «создать базу данных в памяти»

Но что это такое / как мне это реализовать?

Основной вопрос: я думаю, что мне нужно подделать слой базы данных, но разве это не заставляет меня самостоятельно создавать «простую базу данных» или: как я могу сделать это простым и не перестраивать Sql Server только для моих модульных тестов: )

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

Michel

Текущая ситуация, которую я видел на этом клиенте, заключается в том, что testdata содержится в XML-файлах, и существует «поддельный» слой базы данных, который соединяет все xml-файлы вместе. Для реальной базы данных мы используем структуру сущностей, и это работает очень просто. И теперь, в слое 'fake', я должен создать все виды классов для загрузки, сохранения, сохранения и т. Д. Данных. Звучит странно, что в фальшивом слое так много работы, а в реальном слое так мало.

Надеюсь, все это имеет смысл:)

EDIT: так что я знаю, что должен создать отдельный слой базы данных для моего модульного теста, но как мне его реализовать?

Ответы [ 5 ]

3 голосов
/ 29 апреля 2010

Определите интерфейс для вашего уровня доступа к данным и имейте (по крайней мере) две его реализации:

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

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

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

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

Как это может выглядеть

Если вы просто загружаете и сохраняете объекты по идентификатору, то вы можете иметь интерфейс и реализации, подобные (в псевдокоде Java-esque; я не очень разбираюсь в asp.net):

interface WidgetDatabase {
    Widget loadWidget(int id);
    saveWidget(Widget w);
    deleteWidget(int id);
}

class SqlWidgetDatabase extends WidgetDatabase {
    Connection conn;

    // connect to database server of choice
    SqlWidgetDatabase(String connectionString) { conn = new Connection(connectionString); }

    Widget loadWidget(int id) {
        conn.executeQuery("SELECT * FROM widgets WHERE id = " + id);
        Widget w = conn.fetchOne();
        return w;
    }

    // more methods that run simple sql queries...
}

class MemeoryWidgetDatabase extends WidgetDatabase {
    Set widgets;

    MemoryWidgetDatabase() { widgets = new Set(); }

    Widget loadWidget(int id) {
        for (Widget w: widgets)
            if (w.getId() == id)
                return w;
        return null;
    }

    // more methods that find/add/delete a widget in the "widgets" set...
}

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

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

2 голосов
/ 29 апреля 2010

Почему бы вам не использовать насмешливый фреймворк (например, moq или moh rhino mocks)? Если вы получаете доступ к своим данным через интерфейс, вы можете смоделировать этот интерфейс и указать, что вы хотите вернуть в каждом тесте. Другой подход состоит в том, чтобы иметь отдельную среду для целей тестирования с «реальной» базой данных, в которой вы выполняете тесты, прежде чем использовать свой код для производственной среды.

2 голосов
/ 29 апреля 2010

я использовал Sqlite для модульного теста в качестве поддельного DB

1 голос
/ 29 апреля 2010

Ухххх ...... Если вы храните все свои тестовые данные в файлах XML. Вы только что изменили одну базу данных на другую. Это не в базе данных . В PHP вы бы использовали что-то вроде этого.

class MemoryProductDB {

    private $products;

    function MemoryProductDB() {
        $this->products = array();
    }

    public function find($index) {
        return $this->products[$index];
    }

    public function save($product) {
        $this->products[$product['index']] = $product;
    }
}

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

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

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

Это называется гранулярностью абстракции.

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

[править] Вы никогда не должны тестировать свои контроллеры модульной моделью и imdb. Код установки для IMDB будет иметь много трения. Вместо этого при модульном тестировании контроллера вам необходимо выполнить модульное тестирование фиктивной, заглушки, поддельной модели. Лучшее использование imdb - во время интеграционного теста или при модульном тестировании модели. Разве IMDB не подделка?

Мой сценарий:

  1. В моем клиенте я использую плагин для таблицы. DataTables . Обработка на стороне сервера.
  2. Клиент GET запрашивает элементы в таблице product.get(5,10). Возвращаемые данные будут закодированы в формате JSON.

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

public function testSkuTable() {
    $skus = array(
            array('id' => '1', 'data' => 'data1'),
            array('id' => '2', 'data' => 'data2'),
            array('id' => '3', 'data' => 'data3'));

    $names = array(
            'id',
            'data');
    $start_row = $this->parameters['start_row'];
    $num_rows = $this->parameters['num_rows'];
    $sort_col = $this->parameters['sort_col'];
    $search = $this->parameters['search'];
    $requestSequence = $this->parameters['request_sequence'];
    $direction = $this->parameters['dir'];
    $filterTotals = 1;
    $totalRecords = 1;

    $this->gateway->expects($this->once())
            ->method('names')
            ->with($this->vendor)
            ->will($this->returnValue($names));

    $this->gateway->expects($this->once())
            ->method('skus')
            ->with($this->vendor, $names, $start_row, $num_rows, $sort_col, $search, $direction)
            ->will($this->returnValue($skus));

    $this->gateway->expects($this->once())
            ->method('filterTotals')
            ->will($this->returnValue($filterTotals));

    $this->gateway->expects($this->once())
            ->method('totalRecords')
            ->with($this->vendor)
            ->will($this->returnValue($totalRecords));

    $expectJson = '{"sEcho": '.$requestSequence.', "iTotalRecords": '.$totalRecords.', "iTotalDisplayRecords": '.$filterTotals.', "aaData": [ ["1","data1"],["2","data2"],["3","data3"]] }';
    $actualJson = $this->skusModel->skuTable($this->vendor, $this->parameters);

    $this->assertEquals($expectJson, $actualJson);
}

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

public function skuTable($vendor, $parameterList) {
    $startRow = $parameterList['start_row'];
    $numRows = $parameterList['num_rows'];
    $sortCols = $parameterList['sort_col'];
    $search = $parameterList['search'];
    if($search == null) {
        $search = "";
    }
    $requestSequence = $parameterList['request_sequence'];
    $direction = $parameterList['dir'];

    $names = $this->propertyNames($vendor);
    $skus = $this->skusList($vendor, $names, $startRow, $numRows, $sortCols, $search, $direction);
    $filterTotals = $this->filterTotals($vendor, $names, $startRow, $numRows, $sortCols, $search, $direction);
    $totalRecords = $this->totalRecords($vendor);

    return $this->buildJson($requestSequence, $totalRecords, $filterTotals, $skus, $names);
}

Первая часть метода разбивает отдельные параметры из $parameterList, который я получаю из запроса get. Остальные звонки на шлюз. Вот один из методов:

public function skusList($vendor, $names, $start_row, $num_rows, $sort_col, $search, $direction) {
    return $this->skusGateway->skus($vendor, $names, $start_row, $num_rows, $sort_col, $search, $direction);
}
1 голос
/ 29 апреля 2010

Я использовал в памяти Sqlite для моих модульных тестов, это действительно полезно

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