eos-unittest

42568 ワード

eos-unittestはホワイトボックステストです.
eosプロジェクトはbuildのたびにリンクeos/unittestの下のソースファイルを自動的にコンパイルし、eos/build/unittests/unit_を生成します.test.後者はバイナリ・ソース・プログラムで、実行するとすべてのテスト・インスタンスが自動的に実行されます.unit_test --list_contentは、すべてのテストキットおよびテスト例を列挙します../unit_test -t eosio_system_tests/stake_unstake単独テストeosio_system_testsテストキットのstake_unstakeテスト例.
eos-unittestはboostライブラリのユニットテストキットを採用しています.
BOOST_AUTO_TEST_SUITE(テスター名)テスターを宣言し、定義の一番前に置くBOOST_AUTO_TEST_SUITE_END()テストキット終了BOOST_FIXTURE_TEST_CASE(試験用例名、試験類名)FIXTURE試験用例
class template_test_class : public TESTER
{
	// ...
};

BOOST_AUTO_TEST_SUITE(template_test_suite)

BOOST_FIXTURE_TEST_CASE(template_test_case1, template_test_class) try {
    // ...
} FC_LOG_AND_RETHROW()

BOOST_AUTO_TEST_SUITE_END()


ベース_についてtester


base_testerクラス、テストクラスのベースクラス.このクラスは、テストフレームワークの主要なインタフェースを提供します.testerクラスはbase_に継承されますtesterクラス、簡単にbase_を拡張tester(ブロックに関連する3つのメソッドが追加されました).カスタムテストクラスはtesterを継承する必要があります.
ブロック取得時間
control->head_block_time().sec_since_epoch()

検証用マクロ


BOOST_REQUIRE_EQUALは、assetなどの2つの単純なオブジェクトと比較することがよくあります.REQUIRE_MATCHING_OBJECTは、2つのvariant(特にmutable_variant_ojbect)と比較することが多い.

ブロックの生成

void base_tester::produce_blocks( uint32_t n = 1, bool empty = false );

// exp
produce_blocks();
produce_blocks(3);

ユーザーの作成

vector<transaction_trace_ptr>  base_tester::create_accounts(
		vector<account_name> names,
       bool multisig = false,
       bool include_code = true
)

// exp
create_accounts({N(hello), N(world)})

配備契約

// WAVM
void set_code( 
	account_name name, 
	const char* wast, 
	const private_key_type* signer = nullptr 
);

// WABT
void set_code( 
	account_name name, 
	const vector<uint8_t> wasm, 
	const private_key_type* signer = nullptr  
);

void set_abi( 
	account_name name, 
	const char* abi_json, 
	const private_key_type* signer = nullptr 
);

// exp
#include 
#include 
// ...
set_code( N(eosio.token), eosio_token_wast );
set_abi( N(eosio.token), eosio_token_abi );

