±à¼ÍƼö: |
ÎÄÕÂÊ×ÏȽéÉÜÁ˼ܹ¹Ñݽø´øÀ´µÄ²âÊÔÌôÕ½£¬È»ºó½øÐÐÁËÒ»¸ö²âÊԵĸÅÊö£¬×îºó½²½âFreeWheel
ºËÐÄÒµÎñÍŶӲâÊÔʵ¼ù¡£
±¾ÎÄÀ´×ÔÓÚinfoq£¬ÓÉ»ðÁú¹ûÈí¼þAnna±à¼¡¢ÍƼö¡£ |
|
ÒýÑÔ
Ëæ×ż¼ÊõµÄ·¢Õ¹£¬Èí¼þ¿ª·¢·½·¨²»¶ÏÑݽø£¬²âÊÔÒ»Ö±¶¼ÊDz»¿É»òȱµÄÒ»²½¡£×÷ΪÌáÉýÓû§ÌåÑé¡¢±£ÕÏÈí¼þÖÊÁ¿µÄ¹Ø¼ü»·½Ú£¬Èí¼þ²âÊÔÖÁ¹ØÖØÒª¡£ÌرðÊÇÃæ¶Ô¶àÑù»¯µÄ²âÊÔÐèÇó¡¢²»¶Ï¼Ó¿ìµÄ°æ±¾µü´úËÙ¶È£¬ÈçºÎÎ§ÈÆÒµÎñ¹¦ÄÜÐèÇó´î½¨ÊÊºÏÆäÌØµãÇÒ¿ìËÙ¡¢¸ßЧµÄÈí¼þ²âÊÔÌåϵ¡¢¿ò¼ÜºÍÁ÷³Ì£¬FreeWheel
ºËÐÄÒµÎñÍŶӶԴ˽øÐÐÁËÉîÈëµÄ̽Ë÷ºÍʵ¼ù¡£ÍŶӽ«²âÊÔÖоßÓй²ÐÔµÄÄ£¿é½øÐгéÏóºÍÌáÈ¡£¬ÐγÉÁË×Ô¼ºµÄ¡°²âÊÔÖ®µÀ¡±£¬Îª²úÆ·ÖÊÁ¿ÌṩǿÓÐÁ¦µÄ±£ÕÏ¡£
¼Ü¹¹Ñݽø´øÀ´µÄ²âÊÔÌôÕ½

ÔÚÏîÄ¿³õÆÚ£¬ºËÐÄÒµÎñÍŶӲÉÈ¡µÄÊÇ»ùÓÚ Ruby-Rails µÄµ¥Ìå¼Ü¹¹£¬ÈçÉÏͼ×ó²àËùʾ£¬Ö÷Òª°üÀ¨Ç°¶Ë¡¢ÖмäÒµÎñÂß¼²ãºÍÊý¾Ý¿â²ãÈý²ã½á¹¹¡£Ëæ×ÅÏîÄ¿¹æÄ£µÄÀ©´ó£¬Ô½À´Ô½¶àµÄÈ˼ÓÈëÁË¿ª·¢ÍŶӣ¬´úÂëÁ¿Ò²ÔÚÓëÈÕ¾ãÔö£¬µ¥Ìå¼Ü¹¹µÄȱµãÒ²ËæÖ®¿ªÊ¼ÏÔÏÖ£º¸´ÔÓÐԸߣ¬Ä£¿éÖ®¼äÏ໥ÒÀÀµ£»ÏîÄ¿²¿ÊðËٶȱäÂý£¬»¨·ÑµÄʱ¼ä²»¶ÏÔö³¤£»×è°Á˼¼Êõ´´Ð£¬ÏµÍ³À©Õ¹ÄÜÁ¦ÊÜÏÞ¡£
»ùÓÚÒÔÉÏÔÒò£¬ºËÐÄÒµÎñÍŶӾö¶¨¸Ä±ä¼¼Êõ¼Ü¹¹£¬Öð²½Åׯú¹¹½¨µ¥Ò»¡¢ÅÓ´óµÄµ¥ÌåʽӦÓã¬×ª¶ø²ÉÓÃ΢·þÎñ¼Ü¹¹£¬ÈçÉÏͼËùʾ¡£Î¢·þÎñ¼Ü¹¹½«µ¥Ìå¼Ü¹¹µÄÖмä²ã·Ö½â£¬²ð·Ö³É¶à¸ö¿É¶ÀÁ¢Éè¼Æ¡¢¿ª·¢¡¢ÔËÐеÄСӦÓ㬸÷¸öСӦÓÃÖ®¼äÐ×÷ͨÐÅ£¬ÎªÓû§Ìṩ×îÖÕ·þÎñ¡£´ËÍ⣬½«·þÎñ²¿ÊðÔÚ
AWS ÉÏ£¬ÕâЩµ÷Õû¶¼ÓÐЧµØÏû³ýÁËÍ´µã¡£
Óë´Ëͬʱ£¬Î¢·þÎñ¼Ü¹¹µÄʹÓÃÒ²¸øÎÒÃǵIJâÊÔ´øÀ´ÁËеÄÌôÕ½£¬³ýÁËÒªÑéÖ¤¸÷¸ö΢·þÎñµÄ¹¦ÄÜÊÇ·ñÕý³£Ö®Í⣬»¹ÐèÒª¿¼ÂÇÈçÏÂÎÊÌ⣺
1. ÈçºÎ²âÊÔ΢·þÎñÖ®¼äµÄÒÀÀµÊÇ·ñÕý³££»
2. ÔÚ΢·þÎñ¼Ü¹¹ÏÂÈçºÎÑéÖ¤Õû¸öϵͳµÄ¹¦ÄÜÊÇ·ñ·ûºÏÔ¤ÆÚ£»
3. ÈçºÎÓÐЧµÄ½øÐжà¸ö΢·þÎñµÄ²¿ÊðºÍ²âÊÔ¡£
еIJâÊÔÐèÇóÐèҪеIJâÊÔ²ßÂÔÀ´Âú×㣬ÏÂÎÄÊ×ÏÈ»á¶ÔÈí¼þ²âÊÔ½øÐмòµ¥µÄ½éÉÜ£¬È»ºó»á½éÉܺËÐÄÒµÎñÍŶӶÔÕâЩÐèÇóµÄÓ¦¶ÔÁ¼²ß¡£
Èí¼þ²âÊÔ¸ÅÊö
Èí¼þ²âÊÔÊÇʹÓÃÈ˹¤»òÕß×Ô¶¯»¯ÊÖ¶ÎÀ´¼ø¶¨Èí¼þµÄ¹¦ÄÜ»òÐÔÄÜÊÇ·ñÂú×㿪·¢Ö®Ç°Ìá³öµÄÐèÇóµÄÒ»¸ö¹ý³Ì¡£Í¨¹ýÈí¼þ²âÊÔ¿ÉÒÔ¼°Ê±·¢ÏÖÎÊÌâ¡¢½â¾öÎÊÌ⣬Ìá¸ßÈí¼þÖÊÁ¿£¬½µµÍÒòÈí¼þÎÊÌâ´øÀ´µÄÉÌÒµ·çÏÕ£¬ÌáÉýÓû§ÌåÑé¡£
°´ÕÕ²»Í¬·ÖÀà±ê×¼£¬Èí¼þ²âÊÔ¿É»®·ÖΪ²»Í¬µÄÀàÐÍ£¬ÏÂͼÁоÙÁËһЩ³£¼ûµÄÈí¼þ²âÊÔ·ÖÀà¡£

