ããã«ã¡ã¯ããã¡ãã¯Linc'wellã¢ããã³ãã«ã¬ã³ããŒã®1æ¥ç®ã§ãã
çããã¯DBã«å¯ŸããŠæžã蟌ã¿ãçºçãã颿°ã®ãã¹ããã©ã®ããã«è¡ãããŠããã§ããããïŒ
ç§ã¯golangåå¿è
ã©ããããµãŒããµã€ãåå¿è
ãªã®ã§ãæé©è§£ãå
šããã£ãŠåãããªãã£ãã®ã§ããããããã調ã¹ãæ«ããããªæãã§çšæãããšè¯ãããããšãããã¹ãç°å¢ã»æžãæ¹ã«èœã¡çããã®ã§ãããããã«èšããŸãã
åãæ©ã¿ãæã£ãæ¹ã«å€æåºæºãæäŸã§ãããããªèšäºã«ãªã£ãŠãããšå¬ããã§ãã(ããããã°åŒ·ã人ããã®ãã£ãŒãããã¯åŸ ã£ãŠãŸã)
ã¯ããã«: ã©ããªãã¹ããæžãããã®ã
ã¯ããã«èŠä»¶ãã¯ãªã¢ã«ããããã«ã©ããªãã¹ããæžãããã®ãã®å ·äœäŸãæžããŸãã
ãã ä»ç§ã¯ECãµã€ãã®éçºãããŠããŠããã®äžã§å®æè³Œå
¥ã®ä»çµã¿ãäœã£ãŠããŸãã
å®æè³Œå
¥ã¯ãŠãŒã¶ãå®æè³Œå
¥ãç³ã蟌ãã éã«ã次ã«ã©ã®æ¥ä»ã«ã©ããªååã賌å
¥ããã®ãããšããããŒã¿ãæã£ã Subscription ãšããããŒã¿ãäœããæ¥æ¬¡ã§ãã®æ¥ã®æ¥ä»ãæå®ãããå®æè³Œå
¥ã«å¿ããŠæ³šæãäœããããåŠçãè¡ãããšããä»çµã¿ã§äœããŸãã
ãªã®ã§ããã¹ãã®æ¬äŒŒã³ãŒããæžããšãããªæãã§ãã
func TestCreateSubscriptionOrders(t *testing.T) {
// Given: 仿¥çºéäºå®ã®Subscriptionããã
// When: Batchã®é¢æ°ãå®è¡ãã
// Then: ãã®Subscriptionã«å¿ããOrderãäœããã
}
ä»åãã¹ããèãããã®ã¯ãã®ããã«ã颿°ãå®è¡ããããã®é¢æ°ãå®è¡ããããšã«ãã£ãŠDBã®ããŒã¿ãæ£ããå€ãã£ãããšãã確èªãããã±ãŒã¹ã§ãããªã®ã§ã颿°ã«å¯ŸããŠãã¹ãã³ãŒãå ããåç §ã§ããDBã€ã³ã¹ã¿ã³ã¹ãæž¡ããŠãããå¿ èŠããããŸãã
ãã®ããããã®èšäºã§ã¯æ¬¡ã®3ã€ã®äºæã«ã€ããŠèããŠãããŸãã
- DBã€ã³ã¹ã¿ã³ã¹ãã©ãçšæããã
- ãã¹ãããŒã¿ãã©ãçšæããã
- äžèšäŸã®ããã«ãã¹ããå®è¡ããããã«åæãšããŠç¹å®ã®ãã¹ãããŒã¿ãå€ã ããã§ãããããããã©ãçšæãããšè¯ãããããèããŸã
- ãã¹ãããŒã¿ã®ã¯ãªãŒã³ããããã©ããã£ãŠè¡ãã
- ãŠããããã¹ãã§åææ¡ä»¶ããã¹ã察象ã®é¢æ°å ã§INSERTãããããŒã¿ã¯ä»ã®ãã¹ãã«åœ±é¿ãäžããªãããæ¶ããŠããããã§ã
- ãããã©ã®ããã«è¡ãããèããŸã
DBã€ã³ã¹ã¿ã³ã¹ãã©ãçšæããã
倧æ¹éãšããŠã¯ mock ãçšæããããã¹ãçšã«DBãç«ãŠããã®ã©ã£ã¡ãã«ãªããšæããŸãã
mock
詊ãã« go-sqlmock ã®ãµã³ãã«ã³ãŒããèŠãŠã¿ãŸãã
GitHub - DATA-DOG/go-sqlmock: Sql mock driver for golang to test database interactions
func TestShouldUpdateStats(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
// now we execute our method
if err = recordStats(db, 2, 3); err != nil {
t.Errorf("error was not expected while updating stats: %s", err)
}
// we make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
mock ã§ã¯ mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
ã®ããã«ãããããSQLãæ¥ãæã¯ããããçµæãè¿ãããšããæå®ã®ä»æ¹ãã§ããŸãã
èŠããã«ãæåŸ
ããSQLãæ¥ãããšãã¯ç¢ºèªã§ããŸãããæ³å®ããå€ãDBããè¿ã£ãŠããããšãã¯æ
ä¿ã§ããŸããã
å人çã«ã¯ããã ãã§ä»åã®èŠä»¶ã«ã¯åããªãããªãšæããŸãããïŒããšåŒç€Ÿã®ã±ãŒã¹ã§èšããšãsqlboilerãšããORããããŒã䜿ã£ãŠããŠãããã®SQLãåçŸããã®ãããã©ããããšèšãäºæ
ããããŸãããã¡ãããšæ¢ãã°çSQLçºè¡ããã¡ãœãããšãããã®ã§ãããããªãã§ããïŒ
testçšã®DBãç«ãŠã
次ã«ãã¹ãçšã®DBãç«ãŠãæ¹æ³ã«ã€ããŠãè²ã ãããšã¯æããŸãããæ¢ãããšããæ¬¡ã®ã©ã£ã¡ãã£ãœãã§ãã
- éçºçšã®ãã®ãšåãã³ã³ããå ã«ãã¹ãçšã®DBãäœã
- dockertestã䜿ã£ãŠå°çšã®ã€ã¡ãŒãžäœã
ä»ã®èªåãã¡ã®éçºç°å¢ã ãšãã§ã«éçºçšã® postgres ããã»ã¹ããã£ãŠãããã«ãã¹ãçšã®DBäœã£ã¡ããã®ã§ç°¡åã«æžãŸãããããã ã£ãã®ã§ããããããšã«ããŸãããã¡ãã£ãšãŠããããã¹ããDockerç«ãŠãŠããããšã«äžæ¹ã®æ°æã¡æªãããªãããã§ã¯ãªãã§ãããéçºäžã¯åºæ¬çã«åžžã«åãããŠãããã®ãªã®ã§ä»ã®ãšããå°ã£ãŠããªãã§ãã
CIç°å¢ã§ã¯ã©ãããŠããã®ã
ãã ä»GitHub Actionsã䜿ã£ãŠããã®ã§ããã䜿ã£ãŠã®äŸã«ãªããŸãããCIç°å¢ã§ã¯dockerã䜿ããªãã§ postgresç«ãŠãŠ migration (goose)ãæµããšããã®ããã£ãŠããŸãã
name: ci-backend
on: [push]
jobs:
build:
name: setup
runs-on: ubuntu-latest
services:
postgres:
image: postgres:10
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
ports:
- 5432:5432
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@master
- uses: actions/setup-go@v1
with:
go-version: '1.12'
- name: setup bash_profile
run: |
echo 'export GOPATH="$HOME/go"' >>~/.bash_profile
echo 'PATH="$GOPATH/bin:$PATH"' >>~/.bash_profile
- name: run migration
working-directory: server
shell: bash -l {0}
run: |
go get -u github.com/pressly/goose/cmd/goose
goose --dir=internal/db/migrate postgres "user=postgres port=5432 password=postgres host=localhost dbname=postgres sslmode=disable" up
- name: test server
working-directory: server
env:
DB_PORT: ${{ job.services.postgres.ports[5432] }}
DB_USER: postgres
DB_PASSWORD: postgres
DB_HOST_TEST: localhost
DB_NAME_TEST: postgres
run: |
go test ./...
ãã¹ãããŒã¿ã®æºå
èããã芳ç¹ã¯äºã€ãã£ãŠ
- ããã«ç°¡åã«ãã¹ãããŒã¿ãçšæããã
- ããã«äºåã«çšæãããã¹ãããŒã¿ãä»ã®ãã¹ãã«åœ±é¿ããªãããã«ããã
äŸãã°åé ã®å®æè³Œå
¥ã®ã±ãŒã¹ã«ã€ããŠèããŠã¿ããšããæ³šæãäœãããã«ã¢ã¯ãã£ããªå®æè³Œå
¥ãããããšããåæãšããŠããŸãã
ãªã®ã§ã泚æãäœãããããšãããã¹ããããå Žåã¯åææ¡ä»¶ã§ããå®æè³Œå
¥ã®ããŒã¿ãå
¥ããªããã°ãããŸããããã¹ãããŒã¿ãçšæããã®ã¯éåžžã«è©°ãŸããªãäœæ¥ãªã®ã§ãªãã¹ãç°¡ç¥åããããšããã§ãã
ãã ãã§ã¯ã°ããŒãã«ãª seed ããŒã¿ã®ãããªãã®ã«å®æè³Œå ¥ã®ããŒã¿ãå ¥ããŠæåã«å ¥ããã°ãããšèšããã®ã§ããªããäŸãã°äžèšä»¥å€ã«ãå®æè³Œèªããªãå Žåã«æ³šæãäœãããªãããšãããã¹ãããããšãªã£ãå Žåã«äžå¯èœã«ãªã£ãŠããŸããŸãã(ããããªãããããŒã¿ãå ¥ã£ãŠããããšãåæãšããŠããã®ãã¹ãã®åã«å®æè³Œèªã®ããŒã¿æ¶ãããšããäœèšãªç¥èãå ¥ã£ãŠããŸããŸã)
ãªã®ã§ããã¹ãããŒã¿ãç°¡åã«çšæããããšããšããã¹ãéã§æ±ãããŒã¿ãå¹²æžããªããããã«èãããã®ã¯æ¬¡ã®äºã€ã§ã
- ã©ã®ã¿ã€ãã³ã°ã§ããŒã¿ãINSERTããã®ãããã
- ãã¹ãããŒã¿ãç°¡åã«çšæããå®è£ ã®ä»æ¹
ã©ã®ã¿ã€ãã³ã°ã§ããŒã¿ãINSERTããã®ãããã
ãã£ããèšããšäºåã« seed ã¿ãããªã®ãå ¥ãããåãŠããããã¹ãã§åææ¡ä»¶ãšããŠå ¥ãããã§ãã(æ£ç¢ºã«ã¯äºè æäžãšããããããã¹ãåºæã®ããŒã¿ã¯ç»å Žããã«æ±ºãŸã£ãŠããã®ã§ seedãçšæãããã©ãããšããåãã®æ¹ãæ£ããã§ãã)
ä»åŸã¯äœãããäœããããããªãã§ããäžæŠ seed ã®ãããªãã®ã¯äœããªãããšã«ããŸããã
çç±ãšããŠã¯ãããŠããããã¹ããã°ããŒãã«ã«äœããããã¹ãããŒã¿ãåæãšããŠããæã«ãæé»çãªç¥èãæã£ãŠããã®ããã¹ãèªäœã®ä¿å®æ§äžããŠãªããæ°æã¡æªããšæããããã§ãããã®ãã¹ãã ãèŠãã°ãã®ãã¹ãã®åææ¡ä»¶ã¯äœã§èŠããããšã¯äœãåããããã«ããããšæããŸããããªã®ã§å€å°é¢åã¯å¢ãããããããªãã§ãããã£ã¡ãäžã€äžã€ã®ãŠããããã¹ãã«ããŒã¿ã®æºåãæžããŠããããšæããŸãã
ãã ãããã€ããã¹ãæžããŠã¿ãŠæã£ãã®ã¯ User ã¿ãããªã©ããªã·ã¹ãã ã§ãã³ã¢ãšãªãæŠå¿µã¯ããå šãŠã®ãã¹ãã®åææ¡ä»¶ã«ããŠããŸã£ãŠãåé¡ãªãã®ã§ã¯ãšããæ°ã¯ããŠããŠããŸãããããã£ããã®ã¯æŒžé²çã«æ¹åããŠãããããªãšæããŸãã
ãã¹ãããŒã¿ãç°¡åã«çšæããå®è£ ã®ä»æ¹
æäžŠã¿ã§ãããã¡ã¯ããªé¢æ°ãäœã£ãŠããããšæããŸãã
ãæããŸãããšããã®ã¯ãã®èšäºå·çæç¹ã§ã¯å€§ããŠæžããŠãªãã®ã§ãäœãæžãããšãç¡ããšããããšãæããŠããŸããé 匵ããŸãã
æ®éã«èªäœã§è¯ãããã ãã©ããããã©ã€ãã©ãªäœ¿ã£ãæ¹ã楜ãªã®ããª
GitHub - bluele/factory-go: A test fixtures replacement inspired by factory_boy and factory_girl.
3.ãã¹ãããŒã¿ã®ã¯ãªãŒã³ããããã©ããã£ãŠè¡ãã
倧ããã¯æ¬¡ã®äºã€ããªãšæããŸãã
- ãã¹ãå ã®åŠçã¯ãã©ã³ã¶ã¯ã·ã§ã³ãšããŠæã£ãŠãããŠããã¹ããçµãã£ããããŒã«ããã¯ã
- æ¯åããŒã¿ãæ¶ã
çµè«ããè¿°ã¹ããš1åç®ã®æ¯åãã©ã³ã¶ã¯ã·ã§ã³è²Œãããã«ããŸããçç±ã¯ãã¡ãã®æ¹ãè³æ»ã§æžããŠãç¹ã«ãã¡ãªãããæãåœãããªãããã§ãã
å ·äœçã«ã¯æ¬¡ã®ãããªæãã§æžãããã«ããŸãã
func TestCreateAppSubscriptionOrders(t *testing.T) {
// ãã©ã³ã¶ã¯ã·ã§ã³ã¯ã
tx := db.GetTestTransaction()
// ãã¹ã
// çµãã£ããããŒã«ããã¯
tx.Rollback()
}
ãã£ã¡ãã·ã³ãã«ã§ãããã·ã³ãã«ã§ããæ¯åæžãã®ããäžæ®µæ¥œã«ã§ããªãããšæã£ãã®ã§ãbeforeEach/afterEachããæ¹ãããããšããã®ãèããŠã¿ãŸããã
ãªã beforeEach/afterEach ãããã®ã
åè§£ãããšæ¬¡ã®äºã€ã®ã¢ãããŒã·ã§ã³ããããŸãã
- ãã£ããæžãå¿ããç¡ãããã
- ãã ããããå®çŸãããå Žåã¯å šããã±ãŒãžã®ãã¹ãã«å¯ŸããŠé©çšããã®ãå¿ èŠãšãªããã§ããªãã£ãœãã§ã(ã§ãããªãæããŠæ¬²ãã)
- ããã§ãªãå ŽåãDBã¢ã¯ã»ã¹ãå¿ èŠãªãã¹ãã®ããã±ãŒãžå ã§æžãããšã«ãªãããããã§ããã°ãã®ç®çã¯æºãããªãã®ã§ããã®ã¢ãããŒã·ã§ã³ã¯ã©ã転ãã§ãå®çŸã§ããªã
- æéãæžãããã
- åäžããã±ãŒãžå ã«ããªãã®æ°ã®DBãåç §ãããã¹ããããå Žå
ãã ãæ®å¿µãªããšã« golang æšæºã® testing ããã±ãŒãžã«ã¯ beforeEach/afterEach ã¯ååšããŸããã
å®çŸããããšããå Žå㯠ginkgo ãªã©ã®ãã¹ãã£ã³ã°ãã¬ãŒã ã¯ãŒã¯ã䜿ã£ããããå¿
èŠãããã®ã§ããããå
¥ããã»ã©ã®ã¢ãããŒã·ã§ã³ã§ã¯ãªãããªãŒãšæããŸããã
çµè«
é 匵ã£ãŠããããã®ãã¹ãã±ãŒã¹å
ã§ãã©ã³ã¶ã¯ã·ã§ã³ã®éå§ãšRollbackãæžããã
ãã 颿°ãšããŠã¯æ¬¡ã®ããã«æžããŠããã
package db
func GetTestTransaction() *sql.Tx {
psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s",
os.Getenv("DB_HOST_TEST"), os.Getenv("DB_PORT"), os.Getenv("DB_USER"), os.Getenv("DB_PASSWORD"), os.Getenv("DB_NAME_TEST"), os.Getenv("DB_SSL"))
db, err := sql.Open("postgres", psqlInfo)
if err != nil {
panic(err.Error())
}
err = db.Ping()
if err != nil {
panic(err)
}
tx, _ := db.Begin()
return tx
}
// 䜿ãåŽ
func TestCreateAppSubscriptionOrders(t *testing.T) {
tx = db.GetTestTransaction()
/**********
ãã¹ãã®åŠç
**********/
tx.Rollback()
}
ãŸãšã
ç§ã®ãããžã§ã¯ãã§ã¯æ¬¡ã®ããã«çšæããããã«ããŸããã
- DBã€ã³ã¹ã¿ã³ã¹ã¯ããå°çšã®ã³ã³ãããç«ã¡äžãã
- ãã¹ãããŒã¿ã¯Seedã¯çšæãããã¡ã¯ããªé¢æ°ã®ã¿ã§äžæŠè¡ã
- ãã¹ãã®ã¯ãªãŒã³ãããã¯ãã©ã³ã¶ã¯ã·ã§ã³ãçšããŠè¡ã
ãã¡ãããããã¯ãã®æ§è³ªã«ãã£ãŠæ¹éãå€ãããšã¯æããŸãããäžæŠããã§é²ãã§ãããŸãã
ãèªã¿ããã ãããããšãããããŸããã
远èš
æ©éãã£ãŒãããã¯ããã ããŠæ¿ããæè¬ãªã®ã§ããããã¹ãã±ãŒã¹æ¯ã« db create, migration ãªã©ãè¡ãããšã«ãã£ãŠã¯ãªãŒã³ããããå®çŸãããšããã®ãè¯ãããã§ãã
ç§ã®ãã©ã³ã¶ã¯ã·ã§ã³ãçšããŠããæ¹æ³ã¯ãå
šãŠã®ãã¹ãã±ãŒã¹ã«å¯ŸããŠäžåã ãDBç«ãŠãããšãåæãšããæã«äžã
ãã¹ãã«äœ¿ã£ãããŒã¿ãæå®ããŠDELETEããã®ãé¢åã ã£ãããã§ããã®æ¹æ³ãªãè³æ»ã§æžããããtransactionãåºæ¥ãŠããããšã詊ããã¹ãããä»ã®ãã¹ããšéãæžãæ¹ããå®çŸã§ããã®ã§ãããªãšæããŸããã
seedã®åé€ã¯transactionã§ã¯ãã£ãŠãªãã§ãããšããã®ãtransactionãåºæ¥ãŠããããšã詊ããã¹ããããã®ã§ãããã§ãæ°åä»¶ãããã®ãã¹ãã2åãããã§å®äºããŠããã®ã§ãŸãåé¡ãªãããªããšæã£ãŠããããŠãŸã
â Yoshinori Kosaka (@wawoon_jp) December 1, 2019