Estou aprendendo o Go codificando um pequeno projeto pessoal. Mesmo sendo pequeno, decidi fazer testes de unidade rigorosos para aprender bons hábitos no Go desde o início.
Testes de unidade triviais foram excelentes e elegantes, mas estou intrigado com as dependências agora; Quero poder substituir algumas chamadas de função por outras simuladas. Aqui está um trecho do meu código:
func get_page(url string) string {
get_dl_slot(url)
defer free_dl_slot(url)
resp, err := http.Get(url)
if err != nil { return "" }
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil { return "" }
return string(contents)
}
func downloader() {
dl_slots = make(chan bool, DL_SLOT_AMOUNT) // Init the download slot semaphore
content := get_page(BASE_URL)
links_regexp := regexp.MustCompile(LIST_LINK_REGEXP)
matches := links_regexp.FindAllStringSubmatch(content, -1)
for _, match := range matches{
go serie_dl(match[1], match[2])
}
}
Eu gostaria de poder testar o downloader () sem realmente obter uma página através do http - ou seja, zombando de get_page (mais fácil, pois retorna apenas o conteúdo da página como uma string) ou http.Get ().
Encontrei este tópico: https://groups.google.com/forum/#!topic/golang-nuts/6AN1E2CJOxI, que parece ser sobre um problema semelhante. Julian Phillips apresenta sua biblioteca, Withmock ( http://github.com/qur/withmock ) como uma solução, mas não consigo fazê-la funcionar. Aqui estão as partes relevantes do meu código de teste, que é basicamente um código de culto para carga, para ser sincero:
import (
"testing"
"net/http" // mock
"code.google.com/p/gomock"
)
...
func TestDownloader (t *testing.T) {
ctrl := gomock.NewController()
defer ctrl.Finish()
http.MOCK().SetController(ctrl)
http.EXPECT().Get(BASE_URL)
downloader()
// The rest to be written
}
A saída de teste é a seguinte:
ERROR: Failed to install '_et/http': exit status 1
output:
can't load package: package _et/http: found packages http (chunked.go) and main (main_mock.go) in /var/folders/z9/ql_yn5h550s6shtb9c5sggj40000gn/T/withmock570825607/path/src/_et/http
O Withmock é uma solução para o meu problema de teste? O que devo fazer para que ele funcione?
fonte
Respostas:
Parabéns a você por praticar bons testes! :)
Pessoalmente, eu não uso
gomock
(ou qualquer estrutura de zombaria; zombar no Go é muito fácil sem ela). Eu passaria uma dependência para adownloader()
função como parâmetro, ou fariadownloader()
um método em um tipo, e o tipo pode conter aget_page
dependência:Método 1: Passe
get_page()
como um parâmetro dedownloader()
A Principal:
Teste:
Método2: Crie
download()
um método de um tipoDownloader
:Se você não deseja passar a dependência como parâmetro, também pode tornar
get_page()
um membro de um tipo e criardownload()
um método desse tipo, que pode usarget_page
:A Principal:
Teste:
fonte
Se você alterar sua definição de função para usar uma variável:
Você pode substituí-lo em seus testes:
Cuidado, porém, seus outros testes poderão falhar se eles testarem a funcionalidade da função que você substitui!
Os autores do Go usam esse padrão na biblioteca padrão do Go para inserir ganchos de teste no código para facilitar o teste:
https://golang.org/src/net/hook.go
https://golang.org/src/net/dial.go#L248
https://golang.org/src/net/dial_test.go#L701
fonte
Estou usando uma abordagem um pouco diferente, na qual os métodos de estrutura pública implementam interfaces, mas sua lógica é limitada apenas ao agrupamento de funções privadas (não exportadas) que levam essas interfaces como parâmetros. Isso fornece a granularidade de que você precisaria zombar de praticamente qualquer dependência e ainda ter uma API limpa para usar fora do seu conjunto de testes.
Para entender isso, é imperativo entender que você tem acesso aos métodos não exportados no seu caso de teste (ou seja, de dentro de seus
_test.go
arquivos) para testá-los, em vez de testar os exportados que não têm lógica interna além do empacotamento.Resumindo: teste as funções não exportadas em vez de testar as exportadas!
Vamos fazer um exemplo. Digamos que tenhamos uma estrutura da API do Slack, que possui dois métodos:
SendMessage
método que envia uma solicitação HTTP para um webhook do SlackSendDataSynchronously
método que, dado uma fatia de strings, itera sobre eles e pedeSendMessage
todas as iteraçõesEntão, para testar
SendDataSynchronously
sem fazer uma solicitação HTTP toda vez que precisaríamos zombarSendMessage
, certo?O que eu gosto nessa abordagem é que, olhando os métodos não exportados, você pode ver claramente quais são as dependências. Ao mesmo tempo, a API que você exporta é muito mais limpa e com menos parâmetros a serem repassados, pois a verdadeira dependência aqui é apenas o receptor pai que está implementando todas essas interfaces. No entanto, todas as funções dependem potencialmente de apenas uma parte (uma, talvez duas interfaces), o que facilita muito os refatores. É bom ver como o seu código é realmente acoplado apenas observando as assinaturas das funções; acho que ele é uma ferramenta poderosa contra o cheiro do código.
Para facilitar as coisas, coloquei tudo em um arquivo para permitir que você execute o código no playground aqui, mas sugiro que você também verifique o exemplo completo no GitHub, aqui está o arquivo slack.go e aqui o slack_test.go .
E aqui a coisa toda :)
fonte
Eu faria algo como,
a Principal
Teste
E eu evitaria
_
em golang. Melhor usar camelCasefonte
p := patch(mockGetPage, getPage); defer p.done()
. Eu sou novo, e estava tentando fazer isso usando aunsafe
biblioteca, mas parece impossível de fazer no caso geral.Aviso: Isso pode aumentar um pouco o tamanho do arquivo executável e custar um pouco de desempenho em tempo de execução. Na IMO, isso seria melhor se o golang tivesse recursos como macro ou decorador de funções.
Se você deseja simular funções sem alterar sua API, a maneira mais fácil é alterar um pouco a implementação:
Dessa forma, podemos realmente zombar de uma função das outras. Para mais conveniente, podemos fornecer esse tipo de clichê zombeteiro:
No arquivo de teste:
fonte
Considerando que o teste de unidade é o domínio dessa pergunta, é altamente recomendável que você use https://github.com/bouk/monkey . Este pacote faz com que você teste o teste sem alterar seu código-fonte original. Compare com outras respostas, é mais "não intrusivo"。
A PRINCIPAL
TESTE DE MOCK
O lado ruim é:
- Lembrado por Dave.C, esse método não é seguro. Portanto, não o use fora do teste de unidade.
- É não-idiomático Go.
O lado bom é:
++ Não é intrusivo. Faça você fazer as coisas sem alterar o código principal. Como Thomas disse.
++ Faça você alterar o comportamento do pacote (talvez fornecido por terceiros) com menos código.
fonte