ϱí¶Ô¼¸ÖÖ³£¼ûµÄ²âÊÔ×öÁ˽ÏΪÏêϸµÄ±È½Ï¡£

»ùÓÚ´Ë£¬ºËÐÄÒµÎñÍŶӰ´ÕÕ²úÆ·µÄÑз¢½×¶Î·Ö±ð¶Ôµ¥Ôª²âÊÔ¡¢¼¯³É²âÊÔ¡¢¶Ëµ½¶Ë²âÊÔºÍÐÔÄܲâÊÔ½øÐÐÁËʵ¼ù²¢×ܽáÁË·½·¨£¬ÏÂÎĽ«½øÐÐÏêϸ½éÉÜ¡£
FreeWheel ºËÐÄÒµÎñÍŶӲâÊÔʵ¼ù
²âÊÔ¿ò¼Ü

µ¥Ìå¼Ü¹¹Ê±£¬ÍŶӵIJâÊÔÖ÷ÒªÒÀÀµ»ùÓÚ Selenium µÄ¼¯³É²âÊԺͺó¼ì²é²âÊÔ£¬»ùÓÚ Rails µÄµ¥Ôª²âÊÔÂÔÓÐǷȱ¡£×ªµ½Î¢·þÎñ¼Ü¹¹ºó£¬ÎªÁËÂú×ãеÄÐèÇ󣬲âÊÔ¿ò¼ÜÒ²½øÐÐÁËÏàÓ¦µ÷Õû¡£ÉÏͼÊÇĿǰºËÐÄÒµÎñÍŶӵIJâÊÔ½ð×ÖËþ£¬Ëü¿ÉÒԺܺõذïÖúÎÒÃÇÇø·Ö²»Í¬²ã´Î²âÊԵĹØ×¢µã¡£½ð×ÖËþ´Óϲ㵽¶¥²ãÒÀ´ÎΪµ¥Ôª²âÊÔ¡¢¼¯³É²âÊÔ¡¢¶Ëµ½¶Ë²âÊÔºÍÐÔÄܲâÊÔ¡£ÆäÖУ¬Ô½¿¿½ü½ð×ÖËþµÄµ×¶Ë£¬Ò»°ã¶øÑÔ²âÊÔËÙ¶ÈÔ½¿ì£¬·´À¡ÖÜÆÚÒ²Ô½¶Ì£¬²âÊÔ·¢ÏÖÎÊÌâºó¸üÈÝÒ×¶¨Î»ÊÜÓ°ÏìµÄ¹¦ÄÜ£»Ô½ÊÇ¿¿½ü½ð×ÖËþµÄ¶¥¶Ë£¬²âÊÔ¸²¸ÇµÄ·¶Î§Ô½´ó£¬µ«ÐèÒª»¨·Ñ¸ü³¤Ê±¼äÍê³É²âÊÔ£¬¾¹ý²âÊÔºó¹¦ÄܵÄÕýÈ·ÐÔÒ²¸üÓб£Ö¤¡£ÏÂÃæ£¬·Ö±ð½éÉÜ
FreeWheel ºËÐÄÒµÎñÍŶÓÔÚÿһÀà²âÊÔÉϵľßÌåʵ¼ù¡£
µ¥Ôª²âÊÔ
¡°µ¥Ôª¡±ÊÇÈí¼þµÄ×îС¿É²âÊÔ²¿¼þ¡£µ¥Ôª²âÊÔ¾ÍÊÇÈí¼þ¿ª·¢ÖжÔ×îСµ¥Ôª½øÐÐÕýÈ·ÐÔ¼ìÑéµÄ²âÊÔ£¬ËüÊÇËùÓвâÊÔÖÐ×îµ×²ãµÄÒ»Àà²âÊÔ£¬ÓÉ¿ª·¢ÈËÔ±ÔÚ¿ª·¢´úÂëʱͬ²½±àд£¬ÊǵÚÒ»¸öÒ²ÊÇ×îÖØÒªµÄÒ»¸ö»·½Ú¡£
ÍŶӺó¶Ë¿ª·¢Ê¹ÓõÄÓïÑÔÊÇ Go£¬Go ÓïÑÔ×Ô´øÓÐÒ»¸öÇáÁ¿¼¶µÄ²âÊÔ¿ò¼Ü testing£¬¿ÉʹÓÃ×Ô´øµÄ
go test ÃüÁî½øÐе¥Ôª²âÊÔ¡£Í¬Ê±£¬ÎÒÃÇʹÓÃÁË TDD£¬¼´ÔÚ¿ª·¢¹¦ÄÜ´úÂë֮ǰ£¬Ïȱàдµ¥Ôª²âÊÔÓÃÀý£¬ÒÔ²âÊÔ´úÂëÀ´È·¶¨ÐèÒª±àдµÄ²úÆ·´úÂ룬Ìá¸ß´úÂëÖÊÁ¿¡£
Mock ʵ¼ù
µ¥Ôª²âÊԵıàдÍùÍùÓжÀÁ¢ÐÔµÄÒªÇ󣬺ܶàʱºòÒòΪҵÎñÂß¼¸´ÔÓ£¬´úÂëÂß¼Ò²ËæÖ®±äµÄ¸´ÔÓ£¬²ôÔÓÁ˺ܶàÆäËû×é¼þ£¬µ¼ÖÂÔÚ±àдµ¥Ôª²âÊÔÓÃÀýʱ´æÔڱȽϸ´ÔÓµÄÒÀÀµÏÈçÊý¾Ý¿â»·¾³¡¢ÍøÂç»·¾³µÈ£¬ÕâЩÔö¼ÓÁ˵¥Ôª²âÊԵĸ´ÔӶȺ͹¤×÷Á¿¡£
Mock ¶ÔÏó¾ÍÊÇΪ½â¾öÉÏÊöÎÊÌâ¶øµ®ÉúµÄ£¬mock ¶ÔÏóÄܹ»Ä£Äâʵ¼ÊÒÀÀµ¶ÔÏóµÄ¹¦ÄÜ£¬Í¬Ê±ÓÖʡȥÁ˸´ÔÓµÄÒÀÀµ×¼±¸¹¤×÷¡£µ±Ç°£¬ÔÚºËÐÄÒµÎñÍŶÓ
Go ´úÂë¿âÖУ¬´æÔÚ 2 ÖÖ mock ʵ¼ù¡£Ò»ÖÖÊÇºÍ mockery ½áºÏʹÓÃµÄ Testify/mock£¬ÁíÒ»ÖÖÊǺÍ
mockgen ½áºÏʹÓÃµÄ Go/gomock¡£
Testify/Mock
Testify °üÖÐÒ»¸öÓÅÐãµÄ¹¦ÄܾÍÊÇËüµÄ mock ¹¦ÄÜ£¬ÔÚ½øÐе¥Ôª²âÊÔʱ£¬´úÂëÖÐÍùÍùÓдóÁ¿µÄ·½·¨ºÍº¯ÊýÐèҪģÄ⣬´Ëʱ
vertra/mockery ¾Í³ÉΪÁËÎÒÃǵĵÃÁ¦ÖúÊÖ£¬mockery µÄ¶þ½øÖÆÎļþ¿ÉÒÔÕÒµ½ÈκÎÔÚ Go
Öж¨ÒåµÄ interfaces µÄÃû×Ö£¬È»ºó×Ô¶¯Éú³ÉÄ£Äâ¶ÔÏóµ½ mocks Îļþ¼Ð϶ÔÓ¦µÄÎļþÖС£
Golang/mock
Gomock ÊÇ Google ¿ªÔ´µÄ golang ²âÊÔ¿ò¼Ü£¬gomock ͨ¹ý mockgen
ÃüÁîÉú³É°üº¬ mock ¶ÔÏóµÄ .go Îļþ£¬Ëü¿ÉÒÔ¸ù¾Ý¸ø¶¨µÄ½Ó¿Ú×Ô¶¯Éú³É´úÂë¡£ÕâÀï¸ø¶¨µÄ½Ó¿ÚÓÐÁ½ÖÖ·½Ê½£º½Ó¿ÚÎļþºÍʵÏÖÎļþ¡£
Èç¹û´æÔÚ½Ó¿ÚÎļþ£¬¿Éͨ¹ý -source ²ÎÊýÖ¸¶¨½Ó¿ÚÎļþ£¬-source Ö¸¶¨Éú³ÉµÄÎļþÃû£¬-package
Ö¸¶¨Éú³ÉÎļþµÄ°üÃû¡£ÀýÈ磺
mockgen -destination
foo/mock_foo.go -package foo -source foo/foo.go |
Èç¹ûûÓÐʹÓÃ-source Ö¸¶¨½Ó¿ÚÎļþ£¬mockgen Ò²Ö§³Öͨ¹ý·´É䷽ʽÕÒµ½¶ÔÓ¦µÄ½Ó¿Ú£¬Ëüͨ¹ýÁ½¸ö·Ç±êÖ¾²ÎÊýÉúЧ£ºµ¼Èë·¾¶ºÍÓöººÅ·Ö¸ôµÄ·ûºÅÁÐ±í¡£ÀýÈ磺
mockgen database/sql/driver
Conn,Driver |
´ËÍ⣬Èç¹û´æÔÚ·ÖÉ¢ÔÚ²»Í¬Î»ÖõĶà¸öÎļþ£¬Îª±ÜÃâÖ´Ðжà´Î mockgen
ÃüÁîÉú³É mock Îļþ£¬mockgen ÌṩÁËÒ»ÖÖͨ¹ý×¢ÊÍÉú³É mock ÎļþµÄ·½Ê½£¬ÕâÐèÒª½èÖú
go µÄ¡°go generate¡±¹¤¾ßÀ´ÊµÏÖ¡£ÀýÈ磬ÔÚ½Ó¿ÚÎļþÖÐÌí¼ÓÈçÏÂ×¢ÊÍ£º
//go:generate
mockgen -source=foo.go -destination=./gomocks/foo.go
-package=gomocks
|
²âÊÔÓÃÀý
ÏÂÃæ¾ÙÀý½éÉÜ mock ¶ÔÏóÔÚµ¥Ôª²âÊÔÓÃÀýµÄʹÓãº
Éú³ÉµÄ mock Îļþ
type NetworkDao
struct {
mock.Mock
}
// GetNetworkById provides a mock function with
given fields: networkId
func (_m *NetworkDao) GetNetworkById(networkId
int64) (*business.Network, error) {
ret := _m.Called(networkId)
// ... some mock code ...
return r0, r1
} |
±»²âÊÔÎļþ
type dataRightDomain
struct {
networkDao NetworkDao // use NetworkDomain in
the mock code instead NetworkDomain interface
}
func (domain *dataRightDomain) GetDataRightWhitelist(all
bool, searchQuery *types.SearchQuery) ([]*business.WhitelistItem,
int32, error) {
// ... some code ...
partner, err := domain.networkDao.GetNetworkById (item.Id)
// get return values from ExpectedCalls array
in mock when using mock
// ... some code ...
} |
²âÊÔÎļþ
func TestGetDataRightWhitelist(t
*testing.T) {
// ... some code ...
networkDaoMock:= &mock. NetworkDao {}
networkDaoMock.On ("GetNetworkById",
mock2.Anything).Return(nwRet, nil) // set up ExpectedCalls
array in mock
wItems, number, err : = dataRightDomain.GetDataRightWhitelist(true,
searchQuery) // call GetDataRightWhitelist where
networkDao is replaced by mocked one
// ... some code ...
} |
ͨ¹ýµ¥Ôª²âÊÔ£¬ºËÐÄÒµÎñÍŶӴﵽÁËÒÔÏÂÄ¿±ê£º
1. È·±£Ã¿¸ö¹¦Äܺ¯Êý¿ÉÔËÐУ¬²¢±£Ö¤½á¹ûÕýÈ·£»
2. È·±£´úÂëÐÔÄÜ×î¼Ñ£»
3. ¼°Ê±·¢ÏÖ³ÌÐòÉè¼Æ»òʵÏÖµÄÂß¼´íÎó£¬Ê¹ÎÊÌâ¼°Ô籩¶£¬±ãÓÚ¶¨Î»ºÍ½â¾ö¡£
¼¯³É²âÊÔ
¼¯³É²âÊÔÔÚµ¥Ôª²âÊÔÍê³Éºó½øÐУ¬Ëü½«¶à¸ö´úÂëµ¥ÔªÒÔ¼°ËùÓм¯³É·þÎñ£¨ÈçÊý¾Ý¿âµÈ£©×éºÏÔÚÒ»Æð£¬²âÊÔËüÃÇÖ®¼äµÄ½Ó¿ÚÕýÈ·ÐÔ¡£Ëæ×źËÐÄÒµÎñÍŶÓתÏò΢·þÎñ¼Ü¹¹µÄ²½·¥¼Ó¿ì£¬¹¹½¨µÄ
Go ·þÎñÔ½À´Ô½¶à£¬Îª´ËÎÒÃÇÉè¼ÆÁËÊÊÓÃÓÚ²»Í¬·þÎñµÄ¼¯³É²âÊÔÓÃÀý£¬ÔÚ¹¹½¨Ð·þÎñʱ¿ÉÒÔ×î´óÏ޶ȵؼõÉÙѧϰºÍ²âÊԳɱ¾¡£ÏÂͼÃè»æÁËÎÒÃǵɲâÊÔÁ÷³Ì£¬Ö÷Òª°üÀ¨Ëĸö½×¶Î£º×¼±¸²âÊÔÊý¾Ý¡¢×¼±¸²âÊÔ»·¾³¡¢Ö´ÐвâÊÔÓÃÀý¡¢Éú³É²âÊÔ±¨¸æ¡£