eosプロジェクトが持参したeosio_build.shは、2つの特殊なhppファイルを追加的にコンパイルして生成します.
  • contract_name.abi.hpp
  • contract_name.wast.hppにはabiとwastのデータをヘッダファイルに変換し、テスト時に適用しやすい.

  • シーケンス化データの設定


    ユニットテストでは、データ構造および関連メンバーの読み取りと検証を容易にするために、永続化されたデータテーブルのデータを逆シーケンス化することがよくあります.
    //  
    //  token_abi_ser.set_abi 
    const auto& accnt = control->db().get<account_object,by_name>( N(eosio.token) );
    abi_def abi;
    BOOST_REQUIRE_EQUAL(abi_serializer::to_abi(accnt.abi, abi), true);
    token_abi_ser.set_abi(abi, abi_serializer_max_time);
    
    //  , 
    //  token_abi_ser.binary_to_variant 
    {
    	// ...
       vector<char> data = get_row_by_account( N(eosio.token), symbol_code, N(stat), symbol_code );
       return data.empty() ? fc::variant() : token_abi_ser.binary_to_variant( "currency_stats", data, abi_serializer_max_time );
    }
    

    variantについて


    すべてのインテリジェント契約で定義されたデータ構造を共通のフォーマットで識別する3つの一般的なタイプがあります.
  • variant
  • variant_object
  • mutable_variant_object

  • variantは、一般的なデータ型を表すデータのシーケンス化と逆シーケンス化によく使用されます.variant_objectはオブジェクトのコンテナに似ています.mutable_variant_objectは、複雑でネスト可能なデータフォーマットを定義するためによく使用され、asテンプレートメソッドでタイプを変換し、[]メンバーにアクセスできます.

    TRX&ACTIONの送信


    eos-unittestでは、ACTIONを作成する方法がN種類あります.
    class base_tester {
    public:
    
    	/*
    	 push_transaction 
    	 packed_transaction,zlib , 
    	 signed_transaction, 
    	*/
    	
    	transaction_trace_ptr push_transaction(
    		packed_transaction& trx, 
    		fc::time_point deadline = fc::time_point::maximum(),
    		uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US
    	);
    	
    	transaction_trace_ptr push_transaction( 
    		signed_transaction& trx, 
    		fc::time_point deadline = fc::time_point::maximum(),
    		uint32_t billed_cpu_time_us = DEFAULT_BILLED_CPU_TIME_US
    	);
    	
    	/*
    	 push_action , transaction:
    	1.  action 
    	2.  action 、 
    	3.  action 、 active 
    	4.  action 、 
    	*/
    
    	action_result push_action(action&& cert_act, uint64_t authorizer);
    
        transaction_trace_ptr push_action(
        	const account_name& code,
            const action_name& acttype,
            const account_name& actor,
            const variant_object& data,
            uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
            uint32_t delay_sec = 0
    	);
             
        transaction_trace_ptr push_action(
        	const account_name& code,
            const action_name& acttype,
            const vector<account_name>& actors,
            const variant_object& data,
            uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
            uint32_t delay_sec = 0
    	);
        
        transaction_trace_ptr push_action(
        	const account_name& code,
            const action_name& acttype,
            const vector<permission_level>& auths,
            const variant_object& data,
            uint32_t expiration = DEFAULT_EXPIRATION_DELTA,
            uint32_t delay_sec = 0
        );
    };
    

    ここで、push_を1つ除いてActionはactionを返しますresult、その他はtransactionを返します.trace_ptrは、以下のように具体的に定義される.見覚えがあるのではないでしょうか.transaction_trace_ptrは我々がHTTPインタフェースで得たJSONデータ結果フィールドと一致する.action_resultはbase_を切り取るaction_trace::consoleの文字列結果.
    class base_tester
    {
    	typedef string action_result;
    	
    	static action_result success() { return string(); }
    	static action_result error( const string& msg ) { return msg; }
    	static action_result wasm_assert_msg( const string& msg ) { return "assertion failure with message: " + msg; }
    	static action_result wasm_assert_code( uint64_t error_code ) { return "assertion failure with error code: " + std::to_string(error_code); }
    };
    
    struct base_action_trace
    {
    	base_action_trace( const action_receipt& r ):receipt(r){}
    	base_action_trace(){}
    	
    	action_receipt       receipt;
    	action               act;
    	bool                 context_free = false;
    	fc::microseconds     elapsed;
    	uint64_t             cpu_usage = 0;
    	string               console;
    	
    	uint64_t             total_cpu_usage = 0; /// total of inline_traces[x].cpu_usage + cpu_usage
    	transaction_id_type  trx_id; ///< the transaction that generated this action
    	uint32_t             block_num = 0;
    	block_timestamp_type block_time;
    	fc::optional<block_id_type>     producer_block_id;
    	flat_set<account_delta>         account_ram_deltas;
    };
    
    struct action_trace : public base_action_trace {
       	using base_action_trace::base_action_trace;
    
       	vector<action_trace> inline_traces;
    };
    
    struct transaction_trace;
    using transaction_trace_ptr = std::shared_ptr<transaction_trace>;
    

    以下によく使われる例をいくつか示します.
    action_result hello(
    	uint64_t param1,
    	uint64_t param2,
    	uint64_t param3
    )
    {
        auto data = fc::variant_object(
    		fc::mutable_variant_object()
    		("param1", param1)
    		("param2", param2)
    		("param3", param3)
    	);
    
        action act;
        act.account = N(code);
        act.name    = N(hello);
        act.data    = abi_serializer.variant_to_binary(
        	abi_serializer.get_action_type(N(hello)), 
        	data, 
        	abi_serializer_max_time
        );
    
        return base_tester::push_action(std::move(act), uint64_t(signer));
    }
    
    BOOST_REQUIRE_EQUAL(success(), hello(param1, param2, param3));
    BOOST_REQUIRE_EQUAL(error("missing authority of alice"), hello(param1, param2, param3));
    
    
    transaction_trace_ptr hello(
    	uint64_t param1,
    	uint64_t param2,
    	uint64_t param3
    )
    {
    	//  push_action
    	return base_tester::push_action(
    		N(code), N(hello), 
    		vector<account_name>{ {N(actor_account1)}, {N(actor_account2)} },
    		fc::mutable_variant_object()  
    		("param1", param1)
    		("param2", param2)
    		("param3", param3)
    	);
    	// PS:mutable_variant_object C++ :
    	// 1.  mutable_variant_object , “,” 
    	// 2.  , , , 
    	// 3. mutable_variant_object mutable_variant_object
    }
    
    BOOST_REQUIRE_EQUAL(success(), hello(param1, param2, param3)->action_traces[0].console);
    

    永続化データの取得


    書き込みテストの過程で、前置条件と後置条件を判定するには、常に持続化データを判断する必要があり、eos-unittestフレームワークのbase_testerテストクラスには、関連するインタフェースも用意されています.
    vector<char> base_tester::get_row_by_account(
    	uint64_t code, 
    	uint64_t scope,
    	uint64_t table,
    	const account_name& act
    ) const;
    

    次に例を示します
    fc::variant get_tab1(uint64_t primarykey)
    {
    	vector<char> data = get_row_by_account(N(code), N(scope), N(tab1), primarykey);
        
        //  abi_serializer , 。
        return data.empty() ? fc::variant() : abi_serializer::binary_to_variant("tab1_struct", data, abi_serializer_max_time);
    }
    

    学校で永続化データを検証する過程で、最もよく使われる尤数は口座残高を取得し、以下にいくつかの方法を示します.
    //  eosio.system_tester.cpp , get_row_by_account 
    asset get_balance(const account_name& act) {
    	//return get_currency_balance( config::system_account_name, symbol(CORE_SYMBOL), act );
        //temporary code. current get_currency_balancy uses table name N(accounts) from currency.h
        //generic_currency table name is N(account).
        const auto& db  = control->db();
        const auto* tbl = db.find<table_id_object, by_code_scope_table>(boost::make_tuple(N(eosio.token), act, N(accounts)));
        share_type result = 0;
    
        // the balance is implied to be 0 if either the table or row does not exist
        if (tbl) {
        	const auto *obj = db.find<key_value_object, by_scope_primary>(boost::make_tuple(tbl->id, symbol(CORE_SYMBOL).to_symbol_code()));
    
            if (obj) {
                    // balance is the first field in the serialization
                fc::datastream<const char *> ds(obj->value.data(), obj->value.size());
                fc::raw::unpack(ds, result);
            }
        }
    
        return asset( result, symbol(CORE_SYMBOL) );
    }
    
    // base_tester 
    asset base_tester::get_currency_balance(
    	const account_name& contract,
    	const symbol&       asset_symbol,
    	const account_name& account
    ) const;