unit testing and mocking in golang

polaris · · 279 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>hello to all, i want to write some unit tests for this package:</p> <pre><code>package dirService func () Create(path string) bool { if !d.Exist(path) { err := os.Mkdir(path, 0777) if err!=nil{ return false } return true } return false } </code></pre> <p>But i dont want to create unnecessary directories while testing. </p> <p>(Yeah i know its not a good method to test but consider this a db inserter or equivalent. Dont want to write real db.)</p> <p>I did write some unit test in c# and java before, so i know that i have to use mock objects to achieve this, I checked on the internet for golang:</p> <ul> <li><a href="https://husobee.github.io/golang/testing/unit-test/2015/06/08/golang-unit-testing.html" rel="nofollow">https://husobee.github.io/golang/testing/unit-test/2015/06/08/golang-unit-testing.html</a> </li> <li><a href="https://jonathanmh.com/golang-unit-testing-for-absolute-beginners/" rel="nofollow">https://jonathanmh.com/golang-unit-testing-for-absolute-beginners/</a></li> </ul> <p>Implementation:</p> <p>First the interface:</p> <pre><code>type DirCreator interface { Create(path string) bool } </code></pre> <p>And a concrete struct like this:</p> <pre><code>type DirService struct{} func (d *DirService) Create(path string) bool { err := os.Mkdir(path, 0777) if err!=nil{ return false } return true } </code></pre> <p>And now i have to write a method that accepts this interface as a parameter in order to inject dependencies:</p> <pre><code>func Create(d DirCreator, path string) bool { return d.Create(path) } </code></pre> <p>Now i can create a &#34;mock&#34; struct that implements the interface :</p> <pre><code>type MockDirService struct { MockCreate func(path string) bool } func (m *MockDirService) Create(path string) bool { return m.MockCreate(path) } </code></pre> <p>And finally a unit test:</p> <pre><code>func TestCreate(t *testing.T) { var _ = Describe(&#34;Create&#34;, func() { var mockDirService = &amp;MockDirService{ MockCreate: func(path string) bool { //code to throws error return false }, } It(&#34;Should Return false when error occurs&#34;, func() { Expect(false, Create(mockDirService, &#34;/path/that/exists&#34;)) }) }) } </code></pre> <p>For this one method (Create) i implement a lot of codes to test, i&#39;m terrifying about implementing 3 or 4 methods. This is not fun, imo mocking is not easy in golang, is there another way to achieve this problem? i want to discuss this with experienced people out here.</p> <hr/>**评论:**<br/><br/>saturn_vk: <pre><p>I&#39;ve used goautomock to generate mock implementations for testing, and so far I&#39;m quite happy with it: <a href="https://github.com/ernesto-jimenez/goautomock" rel="nofollow">https://github.com/ernesto-jimenez/goautomock</a></p></pre>TheMerovius: <pre><p>My usual advice is &#34;Write fakes, not mocks&#34;. If nothing else, it makes writing Mocks easier. So, instead of creating a MockDirService, you write, e.g.</p> <pre><code>type FakeDirService struct { dirs map[string]bool } func NewFakeDirService() *FakeDirService { return &amp;FakeDirService{ dirs: make(map[string]bool), } } func (d *FakeDirService) Create(path string) bool { exists := d.dirs[path] d.dirs[path] = true return !exists } </code></pre> <p>You can then use this fake in any tests you have to get isolated behavior. If you want to inject errors, you can selectively mock out methods:</p> <pre><code>type MockDirService struct { CreateFunc func(path string) bool DirService } func (m *MockDirService) Create(path string) bool { if m.CreateFunc != nil { return m.CreateFunc(path) } return m.DirService.Create(path) } func TestFoo(t *testing.T) { f := NewFakeDirService() m := &amp;MockDirService{ DirService: f, CreateFunc: func(path string) bool { if path == &#34;/inject/an/error&#34; { return false } return f.Create(path) }, } DoAThing(m) } </code></pre> <p>Now, all of that being said: In Go, there usually are no &#34;canonically correct patterns&#34;. In general, you should try to use the actual dependencies as much as possible and use fake implementations as far down the stack as you can. That way, you prevent a lot of bad test-outcomes, like testing implementation, instead of testing behavior, or testing your <em>Fake</em>, instead of testing your production code. Especially, if the thing you are faking has significant business-logic that you&#39;d need to duplicate - in that case, it would make more sense, to use the real deal, but maybe fake out <em>its</em> dependencies.</p> <p>For example, if you are using a Database, it probably makes more sense, to start the actual database on a temporary directory and pass a connection to it to your production code, instead of creating a fake (or mock). In particular, I would only use mocks if you want to test the error-handling (and don&#39;t just inject errors to enumerate to your error-handling paths, but check actual pathological error conditions, like interruptions or corrupt data).</p> <p>Writing good tests is hard. And there isn&#39;t &#34;the right&#34; way to do it, sadly.</p> <p>[edit] oh and to illustrate further: In your case, you probably should neither fake, nor mock, but just create a temporary directory and point your production code at that for all its fs-operations.</p></pre>drvd: <pre><p>Please No! Stop it! This hurts!</p> <p>You have a function which&#39;s purpose is to create a directory or fail in certain conditions. If you want to test that you will <em>have</em> to create a directory. Stop that mock nonsense and that &#34;unit test must not access whatever&#34; crap. If the sole purpose of the unit under test is writing to the filesystem you have to write to the filesystem.</p> <p>The go tool handles the directory &#34;testdir&#34; especially: You can add test files and directories to it and it is not considered a package. Very handly. Set up a file structure which contains all the cases you are interested in testing, like &#34;directory does not exist but parent is write protected&#34; or &#34;directory already exists&#34;. Then write test. The only thing to remember is to clean up created stuff. That&#39;s simple, defer that code.</p> <p>Take a look at how the standard library of Go does it&#39;s testing. There is no ultrasharp and clear border between &#34;unit&#34; and &#34;integration&#34; testing. Just stay sensible.</p></pre>

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

279 次点击  
加入收藏 微博
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传