²âÊÔÊý¾Ý×¼±¸
ÔÚ²âÊÔÊý¾Ý×¼±¸½×¶Î£¬¾ßÌå²ßÂÔÈçÏ£º
ʹÓÃÒ»¸öÖ÷Êý¾Ý¿â×÷ΪÔËÐзþÎñµÄ»ù´¡Êý¾Ý£¬ÔÚËùÓвâÊÔÓÃÀý¿ªÊ¼Ö´ÐÐǰ£¬´ÓÖ÷Êý¾Ý¿âÖÐÏÂÔØ²âÊÔËùÐèÒªµÄÊý¾Ý±í£¬±£´æ³ÉÁÙʱ
SQL Îļþ¡£Èç¹ûijЩ²âÊÔÓÃÀýÐèÒª½«Êý¾Ý»Ö¸´µ½³õʼ״̬£¬¿ÉʹÓÃÁÙʱ SQL ÎļþË¢ÐÂÊý¾Ý¿â¡£ÔÚËùÓвâÊÔÓÃÀýÖ´ÐÐÍê³Éºó£¬ÔÙ½«ËùÓÐÊý¾ÝË¢»Ø³õʼ״̬¡£ÕâÖÖ×ö·¨ºÍ¹²Ïí²âÊÔÊý¾Ý¿âÏà±È£¬¾ßÓÐÈçÏÂÓÅÊÆ£º
1. ÿ¸ö²âÊÔÓÃÀý¶¼½«ÓµÓжÀÏíµÄÊý¾Ý£¬±ÜÃâÁËÓÉÓÚ¹²ÏíÊý¾Ý¿âÖÐÊý¾Ý¸ü¸Ä¶ø³öÏֵĴíÎó¡£
2. Êý¾ÝˢРSQL µÄÁ¿ºÜС£¬ÒòΪ½öÐèË¢ÐÂÓë²âÊÔÓÃÀý¾ßÌåÏà¹ØµÄÊý¾Ý±í¡£
3. ¹«ÓÃÊý¾Ý½«µÃµ½¸üÑϸñµÄ¹ÜÀí¡£Ëü½«Ìṩһ¸ö¾ßÓиüºÃÊý¾Ý¶àÑùÐÔµÄÊý¾Ý´æ´¢£¬ÒÔÂú×ã²âÊÔÐèÇó¡£
Ö÷Á÷ Go ²âÊÔ¿ò¼ÜÓÐ 3 ¸ö£ºGinkgo£¬GoConvey£¬Godog£¬ÆäÖУ¬GoDog Ö§³Ö
Gherkin Óï·¨£¬ÈÝÒ×ÉÏÊÖ, ËùÒÔÎÒÃÇÑ¡ÔñʹÓà GoDog ±àд¼¯³É²âÊÔÓÃÀý¡£´ËÍ⣬ÏÖÓеIJâÊÔÓÃÀý¼¯Ò²¿ÉÒÔÈ·±£´úÂëµÄÐÞ¸ÄûÓÐÒýÈëеĴíÎó»òµ¼ÖÂÆäËû´úÂë²úÉú´íÎ󣬯ðµ½Á˻عé²âÊԵŦÄÜ¡£
¶Ëµ½¶Ë²âÊÔ
¶Ëµ½¶Ë²âÊÔÊÇÕ¾ÔÚÓû§Ê¹ÓÃÊӽǽøÐеIJâÊÔ£¬Ëü½«Òª²âÊÔµÄÈí¼þÊÓΪºÚºÐ£¬ÎÞÐèÁ˽âÆäÄÚ²¿¾ßÌåʵÏÖϸ½Ú£¬Ö»Ðè¹Ø×¢Êä³ö½á¹ûÊÇ·ñ·ûºÏÔ¤ÆÚ¡£
ÔÚºËÐÄÒµÎñÍŶӵÄ΢·þÎñ¼Ü¹¹ÖУ¬¶Ëµ½¶Ë²âÊÔ»·½Ú¾ßÓиü¹ãµÄ·¶Î§ºÍ¸ü¸ßµÄµØÎ»£¬ÊÇÈ·±£Õû¸ö²úÆ·ÏßÖÊÁ¿µÄ×îºóÒ»µÀ·ÀÏß¡£ÔÚÒÔǰµÄµ¥Ìå¼Ü¹¹ÖУ¬ÎÒÃDzÉÓÃÁË
Cucumber ºÍ Selenium µÄ×éºÏ½øÐж˵½¶Ë²âÊÔ£¬µ«ÕâÖÖ²âÊÔ¿ò¼ÜÖð½¥±©Â¶³öÐí¶àÎÊÌ⣬²¢ÇÒ²»ÊÊÓÃÓÚ΢·þÎñ¼Ü¹¹¡£ÎªÁ˸üºÃµØÔÚµ±Ç°µÄ΢·þÎñ¼Ü¹¹ÏÂʵʩ¶Ëµ½¶Ë²âÊÔ£¬ÎÒÃǶÔ
Cypress ºÍ Selenium ½øÐÐÁ˱ȽϷÖÎö¡£

