Ответ 1
На первый взгляд, у вас есть несколько вариантов, в зависимости от того, что вы хотите протестировать, и вашей способности тратить деньги/менять свою базу кода.
В настоящий момент вы эффективно пишете интеграционные тесты. Если база данных недоступна, ваши тесты потерпят неудачу. Это означает, что тесты могут быть медленными, но с положительной стороны, если они пройдут, вы уверены, что код может попасть в базу данных правильно.
Если вы не против попадания в базу данных, минимальное влияние на изменение вашего кода/расходов будет заключаться в том, чтобы вы могли завершить транзакции и проверить их в базе данных. Вы можете сделать это, сделав снимки базы данных и сбросив базу данных каждый тестовый прогон или имея специальную тестовую базу данных и записывая тесты таким образом, чтобы они могли безопасно удалять базу данных снова и снова, а затем проверять. Например, вы можете вставить запись с инкрементированным идентификатором, обновить запись, а затем проверить, что ее можно прочитать. Возможно, вам придется больше раскручиваться, если есть ошибки, но если вы не изменяете код доступа к данным или структуру базы данных, то часто это не должно быть слишком большой проблемой.
Если вы можете потратить немного денег и хотите на самом деле перевести свои тесты на модульные тесты, чтобы они не попали в базу данных, вам следует рассмотреть возможность поиска в TypeMock. Это очень мощная насмешливая структура, которая может сделать некоторые довольно страшные вещи. Я считаю, что использование API профилирования для перехвата вызовов, а не для использования подхода, используемого такими фреймворками, как Moq. Вот пример использования Typemock для издевательства SQLConnection здесь.
Если у вас нет денег, чтобы потратить/вы можете изменить свой код и не возражаете продолжать полагаться на базу данных, вам нужно взглянуть на какой-то способ поделиться своим соединением с базой данных между вашим тестовым кодом и вашими методами обработки данных. Два подхода, которые spring должны учитывать: либо вводить информацию о соединении в класс, либо делать ее доступной, введя factory, которая дает доступ к информации о соединении (в этом случае вы можете ввести макет factory во время тестирования, который возвращает требуемое соединение).
Если вы перейдете к вышеуказанному подходу, а не прямо вставляете SqlConnection
, подумайте о том, чтобы ввести класс оболочки, который также несет ответственность за транзакцию. Что-то вроде:
public class MySqlWrapper : IDisposable {
public SqlConnection Connection { get; set; }
public SqlTransaction Transaction { get; set; }
int _transactionCount = 0;
public void BeginTransaction() {
_transactionCount++;
if (_transactionCount == 1) {
Transaction = Connection.BeginTransaction();
}
}
public void CommitTransaction() {
_transactionCount--;
if (_transactionCount == 0) {
Transaction.Commit();
Transaction = null;
}
if (_transactionCount < 0) {
throw new InvalidOperationException("Commit without Begin");
}
}
public void Rollback() {
_transactionCount = 0;
Transaction.Rollback();
Transaction = null;
}
public void Dispose() {
if (null != Transaction) {
Transaction.Dispose();
Transaction = null;
}
Connection.Dispose();
}
}
Это предотвратит создание вложенных транзакций +.
Если вы захотите реструктурировать свой код, вы можете захотеть обернуть свой код обработки данных более макетным способом. Так, например, вы можете использовать функциональность доступа к основной базе данных в другой класс. В зависимости от того, что вы делаете, вам нужно расширить его, однако вы можете получить что-то вроде этого:
public interface IMyQuery {
string GetCommand();
}
public class MyInsert : IMyQuery{
public string GetCommand() {
return "INSERT ...";
}
}
class DBNonQueryRunner {
public void RunQuery(IMyQuery query) {
using (SqlConnection connection = new SqlConnection(Properties.Settings.Default.ConnectionString)) {
connection.Open();
using (SqlTransaction transaction = connection.BeginTransaction()) {
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.Transaction = transaction;
command.CommandTimeout = 900; // Wait 15 minutes before a timeout
command.CommandText = query.GetCommand();
command.ExecuteNonQuery();
transaction.Commit();
}
}
}
}
Это позволяет вам unit test больше вашей логики, например кода генерации команды, не беспокоясь о том, чтобы попасть в базу данных, и вы можете протестировать свой основной код доступа к данным (Runner) по базе данных один раз, а не на каждая команда, которую вы хотите запустить против базы данных. Я все равно буду писать интеграционные тесты для всего кода доступа к данным, но Id только имеет тенденцию запускать их, пока фактически работает над этим разделом кода (чтобы гарантировать, что имена столбцов и т.д. Указаны правильно).