Руководства или примеры фиктивных операций и транзакций QLDB

В настоящее время я пишу приложение, которое записывает в регистр QLDB. У меня есть функция вроде:


// Use an interface that matches the QLDB Driver so we can inject / mock
type ILedgerDriver interface {
    Execute(ctx context.Context, fn func(txn qldbdriver.Transaction) (interface{}, error)) (interface{}, error)
    Shutdown(ctx context.Context)
}

// Create checks for a records existence before inserting on to the ledger.
func Create(driver ILedgerDriver, document interface{}) (interface{}, error) {
    return ls.Driver.Execute(context.Background(), func(txn qldbdriver.Transaction) (interface{}, error) {

        result, err := txn.Execute("SELECT * FROM People WHERE ID = ?", id)
        if err != nil {
            return nil, errors.Wrap(err, "error selecting document")
        }

        // Check if there are any results
        if result.Next(txn) {
            // document exists
            return nil, nil
        }

        result, err = txn.Execute("INSERT INTO People ?", document)
        if err != nil {

            return nil, errors.Wrap(err, "error inserting document")
        }

        return result, nil
    })
}

Затем я пытаюсь издеваться над чем-то вроде этого:

// implements qldbdriver.Transaction.
type mockQldbTx struct{}

func (mockQldbTx) Execute(statement string, parameters ...interface{}) (*qldbdriver.Result, error) {
    for _, p := range parameters {
        if ps, _ := p.(string); ps == "ERROR" {
            return nil, errors.New("execute failed")
        }

        if ps, _ := p.(string); ps == "WILLFINDME" {
            emptyResult := &qldbdriver.Result{}

            return emptyResult, nil
        }
    }

    return nil, nil
}

func (mockQldbTx) BufferResult(result *qldbdriver.Result) (*qldbdriver.BufferedResult, error) {
    return nil, nil
}

func (mockQldbTx) Abort() error {
    return nil
}

// implements ILedgerDriver
type mockDriver struct{}

func (mockDriver) Execute(ctx context.Context, fn func(txn qldbdriver.Transaction) (interface{}, error)) (interface{}, error) {
    mockTx1 := mockQldbTx{}
    result, err := fn(mockTx1)

    return result, err
}

func (mockDriver) Shutdown(ctx context.Context) {
}

Что, в основном, работает. Однако, поскольку qldbdriver.Result не является интерфейсом, кажется, я не могу имитировать транзакцию, чтобы проверить случай, когда результат будет иметь свойства index и pageValues (которые вызовут блок if result.Next(txn)).

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


person Chris Sargent    schedule 19.04.2021    source источник


Ответы (1)


В последней фиксации драйвера QLDB команда QLDB представила интерфейс Result для решения проблемы, с которой вы столкнулись. See this commit which resolve эта проблема.

Копирование из руководства в этом выпуске: ниже приведен упрощенный фрагмент кода, показывающий тестирование функции, переданной Execute. Transaction и Result теперь являются интерфейсами, которые mockTransaction и mockResult реализуют в нашем тестовом сценарии.

// Your upsert method that can be passed into QLDBDriver.Execute()
func upsert(txn Transaction) (interface{}, error) {
    res, _ := txn.Execute("SELECT * FROM Table WHERE ID = '12345'")
    if res.Next(txn) {
        txn.Execute("UPDATE Table SET Name = 'Foo' WHERE ID = '12345'")
    } else {
        txn.Execute("INSERT INTO Person <<{'Name' : 'Foo', 'ID' : '12345'}>>")
    }
    return nil, nil
}

type mockTransaction struct {
    mock.Mock
}

func (m *mockTransaction) Execute(statement string, parameters ...interface{}) (Result, error) {
    args := m.Called(statement, parameters)
    return args.Get(0).(Result), args.Error(1)
}

func (m *mockTransaction) BufferResult(res Result) (BufferedResult, error) {
    panic("not used")
}

func (m *mockTransaction) Abort() error {
    panic("not used")
}

type mockResult struct {
    mock.Mock
}

func (r *mockResult) Next(txn Transaction) bool {
    args := r.Called(txn)
    return args.Get(0).(bool)
}

func (r *mockResult) GetCurrentData() []byte {
    panic("not used")
}

func (r *mockResult) GetConsumedIOs() *IOUsage {
    panic("not used")
}

func (r *mockResult) GetTimingInformation() *TimingInformation {
    panic("not used")
}

func (r *mockResult) Err() error {
    panic("not used")
}

func TestUpsert(t *testing.T) {
    // Document already exists (update)
    mockTxn := new(mockTransaction)
    mockRes := new(mockResult)
    mockRes.On("Next", mockTxn).Return(true).Once()
    mockTxn.On("Execute", "SELECT * FROM Table WHERE ID = '12345'", mock.Anything).Return(mockRes, nil)
    mockTxn.On("Execute", "UPDATE Table SET Name = 'Foo' WHERE ID = '12345'", mock.Anything).Return(mockRes, nil)
    upsert(mockTxn)

    mockTxn.AssertCalled(t,"Execute", "UPDATE Table SET Name = 'Foo' WHERE ID = '12345'", mock.Anything)
    mockTxn.AssertNotCalled(t,"Execute", "INSERT INTO Person <<{'Name' : 'Foo', 'ID' : '12345'}>>", mock.Anything)

    // Document did not exist (insert)
    mockRes.On("Next", mockTxn).Return(false).Once()
    mockTxn.On("Execute", "INSERT INTO Person <<{'Name' : 'Foo', 'ID' : '12345'}>>", mock.Anything).Return(mockRes, nil)
    upsert(mockTxn)

    mockTxn.AssertCalled(t,"Execute", "INSERT INTO Person <<{'Name' : 'Foo', 'ID' : '12345'}>>", mock.Anything)
}

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

person alpian    schedule 20.04.2021
comment
Это потрясающе, спасибо за публикацию, я не отчаянно нуждаюсь в этом прямо сейчас, поэтому я подожду, пока он приземлится. - person Chris Sargent; 23.04.2021