Tests that require DB access and code structure

blov · · 790 次点击    
这是一个分享于 的资源,其中的信息可能已经有所发展或是发生改变。
<p>I&#39;m still new and trying to figure out how to do integration tests that require DB access for the past 2-3 days already and I&#39;ve got nowhere. I dont know how to structure my code for such tests.</p> <p>This is how I usually structure my code if I dont do tests and it sticked from a codebase I found on github:</p> <p><a href="https://play.golang.org/p/xUwpgnIU2Ko">https://play.golang.org/p/xUwpgnIU2Ko</a></p> <p>If anyone can give me some advice like how to structure it better, make it easier for testing then I&#39;d really appreciate it. I&#39;m self learning and having bit hard time with testing</p> <hr/>**评论:**<br/><br/>sacrehubert: <pre><p>Try using the <a href="https://en.wikipedia.org/wiki/Data_access_object">DAO pattern</a>. The basic idea is that your database object should be wrapped in an interface that exposes business-logic operations. For instance:</p> <pre><code>type DAO interface { FetchWidget() (*Widget, error) NewWidget() (*Widget, error) StoreWidget(*Widget) error } </code></pre> <p>You would then implement something like a <code>postgresDAO</code> that satsifies <code>DAO</code>. </p> <p>The win for you is that you can create implementations of your DAO for testing. These don&#39;t have to be backed with databases at all (which is preferable in many cases).</p></pre>hybsuns: <pre><p>I believe there are some serious issues in the architecture of your project which contribute to the difficulty of testing.</p> <p>Firstly, you put you entity <code>Movie</code> and the data access code <code>func (m *movieRepo) Read() ([]Movie, error)</code> in the same layer of the system. This is generally a bad design since <code>Movie</code> is the business logic and <code>func (m *movieRepo) Read() ([]Movie, error)</code> is a detail (data access). They should be put into separated packages (at least).</p> <p>The second issue is that your data access code is tightly coupled with package <code>app</code>. This is not desirable since your data access code is already a low level module and deals with I/O. In your case, you cannot instantiate a repository unless you instantiate an <code>App</code>. However, you <code>App</code> is really just a connection to the database. I would question if adding such a layer of abstraction is necessary.</p> <p>I believe your data access code should be structured like this:</p> <p><code>package dataaccess</code></p> <p><code>type PostgresMovieRepository struct {</code></p> <p><code>Database *sql.DB</code></p> <p><code>}</code></p> <p><code>func (m PostgresMovieRepository) Read() ([]Movie, error) {</code> </p> <p><code>// query movies</code></p> <p><code>return movies, nil</code></p> <p><code>}</code></p> <p>In your test code, you can do something like this: </p> <p><code>package dataaccess_test</code></p> <p><code>var movieRepo = dataaccess.PostgresMovieRepository{</code><br/> <code>Database: CreateYourDBConnectionHere(),</code><br/> <code>}</code><br/> <code>func TestPostgresMovieRepository_Read(t *testing.T) {</code></p> <p><code>movies, err :=</code> <a href="https://movieRepo.Read"><code>movieRepo.Read</code></a><code>()</code></p> <p><code>// add your assertion statements here</code></p> <p><code>}</code></p> <p>Note that your connection can be a actual DB connection or a mock connection.</p> <p>I would suggest looking into dependency inversion principle (the D in SOLID principle). This principle can guide you how to structure your code.</p></pre>never-_-laugh: <pre><p>Have you considered using docker? You could stand up a docker env and populate the db with sample data to run your tests. This would just get destroyed every time and keep your live db safe. There is obviously a lot more detail to this and setting it up would require you to familiarize with docker, docker-compose, and maybe write a bash script. The end result is a good way to reproduce a similar env as what your production env looks like and allow you to keep the live db clean.</p></pre>f12_amish: <pre><p>I&#39;m just getting into docker myself and wondered how you give the app access to a docker container. Do you just setup docker on a specified port and have a static DB URL where the app checks for the database?</p></pre>bschwind: <pre><blockquote> <p>Do you just setup docker on a specified port and have a static DB URL where the app checks for the database?</p> </blockquote> <p>This is pretty much how it works. Docker containers can bind to ports on the host network, so a postgres container can bind to port 5432 or any other port you want to specify, and your app will just connect to it like any other database endpoint.</p></pre>never-_-laugh: <pre><p>That is one approach. You can also create a docker network but that may be more than what you need. I would definitely recommend reading the the latest docker docs. I would also say be careful with some of the tutorial people post. Not that they are bad but be mindful of the version of docker-compose they use. There have been added features that change the ideal work flow with docker-compose. So I would try to stick with the official docs or anything that is at least posted very recent.</p></pre>earthboundkid: <pre><p><a href="https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1" rel="nofollow">https://medium.com/@benbjohnson/standard-package-layout-7cdbc8391fc1</a></p></pre>p4r14h: <pre><p>So in practice, when you have multiple environments like dev, staging (integ) and prod you’d have a config class that stores all the runtime configuration (like your SQL DSN). You can then set an ENV var like ‘COMPANY_ENV=production’ which would affect which config file is loaded and serialized into your config class. </p> <p>For instance, we have:</p> <p>base.yaml dev.yaml production.yaml</p> <p>In my dev.yaml I have a set of nested structures: ‘datastore.mysql.dsn’</p> <p>In my main this file is loaded and turned into a config struct that is passed around (similar to your app type) and in my db.go I use Gorm to either create a local SQLite DB for dev or give it a remote connection string for staging/production. </p> <p>For integration tests you probably want to use the same SQL server as production so you’d need a separate SQL database available in your staging environment. When you start the test suite you can bootstrap the DB and schemas- the only difference is you’d pass in a staging config struct then execute the code you want to test and verify the expected result. </p></pre>


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

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