ºËÐÄÒµÎñÍŶӻùÓÚÒÔÉÏ·ÖÎö½á¹û²¢½áºÏÒµÎñÐèÒª£¬ÊµÏÖÁËÒ»¸öеĻùÓÚ Cypress µÄ¶Ëµ½¶Ë²âÊÔ¿ò¼Ü£¬¿ÉÒÔͬʱ֧³Ö
Web UI ºÍ API µÄ×Ô¶¯»¯²âÊÔ¡£
Cypress-fixtures

ÓÉÉÏͼ¿ÉÒÔ¿´³ö£¬ÔÚºËÐÄÒµÎñÍŶӱê×¼µÄ¿ª·¢²âÊÔÁ÷³ÌÖУ¬ÖÁÉÙÓÐÈý¸ö½×¶ÎÐèÒª½øÐж˵½¶Ë²âÊÔ£º
±¾µØ²âÊÔ£ºµ±´úÂëλÓÚ×Ô¶¨Òå·ÖÖ§ÖÐÉÐδºÏ²¢µ½Ö÷·Ö֧ʱ£¬Ðè½øÐж˵½¶Ë±¾µØ²âÊÔ£¬¿ª·¢ÈËÔ±Ìí¼ÓеĶ˵½¶Ë²âÊÔÓÃÀýÀ´Íê³É¹¦Äܼì²â¡£
»Ø¹é²âÊÔ£º¹¦ÄÜ´úÂëºÏ²¢µ½Ö÷·ÖÖ§ºó£¬Ðè½øÐж˵½¶Ë»Ø¹é²âÊÔ¡£¸Ã²âÊÔ CI ͨ³£ÔÚÒ¹¼äÔËÐУ¬²¢´¥·¢·¶Î§¸ü´óµÄ¶Ëµ½¶Ë²âÊÔÓÃÀý£¬ÒÔ°ïÖú¿ª·¢ÈËÔ±²éÕÒй¦ÄܵÄDZÔÚÓ°Ïì¡£
ºó¼ì²é²âÊÔ£º¸Ã¹¦ÄÜ·¢²¼µ½ÏßÉÏ»·¾³Ö®ºó£¬Ðè½øÐж˵½¶Ëºó¼ì²é²âÊÔ£¬ÒÔÈ·±£¸Ã¹¦ÄÜÔÚÏßÉÏ»·¾³ÈÔÄܰ´Ô¤ÆÚ¹¤×÷¡£
»ùÓÚÉÏÊöÇé¿ö£¬ÎªÁË×î´ó»¯¶Ëµ½¶Ë²âÊÔÓÃÀýµÄ¿ÉÖØÓÃÐÔ£¬²¢¿¼Âǵ½¹¹½¨±¾µØ E2E »·¾³µÄ¸´ÔÓÐÔ£¬ÎÒÃǽ«
fixtures Ìí¼Óµ½ÎÒÃǵIJâÊÔÁ÷³ÌÖС£Fixtures ÊÇÔÚÈí¼þ²âÊÔ¹ý³ÌÖУ¬Îª²âÊÔÓÃÀý´´½¨ÆäËùÒÀÀµµÄǰÖÃÌõ¼þµÄ²Ù×÷»ò½Å±¾£¬ÕâЩǰÖÃÌõ¼þͨ³£»á¸ù¾Ý²»Í¬µÄ¶Ëµ½¶Ë²âÊÔ»·¾³¶ø±ä»¯¡£
ÀýÈ磬¼ÙÉèÏÖÓÐÒ»²âÊÔ³¡¾°£º¼ì²éÒ»¸öÌØ¶¨¶©µ¥µÄ״̬£¬¶ø¶©µ¥±àºÅÔÚÏßÉÏ»·¾³ºÍ¿ª·¢»·¾³ÖпÉÄÜÓÐËù²»Í¬£¬¶øÇÒ³ýÁ˶©µ¥±àºÅ£¬ºÍ¶©µ¥Ïà¹ØµÄһЩÆäËüÐÅÏ¢Ò²²»Í¬£¬´Ëʱ¾Í¿ÉÒÔʹÓÃ
fixtures¡£
Cypress-tag
ÔÚ½« fixtures ÓÃÓÚÿ¸ö²âÊÔÁ÷³ÌÖ®ºó£¬»¹Ð迼ÂÇÒ»ÖÖÇéÐΣ¬¼´²»Í¬µÄ»·¾³ÏÂÐèÒªÔËÐеIJâÊÔÓÃÀý¿ÉÄܲ»Í¬¡£¶ÔÓÚÏßÉÏ»·¾³µÄºó¼ì²é²âÊÔ£¬ÐèÒªÔËÐÐ×î¸ß¼¶±ðµÄ
P1 ²âÊÔÓÃÀý£»¶ÔÓÚÈÕ³£¶Ëµ½¶Ë»Ø¹é²âÊÔ£¬ÐèÒªÔËÐÐһЩ¸ü´ó·¶Î§µÄ²âÊÔÓÃÀý¡£ÎªÂú×ã´ËÒªÇ󣬺ËÐÄÒµÎñÍŶÓΪ
Cypress Ìí¼ÓÁ˱êÇ©¹¦ÄÜ£¬ÒÔ¶Ô²âÊÔÓÃÀý½øÐзÖÀà¡£
Cypress ²âÊÔÓÃÀý
ÏÂÃæÍ¨¹ýÀý×Ó¼òµ¥ËµÃ÷ fixtures ºÍ tag ÔÚ cypress
²âÊÔÓÃÀýÖеÄʹÓá£
//fixtureÓÃÀ´±íÃ÷ÊÇÔÚʲô»·¾³ÏÂÖ´ÐвâÊÔÓÃÀý
const fixture = {
prd: {
networkInfo: Cypress.env('prdTestNetWorkInfo'),
campaignId: 26341381
},
stg: {
networkInfo: Cypress.env('stgTestNetWorkInfo'),
campaignId: 26341381
},
dev: {
networkInfo: Cypress.env('localNetWorkInfo'),
campaignId: 133469319
}
};
const { networkInfo, campaignId, brandId } = fixture[Cypress.env('TEST_ENV')];
let insertionOrderId;
//tagÓÃÀ´±íÃ÷ÕâÊÇÒ»¸öP1µÄ²âÊÔÓÃÀý
Cypress.withTags(describe, [io, create', 'p1'])(
'Create IO', function() {
before(function() {
cy
.loginByUI(networkInfo, `/campaigns/${campaignId}/edit
`)
.waitUntilLoaded();
});
it('Create an empty insertion Order', function()
{
cy.createEmptyIO()
.get('{insertion_order_id}:eq(0)')
.invoke('text')
.then(ioId => {
insertionOrderId = ioId;
});
});
¡
after(function() {
cy.deleteInsertionOrder({ insertionOrderId });
});
afterEach(function() {
cy.saveResult(this.currentTest, testOwner);
if (this.currentTest.state === 'failed') {
Cypress.runner.stop();
}
});
});
|
ͨ¹ýʹÓà Cypress ½øÐж˵½¶Ë²âÊÔ£¬ÎÒÃÇʵÏÖÁËÒÔÏÂÄ¿±ê£º
Ìæ»»ÏûºÄÐÔµÚÈý·½¹¤¾ß£¨Èç Selenium£©£¬´ó´ó¼õÉÙÁË×¼±¸ºÍÔËÐж˵½¶Ë²âÊÔÓÃÀýËùÐèµÄʱ¼ä£»
Ò»´Î±àд²âÊÔÓÃÀý£¬Í¨¹ýʹÓà fixture ¿ÉʵÏÖÔÚ²»Í¬µÄ»·¾³£¨ÏßÉÏ/±¾µØ¿ª·¢£©ÖÐÔËÐУ»
¿ÉÖØÓõÄ×Ô¶¨ÒåÃüÁîʹ¿ª·¢ÈËÔ±¿ÉÒÔ¿ìËÙÍê³É²âÊÔÓÃÀý£»
¼ò¶ÌÒ×ÓõIJâÊÔ±¨¸æ°üÀ¨ÊÓÆµ±¨¸æ£¬¿É¿ìËÙµ÷ÊÔʧ°ÜµÄ²âÊÔÓÃÀý£»
ÉèÖöÀÁ¢µÄ²âÊԹܵÀºÍ²âÊÔ±êÇ©£¬ÒÔÈ·±£Ã¿¸ö×é¼þ½ö¿¼ÂÇ×Ô¼ºµÄÇé¿ö¡£
ÐÔÄܲâÊÔ
ÐÔÄܲâÊÔÊÇָͨ¹ý×Ô¶¯»¯µÄ²âÊÔ¹¤¾ßÄ£Äâ¶àÖÖÕý³£¡¢·åÖµÒÔ¼°Òì³£¸ºÔØÌõ¼þÀ´¶ÔϵͳµÄ¸÷ÏîÐÔÄÜÖ¸±ê½øÐвâÊÔ£¬Ö÷ҪʹÓÃ
Loadrunner¡¢JMeter µÈ¹¤¾ß¶ÔÈí¼þ½øÐÐѹÁ¦²âÊÔ¡¢¸ºÔزâÊÔ¡¢Ç¿¶È²âÊԵȣ¬ÒòΪÕâЩ²âÊÔ¹ý³ÌÄÑÒÔÓÃÊÖ¹¤´úÌæ£¬ËùÒÔ±ØÐë×Ô¶¯»¯¡£ºËÐÄÒµÎñÍŶÓÑ¡ÔñÁË
JMeter ×÷Ϊ²âÊÔ¹¤¾ß£¬²¢Ê¹Óà Taurus À´ÔËÐÐ JMeter¡£Taurus Äܹ»Ö±½Ó½âÎöÔÉú½Å±¾£¬Èç
JMeter JMX Îļþ£¬Í¬Ê±»¹Ö§³ÖʹÓüòµ¥ÅäÖÃÓï·¨½«²âÊÔ³¡¾°Ê¹Óà YAML »ò JSON À´ÃèÊö
JMeter ½Å±¾¡£
Taurus Yaml ½Å±¾Àý×Ó£º
execution:
- concurrency: 1 //²¢·¢Ïß³ÌÊý
iterations: 1 //Ö´ÐдÎÊýÏÞÖÆ
ramp-up: 5s //Æô¶¯Ê±¼ä
hold-for: 30s //³ÖÐøÊ±¼ä
scenario: get-rfps //²âÊÔ³¡¾°
modules:
jmeter:
properties:
host: docker.for.mac.localhost
port: 3070
scenarios: //²âÊÔcaseÃèÊö
get-rfps:
headers:
¡//some code¡
variables:
¡//some code¡
requests:
- url: http://${__P(host)}:${__P(port)}/.../rfps
method: GET
reporting:
- module: console
- module: final-stats
percentiles: true //ÏÔʾƽ¾ùʱ¼äºÍ°Ù·Ö±È
test-duration: true //²âÊÔʱ¼ä
dump-csv: perf_result_csv.csv
- module: junit-xml
filename: junit-result.xml
settings:
artifacts-dir: TaurusResult
|
ͬʱÎÒÃÇ´´½¨ Jenkins pipeline ²¢¶¨ÆÚ½øÐвâÊÔ£¬Éú³É performance trend
±¨¸æ£¬ÈçÏÂͼËùʾ£º



ͨ¹ýÐÔÄܲâÊÔ£¬ºËÐÄÒµÎñÍŶӴﵽÁËÒÔÏÂÄ¿±ê£º
¹Ø×¢¸ºÔزâÊÔ£¬¼ì²éÓ¦ÓóÌÐòÔÚÔ¤ÆÚÓû§¸ºÔØÏÂÔËÐеÄÄÜÁ¦£¬ÒÔÔÚÓ¦ÓóÌÐòͶÈëʹÓÃǰȷ¶¨ÆäÐÔÄÜÆ¿¾±£»
ÌṩһÖÖ¹Û²ìÓ¦ÓóÌÐòÐÔÄÜÇ÷ÊÆµÄ·½·¨£»
ͳһ²¢¼ò»¯ÐÔÄܲâÊÔµÄʵÏÖºÍÔËÐС£
²âÊÔ×Ô¶¯»¯
ΪÁËÌá¸ß¿ª·¢Ð§ÂÊ£¬¼°Ôç·¢ÏÖÎÊÌ⣬¼õÉÙÖØ¸´ÐÔÀͶ¯£¬ÊµÏÖ²âÊÔ×Ô¶¯»¯£¬ºËÐÄÒµÎñÍŶӼ¯³ÉÁË Jenkins£¬²ÉÓÃ
Jenkins Pipeline µÄ·½Ê½½øÐÐ CI/CD¡£
CI ½×¶Î²âÊÔ
CI ²âÊԵĴ¥·¢µãÒ»°ãÓÐÁ½¸ö£º
´úÂëºÏ²¢µ½Ö÷¸Éǰ£¬´¥·¢ CI ²âÊÔ£¬¸÷ÖÖ¼ì²éºÍ²âÊÔͨ¹ýÖ®ºó£¬´úÂë²ÅÔÊÐí±»ºÏ²¢µ½Ö÷¸É·ÖÖ§£»
´úÂëºÏ²¢µ½Ö÷¸Éºó£¬´¥·¢ CI ²âÊÔ£¬Ä¿µÄÊÇΪÁ˼ìÑéÖ÷¸É·ÖÖ§ÊÇ·ñ·ûºÏÖÊÁ¿Ô¤ÆÚ¡£

ÉÏͼÊÇÓÉ pipeline groovy ½Å±¾¶¨ÒåµÄ Jenkins Á÷Ë®Ïß blue ocean
Ч¹ûͼ£¬ÏÂÃæ½«½áºÏÀý×Ó¶Ô²âÊÔÏà¹ØµÄ¼¸¸öÖØÒª½×¶Î½øÐзÖÎö¡£
UT& Coverage
Ôڴ˽׶ÎÎÒÃÇ¿ÉÒÔ»ñÈ¡µ¥Ôª²âÊÔ¸²¸ÇÂʱ¨¸æ¡£²âÊÔ¸²¸ÇÂʵı¨¸æ»ñÈ¡ºÜ¼òµ¥£¬Ö»ÐèÔÚ steps ÖÐÖ¸¶¨Åܵ¥Ôª²âÊÔʹÓõĽű¾£¬²¢Ôڽű¾ÖаÑÉú³É¸²¸ÇÂʵĿª¹Ø´ò¿ª£¬½«Éú³ÉµÄ½á¹ûÊä³öµ½ÎļþÖС£
stage('UT
& Coverage'){
¡//some code¡
environment {
core_common = get_core_common(serviceFullName)
//»ñÈ¡ut²âÊÔ¸²¸ÇÂʱ¨¸æ
ut_cobertura_report_file = get_ut_cobertura_report_file(serviceFullName)
}
steps {
//specify shell script to execute ut cases
sh(returnStdout: true, script: "sh ${WORKSPACE}/shell_scripts/unit_coverage.sh")
}
post {
success {
//Èç¹û³É¹¦£¬Éú³Éut²âÊÔ¸²¸ÇÂÊHTML¸ñʽµÄ±¨¸æ
archiveArtifacts allowEmptyArchive: true, artifacts:
ut_cobertura_report_file, fingerprint: true
sh 'echo "ci.ut.result=PASS" >>
${WORKSPACE}/env.props'
}
}
}
|
Regression
Ôڴ˽׶ÎÎÒÃÇ¿ÉÒÔ»ñÈ¡ regression ²âÊÔ¸²¸ÇÂʱ¨¸æ¡£²âÊÔ¸²¸ÇÂʵı¨¸æ»ñÈ¡ºÜ¼òµ¥£¬Ö»ÐèÔÚ steps
ÖÐÖ¸¶¨ÅÜ regression ʹÓõĽű¾£¬½«Éú³ÉµÄ½á¹ûÊä³öµ½ÎļþÖС£
stage('Regression'){
environment {
¡//some code¡
html_report_dir = get_report_dir(serviceFullName)
//»ñÈ¡regression²âÊÔ¸²¸ÇÂʱ¨¸æ
regression_cobertura_report_file = get_regression_cobertura_report_file(serviceFullName)
diff_files = "${WORKSPACE}/diff/*"
}
steps {
sh '''
mysql -uroot -proot -h127.0.0.1 -e "source
${WORKSPACE} /sql/ui_permission_sql.sql"
//regression ²âÊÔÊý¾Ý×¼±¸
${WORKSPACE} /integration_test_data/bin/initDB.sh
//regression ²âÊÔ»·¾³×¼±¸
${WORKSPACE} /shell_scripts/regression_init.sh
if [[ ! -d ${html_report_dir} ]]; then
mkdir ${html_report_dir}
fi
'''
//Ö¸¶¨regression ²âÊÔÓÃÀýµÄÖ´Ðнű¾
sh "${WORKSPACE} /regression_scripts/$
{serviceFullName}_regression.sh"
}
post {
success {
¡//some code¡
}
}
} |
Coverage& Analyze
ΪÁ˱£Ö¤²âÊԵĸßÖÊÁ¿ºÍ¸ß¸²¸ÇÂÊ£¬ÎÒÃÇͨ¹ý Groovy ½Å±¾ÉèÖÃÁ˲âÊÔ¸²¸ÇÂʵÄÄ¿±ê£¬²âÊÔ½á¹ûʧ°Ü»òÕ߸²¸ÇÂÊûÓдï±êµÄºÏ²¢´úÂëÇëÇó¾ù²»ÄÜͨ¹ý£¬²¢ÇÒ»áͨ¹ý
slack ֪ͨÏà¹ØÈËÔ±¡£
stage('Coverage
& Analyze'){
¡//some code¡
post {
success {
//ÅжÏÊÇ·ñ´ïµ½²âÊÔ¸²¸ÇÂÊÄ¿±ê
cobertura autoUpdateHealth: false, autoUpdateStability:
false, coberturaReportFile: combined_cobertura_report_file,
conditionalCoverageTargets: '70, 0, 0', failUnhealthy:
false, failUnstable: false, lineCoverageTargets:
'80, 0, 0', maxNumberOfBuilds: 0, methodCoverageTargets:
'80, 0, 0', onlyStable: false, sourceEncoding:
'ASCII', zoomCoverageChart: false
archiveArtifacts allowEmptyArchive: true, artifacts:
"${html_report_dir}/*.json", fingerprint:
true
}
}
}
post {
failure {
//Èç¹ûûÓдﵽ£¬Ôòͨ¹ýslack·¢ËÍÐÅϢ֪ͨÏà¹ØÈËÔ±
slackSend channel: "#${slack_channel}",color:
"danger", message: "AWS Build
FAIL! :bomb: ${serviceFullName} <${BUILD_URL}|${BUILD_DISPLAY_NAME}>
${currentBuild.description}"
}
}
|
ͬʱ£¬ÎÒÃÇÒ²»áÊÕ¼¯µ¥Ôª²âÊԺͼ¯³É²âÊԵIJâÊÔ¸²¸ÇÂʲ¢Í¨¹ýÓʼþ·¢ËÍ֪ͨ£¬Ò²Æðµ½¶½´ÙºÍ¾¯Ê¾×÷Óá£

CD ½×¶Î²âÊÔ
²úÆ·±»²¿Êðµ½ÏßÉÏÖ®ºó£¬Í¨¹ý Pipeline ¹ØÁª´¥·¢¹¦ÄÜ´¥·¢¶Ëµ½¶Ë²âÊ﵀ Jenkins job£¬½øÐвúÆ·ÉÏÏßÖ®ºóµÄÏà¹Ø²âÊÔ¡£
¶Ëµ½¶Ë²âÊÔ
Cypress Ö§³ÖºÍ Jenkins ½øÐм¯³É£¬ÎÒÃÇÉèÖÃÁ˲»Í¬µÄ
Jenkins job£¬ÓеÄÓÃÀ´½øÐÐÈÕ³£µÄ¶Ëµ½¶Ë»Ø¹é²âÊÔ£¬ÓеÄÓÃÀ´½øÐÐÏßÉÏ»·¾³µÄ¶Ëµ½¶Ë²âÊÔ£¬²¢Í¨¹ý
groovy ½Å±¾ÉèÖý«²âÊÔ½á¹ûͬʱͨ¹ýÓʼþºÍ slack ·¢³ö£¬¼«´óµÄ½µµÍÁ˳ö´í²âÊÔÓÃÀýµÄÏìӦʱ¼ä£¬Ìá¸ßÁ˲úÆ·ÖÊÁ¿¡£
pipeline {
¡//some code¡
post {
success {
publishReport()
notifySuccess()
sendMail()
}
//Èç¹û¶Ëµ½¶Ë²âÊÔÓÃÀýʧ°Ü£¬Ôò·¢ËÍÓʼþºÍslackÐÅϢ֪ͨÏà¹ØÈËÔ±
failure {
publishReport()
archiveArtifacts artifacts: 'screenshots/**,
videos/**'
notifyFail()
sendMail()
}
}
} |


ÆäËûÖÊÁ¿±£Ö¤´ëÊ©
Bug Bash
ºËÐÄÒµÎñÍŶÓÓÐÒ»¸öºÜÓÐȤµÄÌØÉ«´«Í³»î¶¯£ºÔÚ²úÆ·ÉÏÏßǰµÄij¸öÌØ¶¨Ê±¼äµã£¬»á×éÖ¯¿ç team µÄ´óÐÍÕÒ
bug »î¶¯£¬ÑûÇë´ó¼ÒÒ»Æð¶Ô²úÆ·½øÐвâÊÔ£¬²¢ÒÀ¾ÝÕÒ³ö Bug ÊýÁ¿µÄ¶àÉÙ½øÐÐÆÀ±ÈºÍ½±Àø¡£Ò»·½Ãæ¿ÉÒÔÔö¼Ó´ó¼ÒµÄÒµÎñ֪ʶ£¬ÔÚ²âÊÔʱѧϰ×ÔÉí¹¤×÷·¶³ëÖ®ÍâµÄÆäËü¹¦ÄÜÄ£¿é£¬ÁíÒ»·½ÃæÒ²¿ÉÒÔÌáǰ·¢ÏÖÒþ²ØµÄ
bug£¬½øÒ»²½Ìá¸ß²úÆ·ÖÊÁ¿¡£
Bug Bash Tool


Bug bash tool רÃÅ·þÎñÓÚÎÒÃÇµÄ Bug Bash »î¶¯£¬ÓÃÀ´Í³¼ÆÃ¿¸öÈË·¢ÏÖµÄ bug
ÊýÁ¿¡£ÈçͼËùʾ£¬¸ù¾Ý²»Í¬µÄÖ¸±ê¶Ô·¢ÏÖµÄ bug ½øÐÐͳ¼Æ£¬²¢Í¨¹ý¶ÔÕâЩÊý¾ÝµÄ·ÖÎöÌáÁ¶³öһЩÓÐÖúÓÚÌá¸ß²úÆ·ÖÊÁ¿µÄ·½·¨¡£¶ÔÓÚÃûÁÐǰéµÄ
bug finder£¬ÍÅ¶Ó»á¸øÓè·áºñµÄ½±Àø£¬Ìá¸ß´ó¼ÒÕÒ bug µÄ»ý¼«ÐÔ¡£
δÀ´Õ¹Íû
Ëæ×Å FreeWheel ÒµÎñ·¶Î§µÄ²»¶ÏÀ©´óºÍÒµÎñ¸´ÔӶȵijÖÐøÌáÉý£¬Èí¼þ²âÊÔÒ²ÐèÒª²»¶ÏÍêÉÆÒÔ±£Ö¤²úÆ·µÄ¸ßÖÊÁ¿¡£ÀýÈ磬½øÒ»²½ÌáÉýµ¥Ôª²âÊԺͼ¯³É²âÊԵĴúÂ븲¸ÇÂÊ£¬Íƹã»ùÓÚ
Cypress µÄ¶Ëµ½¶Ë²âÊԺͻùÓÚ Jmeter µÄÐÔÄܲâÊÔ¡£´ËÍ⣬ÍŶӻ¹½«¸ù¾ÝÒµÎñ·¢Õ¹ÐèÇ󣬳ÖÐø¿ªÕ¹Ç°Ñص÷Ñкͼ¼Êõ´´Ð£¬ÒÔ¸ü¸ßЧ¡¢È«Ãæ¡¢ÏȽøµÄÈí¼þ²âÊÔÊÖ¶ÎΪ²úÆ·ÖÊÁ¿±£¼Ý»¤º½¡£
|