Извршавам команду помоћу os/exec.Command() која је генерисала "XML" податке. Команда ће се извршавати у функцији која се зове GetData().
Да бих тестирао GetData(), имам неке тестне податке које сам направио.
У мом _test.go имам ТестГетДата који позива GetData(), али који ће користити os.exec, уместо тога бих волео да он користи моје тестне податке.
Који је добар начин да се то постигне? Приликом позивања GetData требам ли имати "тест" начин означавања тако да чита датотеку, тј. GetData (низ начина)?
Неколико ствари
- Када је нешто тешко тестирати, то је често због тога што раздвајање брига није сасвим у реду
- Не додавајте "тест моде" у свој код, уместо тога користите Dependency Injection тако да можете моделирати своје зависности и одвојити бриге.
Узео сам слободу да погодим како би код могао изгледати
type Payload struct {
Message string `xml:"message"`
}
func GetData() string {
cmd := exec.Command("cat", "msg.xml")
out, _ := cmd.StdoutPipe()
var payload Payload
decoder := xml.NewDecoder(out)
// these 3 can return errors but I'm ignoring for brevity
cmd.Start()
decoder.Decode(&payload)
cmd.Wait()
return strings.ToUpper(payload.Message)
}
- Користи
exec.Command
који вам омогућава извршавање спољне команде процеса - Снимамо излаз у
cmd.StdoutPipe
који нам враћаio.ReadCloser
(ово ће постати важно) - Остатак кода је мање -више копиран и залепљен из одличне документације.
- Снимимо било који излаз са стдоут -а у
io.ReadCloser
и затимStart
команду, а затим сачекамо да се сви подаци прочитају позивањемWait
. Између та два позива декодирамо податке у нашуPayload
структуру.
- Снимимо било који излаз са стдоут -а у
Here is what is contained inside msg.xml
<payload>
<message>Happy New Year!</message>
</payload>
Написао сам једноставан тест да то покажем на делу
func TestGetData(t *testing.T) {
got := GetData()
want := "HAPPY NEW YEAR!"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
Код за тестирање је одвојен и има једну намену. Мени се чини да постоје два главна проблема за овај код
- Преузимање сирових "XML" података
- Декодирање "XML" података и примена наше пословне логике (у овом случају
strings.ToUpper
на<message>
)
Први део је само копирање примера из стандардног либ -а.
Други део је где имамо своју пословну логику и гледајући код можемо видети где почиње "шав" у нашој логици; ту добијамо io.ReadCloser
. Можемо користити ову постојећу апстракцију да одвојимо забринутости и учинимо наш код тестираним.
Проблем са ГетДата -ом је што је пословна логика повезана са средствима за добијање "XML"-а. Да бисмо наш дизајн учинили бољим, морамо их одвојити
Наши TestGetData
могу деловати као наш тест интеграције између наше две бриге, па ћемо се тога држати како бисмо били сигурни да наставља да ради.
Ево како изгледа ново раздвојени код
type Payload struct {
Message string `xml:"message"`
}
func GetData(data io.Reader) string {
var payload Payload
xml.NewDecoder(data).Decode(&payload)
return strings.ToUpper(payload.Message)
}
func getXMLFromCommand() io.Reader {
cmd := exec.Command("cat", "msg.xml")
out, _ := cmd.StdoutPipe()
cmd.Start()
data, _ := ioutil.ReadAll(out)
cmd.Wait()
return bytes.NewReader(data)
}
func TestGetDataIntegration(t *testing.T) {
got := GetData(getXMLFromCommand())
want := "HAPPY NEW YEAR!"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
Сада када GetData
узима своје уносе само из io.Reader
-а, учинили смо га тестираним и више се не брине како се подаци преузимају; људи могу поново користити функцију са било чим што враћа io.Reader
(што је изузетно уобичајено). На пример, могли бисмо да почнемо са преузимањем "XML" -а са УРЛ -а уместо из командне линије.
func TestGetData(t *testing.T) {
input := strings.NewReader(`
<payload>
<message>Cats are the best animal</message>
</payload>`)
got := GetData(input)
want := "CATS ARE THE BEST ANIMAL"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
Ево примера јединичног теста за GetData
.
Одвајањем брига и употребом постојећих апстракција у оквиру Го тестирања, наша важна пословна логика је лака.