<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Unit Test &#8211; Few Steps &#8211; ก้าวสั้นๆ แต่ไปเรื่อยๆ</title>
	<atom:link href="https://myifew.com/tag/unit-test/feed/" rel="self" type="application/rss+xml" />
	<link>https://myifew.com</link>
	<description></description>
	<lastBuildDate>Thu, 20 Dec 2018 09:28:34 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.4</generator>

<image>
	<url>https://myifew.com/wp-content/uploads/2018/07/cropped-logo6-ts-32x32.png</url>
	<title>Unit Test &#8211; Few Steps &#8211; ก้าวสั้นๆ แต่ไปเรื่อยๆ</title>
	<link>https://myifew.com</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>มาลอง Code Coverage บน  .Net Core 2+ ด้วย coverlet</title>
		<link>https://myifew.com/5145/code-coverage-on-net-core-2-via-coverlet/</link>
					<comments>https://myifew.com/5145/code-coverage-on-net-core-2-via-coverlet/#respond</comments>
		
		<dc:creator><![CDATA[iFew]]></dc:creator>
		<pubDate>Thu, 20 Dec 2018 09:09:35 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[Automation Testing]]></category>
		<category><![CDATA[Automation Tools]]></category>
		<category><![CDATA[Code Coverage]]></category>
		<category><![CDATA[Code Quality]]></category>
		<category><![CDATA[Testing Tools]]></category>
		<category><![CDATA[Unit Test]]></category>
		<guid isPermaLink="false">https://myifew.com/?p=5145</guid>

					<description><![CDATA[ใครที่เขียนโค้ดด้วย .Net Core 2+ และทำ CI/CD บน Linux ค่อนข้างยุ่งยากหน่อย เพราะหาเครื่องมือสำหรับตรวจสอบ Code Coverage ฟรีๆ ดีๆ ได้น้อยเหลือเกิน ที่ผมเคยใช้ดีๆ ก็มี minicover&#8230;]]></description>
										<content:encoded><![CDATA[
<p>ใครที่เขียนโค้ดด้วย .Net Core 2+ และทำ CI/CD บน Linux ค่อนข้างยุ่งยากหน่อย เพราะหาเครื่องมือสำหรับตรวจสอบ Code Coverage ฟรีๆ ดีๆ ได้น้อยเหลือเกิน ที่ผมเคยใช้ดีๆ ก็มี <a rel="noreferrer noopener" aria-label="minicover (opens in a new tab)" href="https://github.com/lucaslorentz/minicover" target="_blank">minicover</a> จนเพิ่งมาลองเล่นอีกตัว ชื่อ <a rel="noreferrer noopener" aria-label="coverlet (opens in a new tab)" href="https://github.com/tonerdo/coverlet" target="_blank">coverlet</a> ใช้งานได้ง่ายกว่าเยอะมาก เพราะมันทำงานร่วมกับ MSTest ได้เลย</p>



<span id="more-5145"></span>



<h2 class="wp-block-heading">วิธีติดตั้ง</h2>



<p>การใช้งานมี 2 แบบครับ คือใช้คำสั่ง coverlet ทำงานเลย ให้ติดตั้งด้วยคำสั่ง</p>



<pre class="wp-block-preformatted">dotnet tool install --global coverlet.console</pre>



<p>หรือทำงานผ่านคำสั่ง dotnet test ให้เพิ่ม nuget package ด้วยคำสั่ง</p>



<pre class="wp-block-preformatted">dotnet add package coverlet.msbuild</pre>



<h2 class="wp-block-heading">วิธีใช้งาน</h2>



<p>ในตัวอย่างนี้ โครงสร้างโปรเจ็คผมจะหน้าตาตามรูปภาพด้านล่าง และผมเข้าจะยกตัวอย่างคำสั่งทั้งหมดกรณีที่อยู่ในโฟลเดอร์ /tests/web.UnitTest/<br></p>



<figure class="wp-block-image is-resized"><img fetchpriority="high" decoding="async" src="https://myifew.com/wp-content/uploads/2018/12/netcore2-project-structure-453x1024.png" alt="" class="wp-image-5146" width="227" height="512"/></figure>



<p>ดังนั้น เมื่อต้องการดู Code Coverage ผ่านคำสั่ง coverlet</p>



<pre class="wp-block-verse">coverlet <strong>bin/Debug/netcoreapp2.1/api.dll</strong> --target "dotnet" --targetargs "<strong>test web.UnitTest.csproj --no-build</strong>" --output "<strong>../web.UnitTest.Results/Coverage.xml</strong>"&nbsp;--format <strong>opencover</strong></pre>



<p>ซึ่งตัวหนาคือข้อมูลของโปรเจ็คเราเอง โดยผู้อ่านสามารถแก้ไขตามที่จะนำไปใช้งานได้เลย</p>



<ul class="wp-block-list"><li><strong>bin/Debug/netcoreapp2.1/api.dll</strong> : คือ ที่อยู่ของไฟล์ Unit Test Binary ที่เรา Build มาแล้ว</li><li><strong>test web.UnitTest.csproj &#8211;no-build</strong> : คือ arguments เพื่อบอกว่าเราต้องการทดสอบด้วย MSTest ไปที่ File Project ไหน, และผมเพิ่มไปว่า ไม่ต้อง ทำการ build project ใหม่ เพราะผมสั่ง build ไปแล้ว (ถ้าใครไม่สั่ง build ก่อน ให้เอา &#8211;no-build ออกด้วยนะ)</li><li><strong>../web.UnitTest.Results/Coverage.xml</strong> : คือ ที่อยู่ของไฟล์ report ซึ่งผมให้ใช้เป็น Formet ของ OpenCover จึงจ้องมีนามสกุล .xml (ค่ามาตรฐาน ถ้าไม่ระบุ มันจะเป็น JSON)</li><li></li><li><strong>opencover</strong> : คือ Format ของไฟล์ report ที่เราจะนำไปใช้งานต่อ</li></ul>



<p>ลองมาดูการใช้งานผ่านคำสั่ง dotnet test แบบปกติบ้าง</p>



<pre class="wp-block-preformatted">dotnet test --no-restore --no-build --logger:"trx;LogFileName=Results.trx" --results-directory:"../web.UnitTest.Results" <strong>/p:CollectCoverage=true</strong> /p:CoverletOutput="<strong>../web.UnitTest.Results/Coverage.xml</strong>" /p:CoverletOutputFormat=<strong>opencover</strong></pre>



<p>ถ้าให้เดาค่าต่างๆ คงพอเดากันได้นะครับ ระบุเหมือนด้านบนเลย แต่คำสั่งหนึ่งที่ต้องมีเสมอคือ</p>



<pre class="wp-block-preformatted">/p:CollectCoverage=true </pre>



<p>เพื่อบ่งบอกว่า เราต้องการ Generate Code Coverage ออกมาด้วย</p>



<h2 class="wp-block-heading">การแสดงผล</h2>



<p>เมื่อรันคำสั่งตามด้านบนเรียบร้อยแล้ว จะได้หน้าตาประมาณนี้ครับ</p>



<figure class="wp-block-image"><img decoding="async" width="1200" height="540" src="https://myifew.com/wp-content/uploads/2018/12/Screen-Shot-2561-12-20-at-13.50.06-1200x540.png" alt="" class="wp-image-5147" srcset="https://myifew.com/wp-content/uploads/2018/12/Screen-Shot-2561-12-20-at-13.50.06-1200x540.png 1200w, https://myifew.com/wp-content/uploads/2018/12/Screen-Shot-2561-12-20-at-13.50.06-1024x461.png 1024w, https://myifew.com/wp-content/uploads/2018/12/Screen-Shot-2561-12-20-at-13.50.06-768x346.png 768w, https://myifew.com/wp-content/uploads/2018/12/Screen-Shot-2561-12-20-at-13.50.06-600x270.png 600w, https://myifew.com/wp-content/uploads/2018/12/Screen-Shot-2561-12-20-at-13.50.06.png 1990w" sizes="(max-width: 1200px) 100vw, 1200px" /></figure>



<p>ความหมายที่ได้จากการแสดงผล คือ</p>



<ul class="wp-block-list"><li><strong>Module</strong> : ชื่อ System ที่มันทำการทดสอบ</li><li><strong>Method coverage</strong> : จำนวนของ Function/Method ที่ถูกเรียกใช้งาน</li><li><strong>Branch coverage</strong> : จำนวนของ Branch หรือเงื่อนไขใน if statement ถูกเรียกใช้งานทั้ง true และ false</li><li><strong>Line coverage</strong> : จำนวนของบรรทัดที่ถูกเรียกใช้งาน</li></ul>



<pre class="wp-block-preformatted"><em>อ้างอิงคำอธิบายจาก <a href="http://www.somkiat.cc/introduction-to-code-coverage/">http://www.somkiat.cc/introduction-to-code-coverage/</a></em></pre>



<h2 class="wp-block-heading">กำหนดคุณภาพ ด้วยการตั้ง Threshold</h2>



<p>คราวนี้ ถ้าเราอยากตั้งค่าว่า ถ้า Code Coverage เรามี Test ครอบคลุมต่ำกว่า xx% เราจะไม่ให้ผ่านการทดสอบ โดยเพิ่ม Argument ชื่อ threshold ลงไป ดังนี้ </p>



<p>ตัวอย่างด้วยคำสั่ง coverlet</p>



<pre class="wp-block-preformatted">coverlet bin/Debug/netcoreapp2.1/api.dll --target "dotnet" --targetargs "test web.UnitTest.csproj --no-build" --output "../web.UnitTest.Results/Coverage.json" <strong>--threshold 80</strong></pre>



<p>ตัวอย่างด้วยคำสั่ง dotnet test</p>



<pre class="wp-block-preformatted">dotnet test --no-restore --no-build --logger:"trx;LogFileName=Results.trx" --results-directory:"../web.UnitTest.Results" /p:CollectCoverage=true /p:CoverletOutput="../web.UnitTest.Results/Coverage.xml" /p:CoverletOutputFormat=opencover <strong>/p:Threshold=80 /</strong></pre>



<p>ซึ่งในตัวอย่างผมกำหนดว่า Coverage ทุกประเภทจะต้องเกิน 80% ถึงจะเรียกว่าผ่าน ถ้าต่ำกว่านั้นจะ Fail ทันที ดังรูปด้านล่าง</p>



<figure class="wp-block-image"><img decoding="async" width="1200" height="594" src="https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-1-1200x594.png" alt="" class="wp-image-5149" srcset="https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-1-1200x594.png 1200w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-1-1024x507.png 1024w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-1-768x380.png 768w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-1-600x297.png 600w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-1.png 2134w" sizes="(max-width: 1200px) 100vw, 1200px" /></figure>



<p>และเราสามารถกำหนดเฉพาะ Coverage ได้ด้วย เช่นผมต้องการเช็คเฉพาะ Line Coverage ก็สามารถเพิ่ม Argument เข้าไป ดังนี้</p>



<p>ตัวอย่างด้วยคำสั่ง coverlet</p>



<pre class="wp-block-preformatted"><strong>--threshold-type line</strong></pre>



<p>ตัวอย่างด้วยคำสั่ง dotnet test</p>



<pre class="wp-block-preformatted"> <strong>/p:ThresholdType=line</strong></pre>



<p>ผลที่ได้คือ</p>



<figure class="wp-block-image"><img loading="lazy" decoding="async" width="1200" height="509" src="https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-by-type-1200x509.png" alt="" class="wp-image-5150" srcset="https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-by-type-1200x509.png 1200w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-by-type-1024x434.png 1024w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-by-type-768x326.png 768w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-by-type-600x254.png 600w, https://myifew.com/wp-content/uploads/2018/12/coverlet-under-threshold-standard-by-type.png 2142w" sizes="auto, (max-width: 1200px) 100vw, 1200px" /></figure>



<h2 class="wp-block-heading">สรุป</h2>



<p>ถ้าเทียบกับ minicover แล้ว ตัวนี้ค่อนข้างใช้งานง่าย ไม่ซับซ้อนมากนัก รองรับ .Net Core 2.2.1 ตัวล่าสุดด้วย และสามารถแจกแจงรายละเอียด ตาม Coverage Type ต่างๆ พร้อมออก Report ได้ เช่น json, lcov, opencover (xml file), cobertura (xml file) และ teamcity ถ้าสนในรายละเอียดมากกว่านี้ ไปดูได้ที่ Github ของผู้พัฒนาได้เลยครับ ที่ https://github.com/tonerdo/coverlet</p>



<p><em>Featured image from: https://www.aapnainfotech.com/test-coverage-much-testing-enough</em>/</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myifew.com/5145/code-coverage-on-net-core-2-via-coverlet/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>เมื่อ Unit Test ต้องทดสอบร่วมกับระบบอื่นๆ จะแก้ปัญหาอย่างไร?.. มารู้จัก Test Doubles กัน</title>
		<link>https://myifew.com/4556/how-to-skip-dependency-in-unit-test-by-test-doubles/</link>
					<comments>https://myifew.com/4556/how-to-skip-dependency-in-unit-test-by-test-doubles/#respond</comments>
		
		<dc:creator><![CDATA[iFew]]></dc:creator>
		<pubDate>Thu, 05 Jul 2018 18:21:41 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[.Net Core]]></category>
		<category><![CDATA[Automation Testing]]></category>
		<category><![CDATA[Test Doubles]]></category>
		<category><![CDATA[Testable]]></category>
		<category><![CDATA[Unit Test]]></category>
		<guid isPermaLink="false">https://myifew.com/?p=4556</guid>

					<description><![CDATA[หลังจากเขียนเรื่อง Unit Test ไปก็หลายบล็อก ไล่ตั้งแต่เรื่องพื้นฐานอย่าง การออกแบบระบบให้รองรับการทดสอบ (Testable) มาจนถึง วิธีการคิดและลงมือทำ Automated Test ด้วย Unit Test และ รูปแบบการทดสอบของ Unit Test ก็ยังเหลือเรื่องหนึ่งที่สำคัญมากและหลายคนอาจจะสงสัยตอนที่ทำ Unit Test นั่นก็คือ "จะทำอย่างไร เมื่อสิ่งที่เราทดสอบ อย่าง Class, Function มันต้องไปทำงานร่วมกับสิ่งอื่นๆ เช่น Class อื่นๆ, Function อื่นๆ, Database, API ตัวอื่นๆ ของเรา หรือ API ภายนอกระบบ" ..]]></description>
										<content:encoded><![CDATA[<p>หลังจากเขียนเรื่อง Unit Test ไปก็หลายบล็อก ไล่ตั้งแต่เรื่องพื้นฐานอย่าง <a href="https://myifew.com/4414/design-project-code-structure-for-testable/">การออกแบบระบบให้รองรับการทดสอบ (Testable)</a> มาจนถึง <a href="https://myifew.com/4455/design-and-develop-code-for-testable/">วิธีการคิดและลงมือทำ Automated Test ด้วย Unit Test</a> และ <a href="https://myifew.com/4480/unit-test-solitary-or-sociable/">รูปแบบการทดสอบของ Unit Test</a> ก็ยังเหลือเรื่องหนึ่งที่สำคัญมากและหลายคนอาจจะสงสัยตอนที่ทำ Unit Test นั่นก็คือ &#8220;จะทำอย่างไร เมื่อสิ่งที่เราทดสอบ อย่าง Class, Function มันต้องไปทำงานร่วมกับสิ่งอื่นๆ เช่น Class อื่นๆ, Function อื่นๆ, Database, API ตัวอื่นๆ ของเรา หรือ API ภายนอกระบบ&#8221; ..<span id="more-4556"></span></p>
<h2>ทบทวน &#8220;5 คุณสมบัติของ Unit Test ที่ดี&#8221; อีกครั้ง</h2>
<p>ก่อนจะไปต่อ ขอทบทวนคุณสมบัติที่ดีของ Unit Test อีกรอบ ที่ควรจะต้องเป็นไปตาม<a href="http://www.somkiat.cc/good-unit-test-with-first/" target="_blank" rel="noopener">หลักการของ FIRST</a> คือ</p>
<ul>
<li><strong>Fast:</strong> ทำงานได้เร็ว</li>
<li><strong>Isolated:</strong> เป็นอิสระจากกัน</li>
<li><strong>Repeatable:</strong> ทำซ้ำได้ ไม่ขึ้นกับระบบอื่นๆ เช่น API ภายนอก, Database, File System</li>
<li><strong>Self-Verify:</strong> แสดงให้เห็นผลการทดสอบ ผ่าน หรือไม่ผ่านได้อย่างชัดเจน</li>
<li><strong>Timely:</strong> เขียนให้ถูกเวลา คือ ควรมี Test ก่อน เพื่อให้ได้เข้าใจปัญหา และจึงเขียนโค้ดเข้ามาแก้ปัญหานั้น</li>
</ul>
<p>คราวนี้ การที่ Unit Test ของเราต้องไปทำงานร่วมกับสิ่งอื่นๆ มันมักทำให้ขัดแย้งกับ 5 ข้อ ข้างต้น เช่น</p>
<ul>
<li>เมื่อเราต้องการทดสอบ Function ที่ต้องต่อกับ Database มันก็จะทำงานไม่เร็ว เพราะ Query นาน, มันทำซ้ำไม่ได้ เพราะต้องเพิ่มลบข้อมูลจาก Database ตลอด แล้วไหนจะกระทบตัวอื่นๆที่อาจจะใช้ Database เดียวกันด้วย</li>
<li>เมื่อเราต้องการทดสอบ Function ที่ต้องต่อกับ API ภายในของเราเอง หรือภายนอก มันก็ทำงานไม่เร็ว เพราะมีเรื่องของความเร็ว Network/Internet ซึ่งถ้า Internet ล่ม ก็ทำซ้ำไม่ได้ หรือถ้าส่งข้อมูลไปให้แล้วบาง API ก็ส่งข้อมูลซ้ำไม่ได้ เช่น ส่งข้อมูลไป API สมัครสมาชิกที่มีเงื่อนไขสมัครด้วยอีเมลเดียวเท่านั้น</li>
<li>เมื่อเราต้องการทดสอบ Function ที่ต้องไปทำงานร่วมกับ Function อื่น แต่มันยังทำไม่เสร็จหรือไม่ได้ทำ เราก็ทดสอบไม่ได้อยู่ดี</li>
</ul>
<p>ดังนั้น ถ้าต้องการให้ Unit Test ของเราทำงานได้ตามคุณสมบัติดังกล่าว เราจะต้องสร้างการทำงานสิ่งอื่นๆที่เกี่ยวข้องขึ้นมา ซึ่ง จะมีอยู่  5 ประเภท เรียกรวมๆ ว่า Test Doubles</p>
<h2>Test Doubles คืออะไร</h2>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone size-large wp-image-4565 aligncenter" src="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-1200x530.png" alt="" width="1024" height="452" srcset="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-1200x530.png 1200w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-1024x452.png 1024w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-768x339.png 768w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-600x265.png 600w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-700x309.png 700w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles.png 1296w" sizes="auto, (max-width: 1024px) 100vw, 1024px" />Image from: https://blogs.sap.com/</p>
<p>จริงๆ เราอาจเคยทำมันมาแล้วอย่างใดอย่างหนึ่ง แต่เรามักเรียกกันตามแต่ถนัด เช่น Mock, ระบบจำลอง, ระบบทดสอบ ฯลฯ อะไรก็ว่าไป แต่ที่พบคือ Mock มักถูกใช้บ่อยสุด</p>
<p>คำว่า Test Doubles มาจากหนังสือที่ลุง <a href="http://xunitpatterns.com/Test%20Double%20Patterns.html">Gerard Meszaros</a> เขาเขียนมันขึ้นมา โดยแปลงมาจากคำว่า Stunt Double ที่ใช้ในการทำหนัง ซึ่ง Stunt Double คือ สตั๊นแมนหรือนักแสดงที่มีหน้าตา รูปร่าง แต่งตัวคล้ายกับพระเอก แล้วออกไปแสดงแทนพระเอกในบางฉาก</p>
<p>คราวนี้พอมาเป็น Test Doubles โดยอธิบายมุมเดียวกับ Stunt Double มันก็คือ อะไรบางอย่างที่เราทำมันขึ้นมาเพื่อให้มันทำงานแทนของจริง ในขณะที่เราไม่ได้ต้องการทดสอบมัน อย่างเช่น API ของคนอื่น, Class/Function ของคนอื่น, Database  เป็นต้น</p>
<h2>Test Doubles 5 ประเภท มีอะไรบ้าง</h2>
<p>อย่างที่บอก ว่าเราเคยเรียกทุกสิ่งที่ถูกจำลองว่า Mock  แต่เมื่อพิจารณาดีๆ มันมีจุดแตกต่างกัน ซึ่งเราจะใช้การเรียกประเภทต่างๆ อ้างอิงตามที่ลุง Gerard Meszaros ได้เขียนอธิบายไว้ และทั่วโลกก็ใช้เรียกตามนี้เช่นกัน ดังนั้นเวลาใช้สื่อสารเราจะได้เข้าใจตรงกัน</p>
<p>ปล. ตัวอย่างที่จะใช้ต่อไปนี้ เขียนด้วย .Net Core 2</p>
<h3>1. Dummy</h3>
<p>&nbsp;</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4572 aligncenter" src="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Dummy.png" alt="" width="722" height="271" srcset="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Dummy.png 722w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Dummy-600x225.png 600w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Dummy-700x263.png 700w" sizes="auto, (max-width: 722px) 100vw, 722px" /></p>
<p>แปลตรงตัวคือ หุ่นเชิด, ของหลอกตา, คนโง่ เหมือนกับที่เคยมีข่าวว่ากล้องดัมมี่ คือมีแต่กล่องแปะไว้หลอกตาก็อุ่นใจ เช่นกัน ใน Test Doubles มันคือ อะไรก็ได้ ที่ใส่ไปแล้วระบบเราสามารถทำงานได้ หรือคอมไพล์ผ่าน</p>
<p>ถามว่าโง่ได้ขนาดไหน ก็เช่น สร้าง Class เปล่าๆ หรือ Function ที่คืนค่า null หรือ throw exception ก็ยังได้ โดยไม่ต้องสนใจว่ามันทำงานอย่างไร เราสนแค่ว่า ตัวที่เราจะทดสอบสามารถเรียกมันแล้วผ่านไปได้โดยไม่ติดขัด</p>
<p><strong>ตัวอย่าง Dummy</strong></p>
<p>ผมมีโค้ดสำหรับการ Login โดย Constructor Member() จะเป็นส่ง Username/Password ไปตรวจสอบ ซึ่งถ้าถูกต้อง ก็จะทำงานต่างๆ ได้ เช่น แสดงข้อความ &#8220;Welcome to member area&#8221; ใน method Profile()</p>
<p>View the code on <a href="https://gist.github.com/ifew/e9a674894ac6f3101f2c11ea36d47544">Gist</a>.</p>
<p>ผมสร้าง Interface ขึ้นมาชื่อ IAuthorize และคลาสที่ถูกนำไปใช้งานคือ Authorize โดยภายในนั้นมี CheckAuthorize() ซึ่งจะทำการตรวจสอบว่า Login ถูกต้องหรือไม่</p>
<p>และผมสร้างคลาสอีกตัวชื่อ DummyAuthorize เพื่อที่จะใช้ทดสอบ โดยผมไม่ได้ return อะไรออกไปว่าเป็น true/false มีเพียงส่งค่าว่างเปล่าประเภท Boolean ออกไป</p>
<p>View the code on <a href="https://gist.github.com/ifew/a5c31b52c3488e5c65d97cef13d78e48">Gist</a>.</p>
<p>มาดูในไฟล์ Unit Test ซึ่งผมต้องการตรวจสอบว่าสามารถสร้าง Object ของ Member ได้หรือไม่ แต่ติดว่า มันต้องใส่ Username/Password ใน Constructor ด้วยทุกครั้ง ดังนั้น ผมจึงใช้ DummyAuthorize แทน เพื่อให้มันทำงานทะลุผ่านไปได้</p>
<p>View the code on <a href="https://gist.github.com/ifew/1d96adc587c5d57a0e4b7c28a4fb57ed">Gist</a>.</p>
<p>นี่แหละครับ การทำงานของ Dummy ใส่อะไรไปแล้วใช้แทน Dependency ตัวอื่นๆ ได้ เพื่อทะลุไปทำงานอย่างอื่นต่อตามที่ต้องการ ซึ่งในตัวอย่างนี้ ผมลองเช็ค Type Object  Member ดู</p>
<h3>2. Stub</h3>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4564 aligncenter" src="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Stub.png" alt="" width="777" height="271" srcset="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Stub.png 777w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Stub-600x209.png 600w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Stub-768x268.png 768w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Stub-700x244.png 700w" sizes="auto, (max-width: 777px) 100vw, 777px" /></p>
<p>Stub คือการกำหนดสถานะหรือสิ่งต่างๆให้เป็นไปตามที่เราต้องการ (<strong>State verification</strong>) โดยหน้าตาจะคล้ายกับ Dummy แต่ Stub เราจะต้องกำหนดค่าที่จะใช้ด้วย เพื่อให้ได้ผลลัพธ์ออกมาตามที่เราต้องการ</p>
<p>เช่น Test Case ของสมการ 1 + X จะต้องได้เท่ากับ 2 โดยที่ X คือ Function ภายนอกที่เราไปเรียกใช้ และผลลัพธ์จะต้องออกมาเป็น 2 เสมอ ดังนั้น เราจะต้องกำหนดค่า X ว่าต้องเป็น 1 เท่านั้น (ซึ่งต่างจาก Dummy ที่ไม่สนใจการระบุค่า)</p>
<p>และที่เราชอบเรียกกันว่า Mock ส่วนมากคือการทำ Stub นี่เอง เพราะเราเขียนโค้ดขึ้นมาเพื่อให้ได้ค่าอย่างใดอย่างหนึ่งนำไปใช้งานต่อ และแสดงผลลัพธ์ปลายทางที่เราต้องการ</p>
<p><strong>ตัวอย่าง Stub</strong></p>
<p>ผมอ้างอิงจากโค้ดชุดเก่าของ Dummy แต่เปลี่ยน Unit Test ใหม่ โดยผมต้องการตรวจสอบว่า เมื่อเรียกใช้ Profile() จะต้องแสดงข้อความ &#8220;Welcome to member area&#8221; ขึ้นมา</p>
<p>View the code on <a href="https://gist.github.com/ifew/2a112ce61cb112ffec6f997aca415403">Gist</a>.</p>
<p>และเช่นกัน ผมสร้าง Class ชื่อ <span class="pl-en">StubAuthorize</span> จาก <span class="pl-en">IAuthorize </span>และใน CheckAuthorize() จะระบุเป็น true เสมอ เพื่อให้ Unit Test ผมแสดงข้อความ <span class="pl-pds">&#8220;</span>Welcome to member area<span class="pl-pds">&#8221; ขึ้นเสมอ</span></p>
<p>View the code on <a href="https://gist.github.com/ifew/0825095f960938e5dd3c7b60df80f786">Gist</a>.</p>
<p>อาจจะมีความสงสัยว่า ถ้าเราใส่ Username/Password เข้าไปตอนสร้าง object Member เลยได้ไหม อย่างไรก็คืนค่าเป็น  true เสมอ ตอบเลยว่าได้ แต่มีจุดให้คิดเพิ่ม คื อ</p>
<ol>
<li>เราต้องการตรสวจสอบการเรียกใช้ Profile() ว่าแสดงผลถูกต้องหรือไม่ ในสถานะที่เราต้องการให้มันทำงานถูกต้อง ดังนั้นเราไม่ได้ต้องการตรวจสอบ CheckAuthorize() ว่าทำงานถูกต้องหรือไม่ อันนี้จะเขียนเป็นอีก Unit Test หนึ่ง (แต่ก็ไม่ผิดนะครับ เพราะเป็นรูปแบบ Unit Test &#8211; Sociable ซึ่งลองอ่านได้ที่ <a href="https://myifew.com/4480/unit-test-solitary-or-sociable/">รูปแบบการทดสอบของ Unit Test</a>)</li>
<li>แน่ใจหรือไม่ว่า Username/Password ที่ใส่ค้างไว้ จะไม่เปลี่ยนในภายหลัง</li>
</ol>
<h2>3. Mock</h2>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4562 aligncenter" src="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Mock.png" alt="" width="716" height="131" srcset="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Mock.png 716w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Mock-600x110.png 600w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Mock-700x128.png 700w" sizes="auto, (max-width: 716px) 100vw, 716px" /></p>
<p>ชื่อนี้สร้างความสับสนกันทั้งโลก ซึ่ง Mock ใน Test Doubles จะหมายถึง การทดสอบพฤติกรรมการทำงานของสิ่งๆหนึ่ง (Behavior Verification) อย่าง Class/Function เป็นต้น ว่าทำงานตามที่เราต้องการหรือไม่</p>
<p>กล่าวคือ Mock ไม่ได้สนใจผลลัพธ์ปลายทาง แต่สนใจการทำงานของสิ่งที่ต้องการทดสอบ เช่น ส่งค่าเข้าไปใน process แล้วค่านั้นมีการเปลี่ยนแปลงไปตามที่เราคาดหวังหรือไม่</p>
<p><strong>ตัวอย่าง Mock</strong></p>
<p>ระบบ Login ที่ผมสร้างไว้ จะระบุ Username/Password ผ่าน Constructure ชื่อ Member() จากนั้นจะทำการส่งต่อไปให้ CheckAuthorize() เพื่อตรวจสอบว่าถูกต้องหรือไม่ โดยถ้า ถูกต้อง มันจะแสดงผลออกมาที่ Profile() ว่า &#8220;Welcome to member area&#8221;</p>
<p>ดังนั้น Unit Test ผมจะมีหน้าตาประมาณนี้ ซึ่งผมจะ Assert ไปสองค่าว่า มีการใช้งาน <span class="pl-en">CheckAuthorize</span>() และผลลัพธ์ที่ได้คือ &#8220;Welcome to member area&#8221;</p>
<p>View the code on <a href="https://gist.github.com/ifew/6c47ec08d633ddad7bd8bb9d38da25b4">Gist</a>.</p>
<p>จากนั้นผมสร้าง Class ชื่อ <span class="pl-en">MockAuthorize</span> จาก <span class="pl-en">IAuthorize โดยผมสร้างตัวแปร checkAuthorizeWasCalled ขึ้นมา เพื่อใช้ตรวจสอบการทำงาน</span></p>
<p>View the code on <a href="https://gist.github.com/ifew/e2e7a0a9969612da23efbf92774cc8cc">Gist</a>.</p>
<p>ซึ่งถ้าพฤติกรรมการทำงานของโค้ดผมถูกต้อง จะต้องมีขั้นตอนคือ</p>
<ol>
<li>ส่ง Username/Password ผ่าน Constructure ชื่อ Member()</li>
<li>จากนั้นส่งต่อไปให้ CheckAuthorize()</li>
<li>เปลี่ยนค่าตัวแปร checkAuthorizeWasCalled จากค่า  false เป็น true</li>
<li>โยนกลับมาที่ Profile() ให้แสดงผลลัพธ์</li>
</ol>
<p>เป็นอันว่าพฤติกรรมของ Login ของผมมีการเรียกใช้ CheckAuthorize() เพื่อตรวจสอบ Username/Password ถูกต้องนะ</p>
<p><strong>ตัวอย่าง Mock โดยการใช้ Moq4</strong></p>
<p>คราวนี้เมื่อเราเข้าใจวิธีการปกติแล้ว ลองดูตัวอย่างโค้ดของการใช้ Library ชื่อ Moq4  ซึ่งจะทำให้ง่ายขึ้น เพราะสร้างตัว Mock CheckAuthorize() ไว้ใน Unit Test ได้เลย จากนั้นผม Create Object ขึ้นมา และทำการ Verify ว่ามีการเรียกใช้ CheckAuthorize() เกิดขึ้นหรือไม่</p>
<p>View the code on <a href="https://gist.github.com/ifew/d97756b4bd402230bb8b79f883b41963">Gist</a>.</p>
<h3>4. Spy</h3>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4563 aligncenter" src="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Spy.png" alt="" width="716" height="221" srcset="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Spy.png 716w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Spy-600x185.png 600w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Spy-700x216.png 700w" sizes="auto, (max-width: 716px) 100vw, 716px" /></p>
<p>ชื่อนี้แปลตรงตัวคือ สายลับ โดยเราจะส่งสายลับไปดูว่า ระบบของเรามีการเรียกใช้ Function ที่เราต้องการทดสอบจริงๆไหม ซึ่งคล้ายกับ Mock แต่แค่ไม่ได้สนใจพฤติกรรมว่าทำอะไรไปบ้าง ต้องได้ค่าอะไร (<strong>Exclusive Behavior Verification</strong>)</p>
<p>เช่น เรามีระบบรายชื่อที่จะต้องส่งอีเมลออกไป 100 รายการ ระบบของเราจะต้องมีการเรียก Function การส่งอีเมลเป็นจำนวน 100 รอบ ตามที่ต้องการจริงๆ</p>
<p><strong>ตัวอย่าง Spy</strong></p>
<p>ผมทำการสร้าง object Member ขึ้นมาสองครั้ง โดยมันจะต้องไปเรียกใช้งาน CheckAuthorize() จำนวนสองครั้งเช่นกัน ดังนั้นผมจึงทดสอบว่ามีการเรียกใช้งานจำนวนสองครั้งจริงหรือไม่</p>
<p>View the code on <a href="https://gist.github.com/ifew/cd60ca3e959184353e3868a8ddd9c3ab">Gist</a>.</p>
<p>จากนั้นผมสร้าง Class ชื่อ <span class="pl-en">SpyAuthorize</span> จาก <span class="pl-en">IAuthorize โดยผมสร้างตัวแปร checkAuthorizeWasCalled ขึ้นมา เพื่อใช้นับจำนวนการเรียกใช้งาน</span></p>
<p>View the code on <a href="https://gist.github.com/ifew/fde5216072837155fe34ae4042eecc0c">Gist</a>.</p>
<p>สังเกตไหมครับว่า Spy มีการเพิ่มโค้ดเพื่อส่งเข้าไปสอดแนมโดยไม่จำเป็นกับการทำงานด้วย ดังนั้นถ้าเราต้องการทดสอบกับโค้ดที่จะใช้งานจริง เช่น คลาส Authorize() มันก็จะต้องเพิ่มเข้าไปในนั้นด้วย มันจึงเป็นข้อเสียอย่างหนึ่ง</p>
<h2>5. Fake</h2>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4561 aligncenter" src="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Fake.png" alt="" width="701" height="261" srcset="https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Fake.png 701w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Fake-600x223.png 600w, https://myifew.com/wp-content/uploads/2018/07/TestDoubles-Fake-700x261.png 700w" sizes="auto, (max-width: 701px) 100vw, 701px" /></p>
<p>ชื่อก็บอกอยู่ว่าคือการโกหกหลอกลวงนะฮะ ฮ่าๆ มันคือการจำลองอะไรบางอย่างให้เสมือนจริง เหมือนมากจนเป็นข้อเสียหนึ่งที่เราอาจจะสับสนเอง โดยเหมาะกับใช้ในกรณีที่ไม่สามารถทำบน Production หรือระบบที่เราไม่ต้องการให้เกิดผลกระทบ</p>
<p>โดยกรณีนี้ถูกยกตัวอย่างขึ้นมาชัดเจน ก็คือ การใช้ In-Memory เก็บข้อมูลสำหรับทดสอบ แทนการใช้ Database จริงๆ  เป็นต้น</p>
<p>เช่น ระบบมีการสร้าง Order ไว้ใน Database แต่ไม่อยากสร้างจริงใน Database เพราะจะทำให้เลข Order ID ถูกเพิ่มจำนวนเข้าไป และก็ห้ามลบออกด้วย เพราะ Order ID จะขาดช่วง, ดังนั้นเราจะต้องสร้าง Database จำลองขึ้นมา ซึ่งถ้าให้ทุกเครื่องต้องทดสอบได้ และมีความเร็วมากๆ เราก็ต้องใช้ In-Memory นั่นเอง</p>
<p><strong>ตัวอย่าง Fake</strong></p>
<p>ผมต้องการทดสอบว่า Function ชื่อ Get_Member_Information_By_ID() สามารถส่ง Member ID เข้าไป และคืนค่าจาก Database เป็นไปตามที่ผมกำหนดหรือไม่ ดังนั้น แทนที่ผมจะเรียกใช้ Database จริง ผมจึงสร้าง In-Memory Database ขึ้นมาก้อนหนึ่งชื่อ get_by_id_members และทำการใส่ข้อมูลเข้าไปก่อน จากนั้นส่ง Member ID 1 เข้าไปให้ Get_Member_Information_By_ID และทำการตรวจสอบว่า ได้ชื่อ เบอร์โทร วันเดือนปีเกิด ตามใน Database จริงๆหรือไม่</p>
<p>View the code on <a href="https://gist.github.com/ifew/372ecee0dedd227d00e2b368820bda4f">Gist</a>.</p>
<h2>สรุป</h2>
<p>Test Doubles มี 5 ประเภท ที่จะนำไปเลือกใช้ได้ตามสมควร</p>
<ol>
<li><strong>Dummy</strong>: ใช้อะไรก็ได้เพื่อ By Pass การทำงาน แทนการใช้ Dependency จริง</li>
<li><strong>Stub</strong>: กำหนดค่าบางอย่าง เพื่อให้ได้ผลลัพธ์เป็นค่าที่เราต้องการเสมอ</li>
<li><strong>Mock</strong>: เพื่อ verify ว่า process ที่เรียกใช้ มีการส่งค่าไปหา Dependency ตรงตามที่เราคาดหวังหรือเปล่า</li>
<li><strong>Fake</strong>: จำลองบางอย่างขึ้นมาเพื่อใช้แทนของจริง</li>
<li><strong>Spy</strong>: เพื่อ verify ให้แน่ชัดในการใช้ process เช่น โดนเรียกใช้ไปกี่ครั้ง หรือพารามิเตอร์อะไรถูกส่งไปบ้าง</li>
</ol>
<p>หากต้องการเขียน Unit Test เราเลี่ยงไม่ได้ที่จะต้องเรียนรู้เรื่อง Test Doubles เพื่อตัดการเกี่ยวข้องกับสิ่งต่างๆ ทรี่ไม่จำเป็นออก (Dependency) เพราะว่า เราต้องการเจาะจงทดสอบเพียงอย่างเดียวเท่านั้น อย่างอื่นที่ไม่ทดสอบ เราก็จำลองขึ้นมาโดยใช้ Dummy, Stub, Fake ตามแต่จะเลือกใช้ และเราก็ใช้ Mock, Spy เพื่อทดสอบว่าโค้ดที่เขียน มันได้ทำงานถูกต้องจริงๆใช่ไหม ซึ่งวิธีการนี้ นำไปใช้ได้กับทุกภาษาโปรแกรม</p>
<p>และถ้าสังเกตให้ดี จะใช้ Test Double ได้ จำเป็นต้องรู้เรื่อง Unit Test,  Dependency-Injection และการเขียนโปรแกรมแบบ  Object-Oriented ด้วยนะฮะ ไม่เช่นนั้น เข้าใจยากเลยทีเดียว</p>
<h3>อ้างอิง</h3>
<ul>
<li><a href="https://www.javacodegeeks.com/2015/11/test-doubles-mocks-dummies-and-stubs.html" target="_blank" rel="noopener">Test Doubles: mocks, dummies and stubs </a> &lt;&lt; ตัวอย่างน่าสนใจ เข้าใจง่ายดี</li>
<li><a href="https://blog.pragmatists.com/test-doubles-fakes-mocks-and-stubs-1a7491dfa3da" target="_blank" rel="noopener">Test Doubles — Fakes, Mocks and Stubs.</a> &lt;&lt; แนะนำให้อ่านครับ สรุปเข้าใจง่าย ตัวอย่างดี อ่านง่าย</li>
<li><a href="https://8thlight.com/blog/uncle-bob/2014/05/14/TheLittleMocker.html" target="_blank" rel="noopener">The Little Mocker</a> &lt;&lt; แนะนำให้อ่านครับ  อ่านง่าย ตัวอย่างเข้าใจง่ายมาก บทความนี้เขียนจากพื้นฐานเว็บนี้</li>
<li><a href="https://martinfowler.com/articles/mocksArentStubs.html" target="_blank" rel="noopener">Mocks Aren&#8217;t Stubs</a> &lt;&lt; แนะนำให้อ่านครับ อธิบายละเอียดเลย</li>
<li><a href="https://adamcod.es/2014/05/15/test-doubles-mock-vs-stub.html" target="_blank" rel="noopener">Understanding Test Doubles (Mock vs Stub)</a> &lt;&lt; แนะนำให้อ่านครับ สรุปสั้นๆ ตัวอย่างดี อ่านง่าย</li>
<li><a href="http://www.somkiat.cc/test-double-mock-stub-and-dummy/" target="_blank" rel="noopener">มาดูกันว่า Mock, Stub และ Dummy แตกต่างกันอย่างไร ?</a> &lt;&lt; ผมอ้างอิงการแปลภาษาไทยจากเว็บพี่ปุ๋ย</li>
<li>และสุดท้าย กราบขอบพระคุณ<a href="https://www.facebook.com/boyone" target="_blank" rel="noopener">พี่บอย</a> Elder แห่งสยามชำนาญกิจ ที่ช่วยอ่านรีวิวและแนะนำมา ณ ที่นี้ครัช</li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://myifew.com/4556/how-to-skip-dependency-in-unit-test-by-test-doubles/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>รูปแบบการทดสอบของ Unit Test จะเป็น Solitary หรือ Sociable ดี</title>
		<link>https://myifew.com/4480/unit-test-solitary-or-sociable/</link>
					<comments>https://myifew.com/4480/unit-test-solitary-or-sociable/#respond</comments>
		
		<dc:creator><![CDATA[iFew]]></dc:creator>
		<pubDate>Fri, 04 May 2018 09:21:55 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Automation Testing]]></category>
		<category><![CDATA[Integration Test]]></category>
		<category><![CDATA[Test Strategy]]></category>
		<category><![CDATA[Testable]]></category>
		<category><![CDATA[Unit Test]]></category>
		<guid isPermaLink="false">https://myifew.com/?p=4480</guid>

					<description><![CDATA[จากที่ได้ทำงานร่วมกับลูกค้า และพบเองในกลุ่ม Coach ตอนทำงานกันเอง คือการพูดถึงเรื่อง Unit Test ที่เขียนอย่างไรถึงจะถูกต้อง? เช่น ขนาดของ Unit Test ต้องเล็กขนาดไหน?, ทดสอบ Method ที่เรียกหา Method อื่นๆ ตัวเดียวได้ไหม? เป็นต้น]]></description>
										<content:encoded><![CDATA[<p>จากที่ได้ทำงานร่วมกับลูกค้า และพบเองในกลุ่ม Coach ตอนทำงานกันเอง คือการพูดถึงเรื่อง Unit Test ที่เขียนอย่างไรถึงจะถูกต้อง? เช่น ขนาดของ Unit Test ต้องเล็กขนาดไหน?, ทดสอบ Method ที่เรียกหา Method อื่นๆ ตัวเดียวได้ไหม? เป็นต้น</p>
<p>โพสต์นี้ผมจะอ้างอิงถึงลุง Martin Fowler เยอะหน่อย เพราะไปอ่านที่แกอธิบายไว้เรื่อง <a href="https://martinfowler.com/bliki/UnitTest.html" target="_blank" rel="noopener">UnitTest</a> และ <a href="https://martinfowler.com/articles/mocksArentStubs.html" target="_blank" rel="noopener">Mocks Aren&#8217;t Stubs</a> ได้น่าสนใจมาก<span id="more-4480"></span></p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4482 aligncenter" src="https://myifew.com/wp-content/uploads/2018/05/unittest-concept.png" alt="" width="646" height="506" srcset="https://myifew.com/wp-content/uploads/2018/05/unittest-concept.png 646w, https://myifew.com/wp-content/uploads/2018/05/unittest-concept-600x470.png 600w" sizes="auto, (max-width: 646px) 100vw, 646px" /></p>
<p>ถ้าถามว่า Unit Test คืออะไร ต้องทดสอบในหน่วยย่อยขนาดไหน ไม่มีใครสามารถตอบได้ แม้แต่ลุง Martin Fowler และ Kent Beck เพราะนิยามของแกก็เปลี่ยนไปตามงานที่ทำ แต่ไม่ว่าจะนิยามอย่างไร มันจะมีลักษณ์เหมือนกัน 3 ข้อ คือ</p>
<ol>
<li><strong>มุ่งทดสอบในหน่วยที่เล็กที่สุดของระบบ</strong> &#8211; นั่นสิ แล้วระบบเรามีหน่วยย่อยสุดแค่ไหน ต้องกลับไปดูว่าเราออกแบบไว้อย่างไร</li>
<li><strong>เขียนโดยโปรแกรมเมอร์ ด้วยเครื่องมือที่ถนัด</strong>  &#8211; ใช่แล้ว เป็นโปรแกรมเมอร์เขียนนะ ไม่ใช่ Tester!, และจะใช้เครื่องมืออะไรก็ตามถนัดเลย จะเขียนเช็เทียบค่าเอง หรือใช้ Unit Test Framework เช่น xUnit ก็ตามสะดวก</li>
<li><strong>มีการทดสอบที่ได้ผลลัพธ์ออกมาเร็วมากๆ</strong> &#8211; เห็นผลในระดับเสี้ยววินาที (millisecond)</li>
</ol>
<p>ข้อ 2, 3 ยังพอเข้าใจได้ แต่ข้อ 1 ขอบเขตยังไม่ชัดเจนนัก แต่อย่างที่ผมบอก มันอยู่ที่การออกแบบซอฟต์แวร์เราแต่แรกว่าย่อยได้ขนาดไหนและครบถ้วนหรือไม่ (ดังนั้นจึงเป็นที่มาว่าทำไมซอฟต์แวร์ที่พัฒนาโดยใช้หลักการ Test-Driven Development ถึงสามารถเขียน Unit Test ได้ครบ, ครอบคลุม และมี<a href="https://myifew.com/4414/design-project-code-structure-for-testable/">โครงสร้างระบบที่สามารถทดสอบได้</a>ตั้งแต่ต้น)</p>
<p>การออกแบบซอฟต์แวร์ของเรา หลีกเลี่ยงไม่ได้ที่จะมี <strong>Method ที่ทำงาน 2 แบบ คือ ทำงานจบได้ในตัวมันเอง กับ ต้องอาศัย Method อื่นๆ มาประกอบกันเพื่อทำงาน</strong></p>
<p>ซึ่งการทดสอบของ Unit Test ก็เช่นกัน ได้จำแนกออกเป็น 2 แบบตามการทำงานของ Method คือ</p>
<h3>Unit Test ที่ทดสอบแบบ Solitary</h3>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4483 aligncenter" src="https://myifew.com/wp-content/uploads/2018/05/solitary-tests.png" alt="" width="684" height="382" srcset="https://myifew.com/wp-content/uploads/2018/05/solitary-tests.png 684w, https://myifew.com/wp-content/uploads/2018/05/solitary-tests-600x335.png 600w" sizes="auto, (max-width: 684px) 100vw, 684px" /></p>
<p>คือทดสอบการทำงานเพียง​ Method เดียวเท่านั้น เช่น เรามีระบบที่ให้สมาชิกสามารถโอนเงินได้และคิดค่าธรรมเนียม ดังนั้นเราจะทดสอบเฉพาะ Method การคิดค่าธรรมเนียม โดยไม่เอา Method ทำงานเรื่องสมาชิกและการโอนเงินเข้ามาเกี่ยวข้องเลย ดังนั้น จะต้องออกแบบให้แยกจากกันให้ดี และใช้หลักการของ Test Doubles เช่น Mock หรือ Stub เข้ามาจำลองการทำงานอื่นๆ (ส่วนของสมาชิก, โอนเงิน) ที่เราไม่ต้องการทดสอบ</p>
<h3>Unit Test ที่ทดสอบแบบ Sociable</h3>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4484 aligncenter" src="https://myifew.com/wp-content/uploads/2018/05/sociable-tests.png" alt="" width="570" height="378" /></p>
<p>คือทดสอบการทำงานของ Method ที่เกี่ยวข้องกับ Method อื่นๆ ด้วย ซึ่งลุง Martin บอกว่าในสมัยยุค 90 ที่ xUnit เพิ่งจะเกิด รูปแบบ Sociality นี้ได้รับความนิยมมาก เพราะมันทำได้สะดวกกว่าแบบ Solitary</p>
<p>การทดสอบแบบ Sociality นี้เอง ค่อนข้างสร้างความสับสนให้หลายคน ว่าเรียกมันเป็น Unit Test ได้หรือไม่ เพราะไม่ได้ทดสอบการทำงานเพียง Method เดียว, ซึ่งลุง Martin คิดว่ามันเป็น Unit Test ครับ เพราะเป็นการทดสอบสิ่งที่เกิดขึ้นของ Method จริงๆ ไม่มีการตัดแต่งเพื่อทดสอบหรือการจำลองใดๆ แม้ว่ามันจะดูมีหลายการทำงาน (หรือหลาย Unit ในความคิดเรา) ก็ตาม</p>
<p>ต่อมาในช่วงปี ค.ศ. 2000 ที่ xUnit เป็นที่นิยมแล้ว การทดสอบแบบ Solitary ก็กลับมาบูมอีกครั้ง เพราะมีเครื่องมือที่ช่วยในการทำ Mock เกิดขึ้นมากมาย จนได้เกิดสำนักที่เขียน Unit Test ขึ้นเป็นสองสำนักใหญ่ๆ (School) คือ</p>
<h3><strong>Classical </strong>(หรือเรียกว่าชาว Classicist, หรือสำนัก <strong>Detroit</strong>)</h3>
<p>คือใช้รูปแบบการทดสอบแบบ Sociable เน้นทดสอบโดยใช้ Object, Method, Properties จริงๆ มีการอ้างอิง Method ถึงกันก็ปล่อยมันไป เพื่อให้เห็นพฤติกรรมจริงๆของมัน, แต่ทั้งนี้ ก็มีใช้ Mock ด้วย แต่เท่าที่จำเป็นเท่านั้น อย่างการเชื่อมต่อ Database, Network</p>
<p>โดยกลุ่ม Classical จะเป็นพวกเน้นทดสอบสถานะการทำงานของ Method นั้นๆ (States verification) ว่าทำงานแสดงค่าออกมาได้ถูกต้องตามที่คาดหวังหรือไม่ เช่น ส่งข้อมูลเข้าไป Method ผลที่ได้ออกมาต้องเป็น ถูก หรือ ผิด หรือได้ค่าการเปลี่ยนแปลงตามที่หวังไว้ออกมาเหมือนเดิมเสมอๆ</p>
<p>ที่มาของชื่อ Detroit เพราะมันถูกใช้ใน XP Programming ครั้งแรกกับโปรเจ็ค C3 ของ บริษัท Chrysler ในเมือง Detroit</p>
<h3>Mockist (หรือเรียกว่าชาว Mockist, หรือสำนัก London)</h3>
<p>คือใช้รูปแบบการทดสอบแบบ Solitary ที่ทำงานเดียวเท่านั้น ดังนั้นจะต้องตัดตัวเกี่ยวข้องออก มีการใช้ Mock, Stub เข้ามาสร้างตัวจำลองของ Object, Method, Properties เสมอๆ</p>
<p>โดยกลุ่ม Mockist จะเป็นพวกเน้นใช้ Mock เพื่อทดสอบพฤติกรรมการทำงานของ Method นั้นๆ (Behavior verification) ว่าทำงานได้ตามที่คาดหวังหรือไม่ เช่น ส่งข้อมูลเข้าไป Method เพื่อบันทึกข้อมูล จะเห็นว่ามีการบันทึกข้อมูลเกิดขึ้นเสมอ เป็นต้น</p>
<p>ที่มาของชื่อ London คือ ถูกใช้โดยกลุ่มคนใช้ XP Programming ในยุคเริ่มแรกในเมือง London</p>
<p>&#8230;</p>
<p>ซึ่งการทดสอบสองแบบนี้เรียกว่าเป็น Unit Test ทั้งคู่ และลุง Martin เองก็เคารพในการตัดสินใจของของผู้ที่จะเลือกใช้ ไม่มีใครผิดใครถูก หรืออะไรดีกว่ากัน</p>
<p>และที่สำคัญลุง Martin มองว่า ถ้าต้องไปเชื่อมต่อกับระบบภายนอกเช่น I/O, Database, Network ที่มันมีความเสถียรพอ และมีความเร็วมากพอ ก็สามารถเอามาใช้ใน Unit Test ได้นะ เช่น จำลอง MySQL Database ด้วย In-Memory Database เป็นต้น</p>
<h3>แล้วจะเลือกอะไรดีระหว่าง Classicist หรือ Mockist</h3>
<p>จากทั้งหมดที่เขียนเล่ามา ไม่ว่าจะความเห็นส่วนตัว หรือความเห็นของลุง Martin เอง ทั้งสองแบบดีหมด ไม่จำเป็นต้องไปเลือกมันหรือต้องไปสนใจว่า Method นี้จะทดสอบด้วยอะไร มันอยู่ที่ว่าเราเข้าใจหรือเปล่าว่า Unit Test คืออะไร และเราจะทดสอบอะไร</p>
<p>ถ้า Method มันทำงานจบในตัวเอง หรือมีการใช้ Method อื่น แบบง่ายๆ สามารถแยกการทำงานได้ แยกการทำสอบได้ เราก็ไม่จำเป็นต้องไปจำลอง Object โดยใช้ Mock, Stub หรืออื่นๆใน Test Doubles เลยก็ได้</p>
<p>แต่ถ้ามันดูยุ่งยาก และจำเป็นต้องเชื่อมต่อกับส่วนอื่น และมันทำให้การทดสอบช้าลง หรือไม่เสถียร เช่น Network, Database แบบนี้เราก็เลี่ยงไม่ได้ที่จะต้องใช้ Test Doubles นะ</p>
<h2>สรุป</h2>
<ul>
<li>เราจะใช้ Unit Test ที่มีรูปแบบการทดสอบเป็น Classical หรือ Mockist ก็ได้ ไม่มีผิดไม่มีถูก</li>
<li>แยกการทำงานของ Method ให้ทำงานทีละอย่าง</li>
<li>ถ้า Method ไหนมีแต่ Business Logic &#8211; เป็น Classical</li>
<li>ถ้า Method ไหนมี Business Logic โดยจำเป็นต้องข้องเกี่ยวกับ Method อื่นที่คุมไม่ได้ หรือระบบภายนอก &#8211; เป็น Mockist</li>
<li>ถ้า Method ไม่มี Business Logic และไปข้องเกี่ยวกับอื่นที่คุมไม่ได้ หรือระบบภายนอก &#8211; เป็น Mockist</li>
<li>จะแยกแยะได้ขนาดนี้ แปลว่าต้องทำ High Level Design และ <a href="https://myifew.com/4455/design-and-develop-code-for-testable/" target="_blank" rel="noopener">Detail Level Design</a></li>
<li>ถ้าจะทำให้เขียน Unit Test ให้ง่าย ควรเริ่มด้วย <a href="https://myifew.com/4455/design-and-develop-code-for-testable/" target="_blank" rel="noopener">Test First และ Test-Driven Development</a></li>
<li>ถ้าอ่านโพสต์นี้แล้วไม่เข้าใจ ต้องไปเรียนรู้ Unit Test และ Test Doubles เพิ่มนะ</li>
<li>พอเขียนเสร็จ รู้สึกว่าตัวเองต้องไปเรียนรู้เรื่อง OOP, SOLID ให้มากขึ้น</li>
</ul>
<h3>อ้างอิง</h3>
<ul>
<li><a href="https://martinfowler.com/bliki/UnitTest.html" target="_blank" rel="noopener">UnitTest</a>&lt; (อ้างอิงจากที่นี่ และรูปในโพสต์นี้ก็ใช้จากบทความนี้)/li&gt;</li>
<li><a href="https://martinfowler.com/articles/mocksArentStubs.html" target="_blank" rel="noopener">Mocks Aren&#8217;t Stubs</a></li>
<li><a href="https://manas.tech/blog/2009/04/30/state-vs-behaviour-verification.html" target="_blank" rel="noopener">State vs Behaviour Verification</a></li>
</ul>
]]></content:encoded>
					
					<wfw:commentRss>https://myifew.com/4480/unit-test-solitary-or-sociable/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>ต้องคิดอย่างไร รู้อะไร และทำอย่างไร เพื่อให้เกิด Automated Test</title>
		<link>https://myifew.com/4455/design-and-develop-code-for-testable/</link>
					<comments>https://myifew.com/4455/design-and-develop-code-for-testable/#respond</comments>
		
		<dc:creator><![CDATA[iFew]]></dc:creator>
		<pubDate>Tue, 10 Apr 2018 09:01:02 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Automation Testing]]></category>
		<category><![CDATA[Continuous Integration]]></category>
		<category><![CDATA[Integration Test]]></category>
		<category><![CDATA[Test Strategy]]></category>
		<category><![CDATA[Testable]]></category>
		<category><![CDATA[Unit Test]]></category>
		<guid isPermaLink="false">https://myifew.com/?p=4455</guid>

					<description><![CDATA[หลังจากได้เขียนอธิบายเรื่อง "จะทำระบบให้รองรับ Automated Test ได้อย่างไร (Testable)" ไปคราวก่อน ได้รับผลตอบรับด้วยดี คราวนี้เลยมาเขียนเพิ่มเติมเพื่อเป็นตัวอย่างแก่ผู้ที่ต้องการนำไปพัฒนาระบบจริงครับ]]></description>
										<content:encoded><![CDATA[<p>หลังจากได้เขียนอธิบายเรื่อง &#8220;<a href="https://myifew.com/4414/design-project-code-structure-for-testable/">จะทำระบบให้รองรับ Automated Test ได้อย่างไร (Testable)</a>&#8221; ไปคราวก่อน ได้รับผลตอบรับด้วยดี คราวนี้เลยมาเขียนเพิ่มเติมเพื่อเป็นตัวอย่างแก่ผู้ที่ต้องการนำไปพัฒนาระบบจริง โดยในบล็อกนี้ผู้อ่านจะได้เห็นภาพของสิ่งเหล่านี้ คือ</p>
<ul>
<li>กระบวนการคิดเพื่อเตรียมทำ Automated Test</li>
<li>Unit Test</li>
<li>Integration Test</li>
<li>DI (Dependency Injection)</li>
<li>Stub (Test Double)</li>
<li>Code Coverage</li>
</ul>
<p><span id="more-4455"></span></p>
<h2>ระบบที่จะทำในบล็อกนี้</h2>
<p>ผมต้องการทำระบบคิดเงินอัตราแลกเปลี่ยน โดยการดึงข้อมูลอัตราแลกเปลี่ยนกลางจาก <a href="https://iapi.bot.or.th/Developer?lang=th" target="_blank" rel="noopener">API ของธนาคารแห่งประเทศไทย</a> และบวกค่าธรรมเนียมการให้บริการไปด้วย (ทำตัวเป็นคนกลางรับแลกเปลี่ยนเงินตรา) โดยรายละเอียดของเงื่อนไขมีดังนี้</p>
<ul>
<li>สามารถแลกเงินได้ตั้งแต่ 1$ ขึ้นไป</li>
<li>ต้องแลกเป็นเงินจำนวนเต็ม 1$ เท่านั้น ไม่สามารถแลกเงินเป็นเศษได้</li>
<li>บวกค่าธรรมเนียมการแลกเงิน 2.50% จากราคาปกติ เมื่อลูกค้าแลกเงินในช่วง 1$ &#8211; 100$</li>
<li>บวกค่าธรรมเนียมการแลกเงิน 2.15% จากราคาปกติ เมื่อลูกค้าแลกเงินในช่วง 101$-500$</li>
<li>บวกค่าธรรมเนียมการแลกเงิน 2.00% จากราคาปกติ เมื่อลูกค้าแลกเงินตั้งแต่ 501$ ขึ้นไป</li>
<li>แสดงค่าอัตราแลกเปลี่ยนเป็นทศนิยม 2 ตำแหน่ง ถ้ามีเศษให้ปัดขึ้นเสมอ โดยพิจารณาที่ตำแหน่งที่ 2 (ตัวอย่าง 1 USD = 35.00THB + 2.5% = 35.875THB = 35.88THB)</li>
<li>ให้ดึงค่าอัตราแลกเปลี่ยนจาก BOT API ที่ <a href="https://iapi.bot.or.th/Developer?lang=th" rel="nofollow">https://iapi.bot.or.th/Developer?lang=th</a> (ใช้ API: อัตราแลกเปลี่ยนเฉลี่ย &#8211; รายวัน)</li>
</ul>
<p>ตัวอย่างของโจทย์นี้คือ &#8230; ผมเป็นบริษัทรับแลกเงิน จะคำนวณเงินบาท (THB) ที่ลูกค้าต้องจ่าย เพื่อแลกกับจำนวนเงินดอลลาร์ (USD) ที่ต้องการ เช่น ลูกค้าต้องการเงินจำนวน 1$ เมื่อดึงข้อมูลจากธนาคารแห่งประเทศไทยได้ 35 บาท ผมจึงบวกค่าธรรมเนียมอีก 2.5% และปัดเศษขึ้นกลมๆ แปลว่าลูกค้าต้องจ่ายให้ผม 35.88 บาท</p>
<p>ดังนั้นลูกค้าจะแลกเงินเท่าไร ก็จะบวกค่าธรรมเนียม และแสดงผลว่าลูกค้าต้องจ่ายเงินบาทเท่าไร ตามจำนวนเงินดอลลาร์ที่เขาต้องการ</p>
<h2>จะทำ Automated Test ต้องเริ่มต้นที่ออกแบบ</h2>
<p>จากโจทย์ ผมกำหนด Input, Output ง่ายๆ ให้ระบบผมก่อน ในที่นี่ Ouput คือ &#8220;เงินบาทที่ลูกค้าต้องจ่าย&#8221; ส่วน Input ผมใส่ &#8220;จำนวนเงินดอลลาร์ที่ลูกค้าต้องการ&#8221; และผมขอเพิ่ม &#8220;วันที่อ้างอิงของอัตราแลกเปลี่ยน&#8221; ที่ผมต้องการด้วย เพื่อกำหนดค่าของวันให้คงที่ ใช้ในกรณีผมต้องการทดสอบ (และเผื่ออนาคตถ้าผมอยากเปลี่ยน Business Condition ทางวันที่)</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4459 aligncenter" src="https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Blackbox.png" alt="" width="641" height="165" srcset="https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Blackbox.png 641w, https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Blackbox-600x154.png 600w" sizes="auto, (max-width: 641px) 100vw, 641px" /></p>
<p>จาก Process กล่องดำๆ มืดมัว (Black Box) ผมจะแจกแจงเพื่อให้เข้าใจกระบวนการทำงานของมันเสียก่อน (White Box) ซึ่งผมถนัดคิดออกมาเป็นข้อๆก่อน จึงขอร่างไว้ดังนี้</p>
<div>
<ol>
<li><strong>รับค่า:</strong> จำนวนเงินดอลลาร์, วันที่ของอัตราแลกเปลี่ยนเงิน</li>
<li>ตรวจสอบจำนวนเงินมีค่าเป็นตัวเลขจำนวนเต็มบวกหรือไม่</li>
<li>เรียกข้อมูลอัตรแลกเปลี่ยนเงินจาก API ของธนาคารแห่งประเทศไทย โดยอ้างอิงจากวันที่ต้องการ</li>
<li>นำจำนวนเงินจำนวนเงินดอลลาร์คูณกับอัตราแลกเปลี่ยน Selling (ใช้ Selling เพราะหมายถึงอัตราที่ธนาคารขายให้เรา)</li>
<li>นำจำนวนเงินจำนวนเงินดอลลาร์ไปหา % ของธรรมเนียมที่จะไปคิด</li>
<li>นำจำนวนเงินบาทที่แปลงจากจำนวนเงินดอลลาร์ มาบวกกับค่าธรรมเนียมที่คิดเป็นเงินบาท</li>
<li>นำจำนวนเงินบาทหลังรวมค่าธรรมเนียมแล้วปัดเศษให้เหลือสองหลัก โดยปัดเศษที่ตำแหน่งที่ 2</li>
<li><strong>คืนค่า:</strong> จำนวนเงินบาทหลังรวมค่าธรรมเนียมและปัดเศษเหลือสองตำแหน่ง</li>
</ol>
<p>ซึ่งถ้าลองพิจารณากระบวนการดีๆแล้ว เราจะเห็นได้ว่า ข้อไหนมีการคำนวณ มีเงื่อนไขบ้างก็ประเมินไว้ก่อนเลยว่าอาจต้องทำ Unit Test และข้อไหนมีการเชื่อมต่อกับระบบภายนอกก็ต้องทำ Integration Test</p>
<p>คราวนี้ผมลองแปลงให้เป็น Flowchart ดู จะได้ง่ายต่อการอ่าน</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-4462 aligncenter" src="https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Whitebox-Test.png" alt="" width="748" height="1074" srcset="https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Whitebox-Test.png 748w, https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Whitebox-Test-600x861.png 600w, https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Whitebox-Test-713x1024.png 713w, https://myifew.com/wp-content/uploads/2018/04/APIExchange-Problem-Whitebox-Test-488x700.png 488w" sizes="auto, (max-width: 748px) 100vw, 748px" /></p>
<h2>มาทำ Test Cases และ Test Data กัน</h2>
<p>ตอนนี้เราเห็นกระบวนการทำงานทั้งหมดของระบบเราแล้ว ได้เห็นเส้นทาง ทางแยกที่จะเกิดขึ้น ดังนั้น ก็พอจะสรุป Test Cases ได้แล้ว</p>
<p>แต่เดี๋ยวก่อน! ถ้าจะเขียน Test ให้มันทดสอบได้ แปลว่าผมคาดหวังสิ่งใดไว้ ผมจะต้องได้ค่านั้นออกมาเสมอใช่ไหม สิ่งต่างๆใน Test ของผมควรจะต้องคงที่ด้วย ดังนั้นผมจึงกำหนดค่าคงที่ให้กับวันที่เสียก่อน เพราะมันเป็นค่าเดียวที่จะทำให้อัตราแลกเปลี่ยนผิดเพี้ยนไปจากความคาดหวัง (Expect Data) ที่ผมจะเขียนไว้ใน Test</p>
<p>ดังนั้น ในที่นี้ผมจะกำหนดให้ใช้อัตราแลกเปลี่ยนของวันที่ 1 กุมภาพันธ์ 2561 เป็นตัวทดสอบ โดยมีอัตราแลกเปลี่ยนที่ 1$ เท่ากับ 31.5408000 THB</p>
<p>เราก็จะได้ Test Cases ดังนี้</p>
<h4>Test Cases</h4>
<ol>
<li>สามารถแลกเงินที่มากกว่าหรือเท่ากับ 1$ แต่ไม่เกิน 100$ ที่อ้างอิงกับอัตราแลกเปลี่ยนของวันที่ 1 กพ 2018 และบวกค่าธรรมเนียม 2.50% ได้ถูกต้อง</li>
<li>สามารถแลกเงินที่มากกว่าหรือเท่ากับ 101$ แต่ไม่เกิน 500$ ที่อ้างอิงกับอัตราแลกเปลี่ยนของวันที่ 1 กพ 2018 และบวกค่าธรรมเนียม 2.15% ได้ถูกต้อง</li>
<li>สามารถแลกเงินที่มากกว่าหรือเท่ากับ 501$ ขึ้นไป ที่อ้างอิงกับอัตราแลกเปลี่ยนของวันที่ 1 กพ 2018 และบวกค่าธรรมเนียม 2.00% ได้ถูกต้อง</li>
<li>ไม่สามารถแลกเงินที่น้อยกว่า 1$ ได้</li>
</ol>
<p>จาก 4 Test Cases ข้างต้น ผมใช้หลักการ BVA (Boundary value analysis) เป็นตัวสร้าง Test Data ให้ผม หมายความว่า ค่าใดที่ดูเป็นค่าที่จะเปลี่ยนแปลง Business Condition จะต้องเอาค่าก่อนหน้าและหลังมาใช้ด้วย เช่น 500$ ผมก็จะหยิบ 499$ และ 501$ มาทดสอบด้วย (เชื่อเถอะ ทดสอบแบบนี้ดีแล้ว เพราะโปรแกรมเมอร์ชอบมีปัญหากับเครื่องหมาย &lt;, &lt;=, &gt;, &gt;= ในการตรวจสอบค่า)</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4463 aligncenter" src="https://myifew.com/wp-content/uploads/2018/04/Boundary-value-analysis.jpg" alt="" width="500" height="311" />Photo: http://toolsqa.com/software-testing/boundary-value-analysis/</p>
<p>และเราก็จะได้ Test Data ดังนี้</p>
<h4>Test Data</h4>
<table>
<thead>
<tr>
<th>Test Cases</th>
<th>Date</th>
<th>Change USD</th>
<th>Selling THB</th>
<th>Selling THB with Fee</th>
<th>After Celling</th>
</tr>
</thead>
<tbody>
<tr>
<td>#1</td>
<td>1 Feb 2018</td>
<td>1</td>
<td>31.5408000</td>
<td>32.33</td>
<td>32.33</td>
</tr>
<tr>
<td>#1</td>
<td>1 Feb 2018</td>
<td>100</td>
<td>3154.08000</td>
<td>3232.932000</td>
<td>3232.94</td>
</tr>
<tr>
<td>#2</td>
<td>1 Feb 2018</td>
<td>101</td>
<td>3185.620800</td>
<td>3254.111647</td>
<td>3254.12</td>
</tr>
<tr>
<td>#2</td>
<td>1 Feb 2018</td>
<td>499</td>
<td>15738.859200</td>
<td>16077.244673</td>
<td>16077.25</td>
</tr>
<tr>
<td>#2</td>
<td>1 Feb 2018</td>
<td>500</td>
<td>15770.400000</td>
<td>16109.463600</td>
<td>16109.47</td>
</tr>
<tr>
<td>#3</td>
<td>1 Feb 2018</td>
<td>501</td>
<td>15801.940800</td>
<td>16117.979616</td>
<td>16117.98</td>
</tr>
<tr>
<td>#3</td>
<td>1 Feb 2018</td>
<td>1000</td>
<td>31540.800000</td>
<td>32171.616000</td>
<td>32171.62</td>
</tr>
<tr>
<td>#4</td>
<td>1 Feb 2018</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
<tr>
<td>#4</td>
<td>1 Feb 2018</td>
<td>0.1</td>
<td>0</td>
<td>0</td>
<td>0</td>
</tr>
</tbody>
</table>
<p>ปล. สังเกตว่า ในตารางผมมี Test Cases ประกบด้วยนะเออ เอาไว้เป็นเช็คลิสเล็กๆว่า ทำครบทุกเคสหรือไม่</p>
<h2>ในส่วนของโค้ดนั้น</h2>
<p>ในการเขียนโปรแกรมของโจทย์นี้ ผมใช้หลักการที่เรียกว่า TDD (Test Driven Development) ในการทำ เนื่องจากผมรู้ Test Cases รู้ Data Test ผมจึงเริ่มเขียนตั้งแต่ Test ได้เลย ..</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4466 aligncenter" src="https://myifew.com/wp-content/uploads/2018/04/stride-nyc-test-driven-development-chart-700x400.jpg" alt="" width="700" height="400" srcset="https://myifew.com/wp-content/uploads/2018/04/stride-nyc-test-driven-development-chart-700x400.jpg 700w, https://myifew.com/wp-content/uploads/2018/04/stride-nyc-test-driven-development-chart-700x400-600x343.jpg 600w" sizes="auto, (max-width: 700px) 100vw, 700px" />Photo: http://haselt.com/coding-dojo-with-tdd/</p>
<p>หลักการของ TDD ตามรูป</p>
<ol>
<li>เริ่มจากเขียนโค้ด Unit Test ก่อนเนอะ โดยเราระบุค่า Input และค่าที่คาดหวังว่าระบบเราจะต้องส่งกลับออกมา (Output, Expect Data)</li>
<li>พอรัน Unit Test มันก็จะไม่ผ่าน (จะผ่านได้ไงล่ะ! มันยังไม่มี Production Code ที่ใช้ทำงาน) <span style="color: #ff0000;">&lt;== กระบวนการสีแดง</span></li>
<li>เขียน Production Code ที่ง่ายที่สุด เพื่อให้ Unit Test แรกมันผ่าน เช่น ใน Unit Test คาดหวังไว้ว่าต้องได้ค่าคืนมาเป็น  32.33 ผมก็จะสร้างโค้ดโง่ๆ คือ ส่งเลข 32.33 นี้ออกมาเลย <span style="color: #008000;">&lt;== กระบวนการสีเขียว</span></li>
<li>ในการโค้ดแรกๆอาจยังเรียบๆ ไม่มีอะไร Refactoring ก็ข้ามไปก่อน<span style="color: #0000ff;"> &lt;== กระบวนการ Refactor</span></li>
<li>แล้วก็วนกลับไปเขียน Unit Test ใหม่ ในข้อที่สอง ตามตาราง Test Data</li>
</ol>
<p>ซึ่งโค้ดในส่วนของ Unit Test ทั้งหมดของผม หลังจากทำครบทุกเคสและมีการ Refactor แล้ว จะหน้าตาประมาณนี้</p>
<p><script src="https://gist.github.com/ifew/eef26279d1c1a02ec69658457af0f699.js"></script></p>
<p>คราวนี้ระบบของผมมีอยู่ Method หนึ่งทำการเชื่อมต่อกับ API ธนาคารของประเทศไทยด้วย ดังนั้น จะทำอย่างไรล่ะเพื่อให้เป็น Unit Test ได้ เพราะขัด<a href="http://www.somkiat.cc/good-unit-test-with-first/" target="_blank" rel="noopener">หลักการของ FIRST</a> อยู่สองข้อคือ</p>
<ul>
<li><strong>Fast</strong>: ต้องทำงานได้เร็ว &lt;== การมี Connection ออกไปข้างนอกจะช้า</li>
<li><strong>Repeatable</strong>: ทำซ้ำได้ ไม่ขึ้นกับระบบอื่นๆ &lt;== มันไม่สามารถทำซ้ำได้ ถ้าระบบภายนอกเชื่อมต่อไม่ได้</li>
</ul>
<p>และวิธีการที่เราจะทำต่อไปคือ ต้องจำลองค่า JSON ที่ส่งออกมาจาก API ธนาคารแห่งประเทศไทย ให้ได้ตามนี้</p>
<p><script src="https://gist.github.com/ifew/ccef1e89a4519e6559fa8ec63632d011.js"></script></p>
<p>ซึ่งวิธีข้างต้นเรียกว่าการ Stub (เป็นหนึ่งในหลักการของ <a href="http://www.somkiat.cc/test-double-mock-stub-and-dummy/" target="_blank" rel="noopener">Test Double</a>) ซึ่งหมายถึง ผมต้องการจำลองการทำงานอะไรบางอย่างเพื่อให้ได้ค่าเดิมออกมาเสมอ</p>
<p>คราวนี้ โค้ดที่ต่อ API ธนาคารแห่งประเทศไทย มีการเรียกใช้ Library ชื่อ HttpClient ซึ่งเป็นการเรียกออกไปที่ Internet จริงๆ ดังนั้น ก็ยังผิดหลักการทำ Unit Test อยู่ดี แม้ว่าผมจะจำลองการคืนค่า JSON ได้แล้วก็ตาม</p>
<p>วิธีที่ผมเลือกทำคือ ผมใช้หลักการ <a href="http://www.somkiat.cc/let-start-with-dependency-injection/" target="_blank" rel="noopener">Dependency Injection</a> ซึ่งผมไม่ได้ให้ &#8220;Method ที่เรียก API ธนาคารแห่งประเทศไทย&#8221; เป็นตัวเรียกใช้ HttpClient แต่ผมเปลี่ยนเป็น ใครก็ตามที่เรียกใช้ &#8220;Method ที่เรียก API ธนาคารแห่งประเทศไทย&#8221; ต้องส่ง HttpClient ให้มันด้วย โดยลองดูตัวอย่างต่อไปนี้ครับ</p>
<p>นี่คือโค้ด BotService ที่ผมเขียนไว้เรียกใช้ API ของธนาคารแห่งประเทศไทย ที่ผมมีการส่ง HttpClient เข้ามาที่ Constructor</p>
<p><script src="https://gist.github.com/ifew/b1937af1eada2aba5bd6a562d7837a4a.js"></script></p>
<p>และนี่คือโค้ด Integration Test ของผมที่ไปเรียกใช้ BotService มันอีกที ดังนั้น ผมจะต้องส่ง HttpClient ให้มันด้วย</p>
<p><script src="https://gist.github.com/ifew/c0892111d586b361bd89093978b28f3c.js"></script></p>
<p>ซึ่งจากโค้ดดังกล่าว ใน BotService ผมไม่ใส่ HttpClient ให้กับ BotService เลย เพราะผมต้องการให้ผู้ที่เรียกใช้มันเป็นตัวบอกเองว่า จะส่ง &#8220;HttpClient จริงๆ&#8221; หรือ ส่ง &#8220;HttpClient ที่ Stub&#8221; ให้มัน โดยในตัวอย่างข้างต้น ผมเขียน Integration Test  ดังนั้น ผมจึงส่ง &#8220;HttpClient จริงๆ&#8221; ให้มันไปเลย</p>
<p>คราวนี้ ถ้ากลับมาดูของ Unit Test ผมต้องการส่ง&#8221;HttpClient ที่ Stub&#8221; ให้มันแทน ซึ่งในที่นี้ผมใช้ Library ชื่อ <a href="https://github.com/moq/moq4" target="_blank" rel="noopener">Moq</a> (ถ้าของ Java เช่น <a href="https://github.com/azagniotov/stubby4j" target="_blank" rel="noopener">Stubby4j</a>) ในการทำ Stub เพื่อบอกว่า</p>
<p>ถ้ามีการเรียก URL  ชื่อว่า <em>&#8220;https://iapi.bot.or.th/Stat/Stat-ExchangeRate/DAILY_AVG_EXG_RATE_V1/?start_period=2018-02-01&amp;end_period=2018-02-01&amp;currency=USD&#8221;</em> จะต้องส่งค่า JSON ที่ผมต้องการออกมาเสมอ ดังนั้นจะได้โค้ดของ Unit Test ชื่อ <span class="pl-en">ExchangeRateServiceTest</span>() ดังนี้</p>
<p><script src="https://gist.github.com/ifew/0b83f7194a17d96f10a1ee92cd931d25.js"></script></p>
<p>คราวนี้ เมื่อลองรันทดสอบทั้ง Unit Test และ Integration Test ทั้งหมด จะเกิด Unit Test 9 ตัว และ Integration Test 1 ตัว</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4468" src="https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.44.43.png" alt="" width="1046" height="512" srcset="https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.44.43.png 1046w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.44.43-600x294.png 600w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.44.43-1024x501.png 1024w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.44.43-768x376.png 768w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.44.43-700x343.png 700w" sizes="auto, (max-width: 1046px) 100vw, 1046px" /></p>
<p>และถ้าหากเรามาเช็คเรื่อง Code Coverage ว่า โค้ดที่เราเขียนมาทั้งหมดนี้ มีการ Test ครอบคลุมครบทุกส่วนของ Business Condition หรือไม่ ผมที่ได้คือ ..</p>
<p><img loading="lazy" decoding="async" class="alignnone size-full wp-image-4469" src="https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.48.59.png" alt="" width="1150" height="486" srcset="https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.48.59.png 1150w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.48.59-600x254.png 600w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.48.59-1024x433.png 1024w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.48.59-768x325.png 768w, https://myifew.com/wp-content/uploads/2018/04/Screen-Shot-2561-04-10-at-15.48.59-700x296.png 700w" sizes="auto, (max-width: 1150px) 100vw, 1150px" /></p>
<h2>สรุป</h2>
<p>ด้วยกระบวนการทั้งหมดที่ผมเขียนไว้ ตั้งแต่การออกแบบ จนมาทำเป็น Unit Test และ Integration Test ซึ่งถ้าทำได้ มันคือการทำให้เกิด Automated Test ดังนั้นไม่ว่าโค้ดเราจะแก้ไขอะไรไป เราก็แค่กลับมารัน Unit Test และ Integration Test ของเรา ให้มันตรวขสอบให้เราทุกเคสแบบอัตโนมัติว่ายังทำงานได้ถูกต้องหรือไม่ จากนั้นเราก็มาตรวจสอบอีกชั้นว่า นอกจากถูกต้องแล้ว ชุด Test ของเรา ยังครอบคลุมทุกบรรทัดที่เราเขียนไว้หรือไม่</p>
<p>เมื่อได้ Automated Test แล้ว การจะไปทำกระบวนการ Continous Integration, Continous Deployment ก็ไม่ยากแล้วครับ เพราะเรามั่นใจได้ว่าโค้ดของเรามีการทดสอบและครอบคลุมเสมอ พร้อม Deploy ไปสู่ระบบต่างๆ ได้ทันทีเมื่อเขียนระบบเสร็จ เกิดการ feedback จากผู้ใช้งานได้ไว และแก้ไขงานได้ไว มันก็จะเป็นส่วนหนึ่งที่ไปเสริมความให้เกิด Agile ในองค์กรต่อไป</p>
<p>โค้ดตัวอย่างทั้งหมด ดูได้ที่ <a href="https://github.com/ifew/dojo-BotExchangeRate" target="_blank" rel="noopener">https://github.com/ifew/dojo-BotExchangeRate</a></p>
<hr />
<p><strong>เพิ่มเติมทิ้งท้าย</strong></p>
<ul>
<li>ผมสังเกตว่า API Exchange Rate ของธนาคารแห่งประเทศไทย ถ้าวันไหนไม่มีอัตราแลกเปลี่ยน เช่น วันหยุด, วันเสาร์-อาทิตย์ ช่วงเวลา 00:00-17:59 จะใช้ค่าเงินล่าสุดของวันก่อนหน้านั้น แต่ถ้าวันปัจจุบันมีอัตราแลกเปลี่ยนและเวลามากกว่าหรือเท่ากับ 18:00 จะใช้ค่าเงินของวันนั้น</li>
<li>ในตัวอย่าง ผมไม่ได้ตรวจสอบว่า ถ้า API Exchange Rate ของธนาคารแห่งประเทศไทย ไม่สามารถเรียกใช้งานได้ จะต้องทำอย่างไรต่อไป</li>
<li>ทำไมต้องปัดเศษทศนิยมขึ้นที่ตำแหน่งที่สองด้วย? .. เพราะผมหน้าเลือดครับ ฮี่ๆ</li>
</ul>
</div>
]]></content:encoded>
					
					<wfw:commentRss>https://myifew.com/4455/design-and-develop-code-for-testable/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>จะทำระบบให้รองรับ Automated Test ได้อย่างไร (Testable)</title>
		<link>https://myifew.com/4414/design-project-code-structure-for-testable/</link>
					<comments>https://myifew.com/4414/design-project-code-structure-for-testable/#respond</comments>
		
		<dc:creator><![CDATA[iFew]]></dc:creator>
		<pubDate>Sat, 17 Feb 2018 19:07:10 +0000</pubDate>
				<category><![CDATA[Technology]]></category>
		<category><![CDATA[Automation Testing]]></category>
		<category><![CDATA[Continuous Integration]]></category>
		<category><![CDATA[Integration Test]]></category>
		<category><![CDATA[Test Strategy]]></category>
		<category><![CDATA[Testable]]></category>
		<category><![CDATA[Unit Test]]></category>
		<guid isPermaLink="false">https://myifew.com/?p=4414</guid>

					<description><![CDATA[ตอนที่หัดเขียน Automated  Test ผมเคยเจอปัญหาว่า โค้ดเก่าที่เขียนมา มันไม่รองรับการเขียน Unit Test, Integration Test เอาซะเลย แกะยากเหมือนกัน จนแล้วจนรอดก็ลองทำใหม่ แล้วฝึกกับโจทย์ไปเรื่อยๆ จนพอตกผลึกได้บ้าง ว่าถ้าจะทำระบบให้รองรับการ Test (Testable) จะต้องทำอย่างไร

วันก่อน มีน้องในทีมถามว่า "ผมจะเขียน Integration Test อย่างไรดี" ก็เลยได้ย้อนไปทบทวนประสบการณ์ หาข้อมูล และถามจากผู้รู้เพิ่มเติม จึงกลับไปเล่าให้น้องฟัง ก็คิดว่ามีประโยชน์ จึงอยากแชร์ไว้มาแลกเปลี่ยนความรู้กัน]]></description>
										<content:encoded><![CDATA[<p>ตอนที่หัดเขียน Automated  Test ผมเคยเจอปัญหาว่า โค้ดเก่าที่เขียนมา มันไม่รองรับการเขียน Unit Test, Integration Test เอาซะเลย แกะยากเหมือนกัน จนแล้วจนรอดก็ลองทำใหม่ แล้วฝึกกับโจทย์ไปเรื่อยๆ จนพอตกผลึกได้บ้าง ว่าถ้าจะทำระบบให้รองรับการ Test (Testable) จะต้องทำอย่างไร</p>
<p>วันก่อน มีน้องในทีมถามว่า &#8220;ผมจะเขียน Integration Test อย่างไรดี&#8221; ก็เลยได้ย้อนไปทบทวนประสบการณ์ หาข้อมูล และถามจากผู้รู้เพิ่มเติม จึงกลับไปเล่าให้น้องฟัง ก็คิดว่ามีประโยชน์ จึงอยากแชร์ไว้มาแลกเปลี่ยนความรู้กัน<span id="more-4414"></span></p>
<h2>ต้องเข้าใจโครงสร้างโปรเจ็คเสียก่อน</h2>
<p>ทุกวันนี้เราเข้าใจโครงสร้างของโปรเจ็คเราดีแล้วหรือยัง หรือถ้าเข้าใจดีแล้ว เราได้แยกส่วนชัดเจนไหมว่าส่วนไหนเป็นโค้ดของระบบ (Production Code) และส่วนไหนเป็นโค้ดของการทดสอบ (Test Code)</p>
<p>ขอยกตัวอย่าง ถ้าผมจะเขียนโปรเจ็คตัวหนึ่งคือ ระบบ API (My API) โดยมีการเชื่อมต่อไปยังระบบ API ข้างนอก (External API) จากนั้นผมมีโปรเจ็ค Test แยกออกมาอีกตัวหนึ่ง รูปร่างหน้าตามันก็จะเป็นเช่นนี้</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter wp-image-4424 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest1.png" alt="" width="591" height="277" />(รูปโครงสร้างโปรเจ็ค)</p>
<p>ลองเจาะลึกเข้าไปในการทำงานของโปรเจ็ค ในที่นี้ผมเขียนเป็น MVC ซึ่งถ้าผมเป็น User ผมจะเรียกใช้ API ตัวหนึ่ง ผ่าน Controller จากนั้นไปเรียก Services เพื่อทำการติดต่อไปยัง External API และรับค่ามาแสดงให้ User ได้เห็น</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter wp-image-4425 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest2.png" alt="" width="654" height="277" srcset="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest2.png 654w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest2-600x254.png 600w" sizes="auto, (max-width: 654px) 100vw, 654px" />(รูปการทดงานภายในโปรเจ็ค)</p>
<h2>แยกโปรเจ็คของชุดทดสอบ (Test) ให้ชัดเจน</h2>
<p>จากรูปการทดงานภายในโปรเจ็ค, ผมสมมติว่า Controller ผมมี Business Logic อะไร เป็นเพียงการรับ Request และส่งผ่าน Data ไปให้ Services ทำงานต่อ คราวนี้ถ้าผมจะเขียนทดสอบ ผมควรต้องเขียนทดสอบที่ Services ซึ่งในที่นี้ผมจะทดสอบการทำงานในชั้นที่เล็กที่สุด นั่นคือ Unit Test</p>
<p>แต่ตามทฤษฎีแล้ว คุณสมบัติที่ดีของ Unit Test จะต้องเป็นไปตาม<a href="http://www.somkiat.cc/good-unit-test-with-first/" target="_blank" rel="noopener">หลักการของ FIRST</a> คือ</p>
<ul>
<li>Fast: ทำงานได้เร็ว</li>
<li>Isolated: เป็นอิสระจากกัน</li>
<li>Repeatable: ทำซ้ำได้ ไม่ขึ้นกับระบบอื่นๆ เช่น API ภายนอก, Database, File System</li>
<li>Self-Verify: แสดงให้เห็นผลการทดสอบ ผ่าน หรือไม่ผ่านได้อย่างชัดเจน</li>
<li>Timely: เขียนให้ถูกเวลา คือ ควรมี Test ก่อน เพื่อให้ได้เข้าใจปัญหา และจึงเขียนโค้ดเข้ามาแก้ปัญหานั้น</li>
</ul>
<p>หมายความว่า ถ้าผมทำ Unit Test ผมจะไม่สามารถทำการทดสอบได้เลย ถ้า External API พัง หรือ Internet ล่ม เป็นต้น</p>
<p>ดังนั้น ผมต้องเขียนโค้ดทดสอบเพิ่มเข้ามาอีกชุด  ที่เรียกว่า Integration Test</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter wp-image-4417 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-IntegrationTest.png" alt="" width="591" height="277" />(รูปการทำงานภายในโปรเจ็คที่มี Unit Test และ Integration Test)</p>
<p>ถ้าดูจากเส้นการทำงาน ผมจะเขียน Unit Test เพื่อทดสอบแค่กระบวนการภายใน Services ของผมเอง (เส้นสีน้ำเงิน) ส่วน Integration Test ผมจะทดสอบกระบวนการที่ Services ผมไปเรียกใช้ External API (เส้นสีเขียว)</p>
<h2>วิเคราะห์การทำงานของโค้ด และแยกแยะให้ถูก</h2>
<p>มาถึงตรงนี้พอเห็นภาพโครงสร้างกว้างๆ แล้วใช่ไหมครับว่า เราจะแยกการทดสอบอย่างไร คราวนี้เราจะลองลงไปให้ลึกเข้าไปอีก ในระดับของโค้ด</p>
<p>ผมสมมติว่า Services ที่ผมทำขึ้นมี Method ชื่อว่า IsAdd() โดยรับหมายเลขสองตัวมาบวกกัน จากนั้นส่งไปให้ External API ตรวจสอบว่าคำนวณถูกต้องไหม ถ้าถูก ให้คืนค่า True กลับมา</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter wp-image-4422 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method.png" alt="" width="704" height="387" srcset="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method.png 704w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-600x330.png 600w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-700x385.png 700w" sizes="auto, (max-width: 704px) 100vw, 704px" />(รูปของ Method IsAdd() ที่ยังไม่รู้กระบวนการภายใน)</p>
<p>จากคำอธิบายที่ผ่านมา มันยังเป็นเพียง Black Box ที่เราแทบไม่เห็นการทำงานของมันเลย รู้แค่ว่าส่งอะไรเข้าไป และคืนค่าอะไรกลับมา ดังนั้น เราจะต้องวิเคราะห์การทำงานของโค้ดเราให้กระจ่างเสียก่อน</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter wp-image-4418 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Explain.png" alt="" width="704" height="387" srcset="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Explain.png 704w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Explain-600x330.png 600w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Explain-700x385.png 700w" sizes="auto, (max-width: 704px) 100vw, 704px" />(รูปของ Method IsAdd() ที่เราวิเคราะห์ว่ามีการทำงานใดเกิดขึ้นบ้าง)</p>
<p>หลังจากวิเคราะห์ออกมา เราพบว่ามีการทำงานสองส่วนคือ</p>
<ul>
<li>Process A: เป็นการคำนวณเลขสองตัวที่รับเข้ามาว่าบวกกันได้เท่าไร</li>
<li>Process B: นำค่าที่ได้จากการคำนวณ Process A ส่งไปให้ External API และรับค่าผลลัพธ์กลับมา</li>
</ul>
<p>แปลว่า เราต้องแบ่ง Method ตัวนี้ออกเป็นสอง Method เพื่อทำการทดสอบ Unit Test กับ Process A และ Integration Test กับ Process B</p>
<p>ถ้าค้นพบว่างานที่เราทำอยู่ กำลังเป็นเช่นนี้ และต้องแยกมันออกมา นั่นคือ เราเขียน Method นี้ ไม่ดีแต่แรกแล้วครับ เพราะอย่าลืมว่า 1 Method ควรจะมีเพียง 1 การทำงานเท่านั้น</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter wp-image-4421 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full.png" alt="" width="757" height="387" srcset="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full.png 757w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-600x307.png 600w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-700x358.png 700w" sizes="auto, (max-width: 757px) 100vw, 757px" />(รูปของ Method IsAdd() ที่เราวิเคราะห์การทำงาน และแบ่งชุดทดสอบ)</p>
<h2>ต้องรองรับกระบวนการ Continuous Integration ได้ด้วย</h2>
<p>เมื่อโครงสร้างของโปรเจ็คและโครงสร้างของโค้ดเรา สามารถรองรับการทดสอบได้แล้ว สิ่งที่ต้องคิดต่อมาคือ จะนำเข้ากระบวนการ Continuous Integration ได้อย่างไร เพื่อให้เกิด Pipeline และ Feedback Loop ที่เร็ว</p>
<p>เพราะ Unit Test สามารถอยู่ได้ด้วยตัวเอง แต่ Integration Test จะต้องเกี่ยวข้องกับระบบอื่นๆ เช่น  Database หรือ File System ซึ่งระบบเหล่านี้ควรมีประบวนการพร้อมใช้งานก่อน ดังนั้น ถ้ามันอยู่ร่วมโปรเจ็คเดียวกัน คงไม่ดีแน่นอน และไม่รู้ได้ด้วยว่าถ้าเกิดการรันและ Fail มันเกิดที่กระบวนการของ Unit หรือ Integration กันแน่ จึงควรแยกโปรเจ็คของชุดทดสอบนี้ออกจากกัน</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="aligncenter wp-image-4420 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated.png" alt="" width="757" height="387" srcset="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated.png 757w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated-600x307.png 600w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated-700x358.png 700w" sizes="auto, (max-width: 757px) 100vw, 757px" />(รูปของ Method IsAdd() ที่เราแบ่งชุดทดสอบ ออกเป็น 2 Project)</p>
<p style="text-align: center;"><img loading="lazy" decoding="async" class="alignnone wp-image-4426 size-full" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Build_Pipeline.png" alt="" width="782" height="63" srcset="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Build_Pipeline.png 782w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Build_Pipeline-600x48.png 600w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Build_Pipeline-768x62.png 768w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Build_Pipeline-700x56.png 700w" sizes="auto, (max-width: 782px) 100vw, 782px" /><br />
(รูปตัวอย่างเมื่อแยกโปรเจ็ค Unit Test, Integration Test แล้ว สามารถกำหนด Pipeline ให้เหมาะสมได้)</p>
<h2>Tip: ใช้ Postman ทดสอบ Integration Test จะได้หรือไม่</h2>
<p>คำถามนี้ตอนแรกต้องยอมรับว่า ผมก็สับสนเหมือนกัน แต่เพิ่งได้ข้อสังเกตมาจากผู้รู้ว่า ความต่างระหว่างที่เราเขียนโค้ดทดสอบเพื่อเรียก API ตัวเอง กับใช้ Postman เพื่อทดสอบ API ตนเอง จะเรียกทั้งคู่เป็น Integration Test ได้หรือไม่</p>
<p>คำตอบคือ ใช้ Postman ทดสอบเรียก API จะไม่เรียกเป็น Integration Test แม้ว่าภายใน API นั้นจะเรียกไปที่ต่างๆจริง คืนค่าจริงกลับมา, แต่ถ้าให้ถูกต้องจะเรียกว่า End-to-End Testing</p>
<p>เพราะเราเอาระบบภายนอก (Postman) เรียกไปที่ API ว่าทำงานได้หรือไม่ แต่เราไม่ได้ทดสอบว่าระบบทั้งสองทำงานถึงกันได้หรือไม่ (เส้นสีชมพู) นั่นจึงเป็นที่มาว่า ใช้ Postman ก็อาจไม่ชัวร์ว่า ระบบเราจะทำงานได้จริง</p>
<p>หรือให้ลองนึกอีกที เคยไหมครับ ที่ใช้ Postman ทดสอบ API ได้ แต่พอรันโค้ด กลับทำงานไม่ได้ เพราะมี คอมพิวเตอร์เรามี Environment เรียกใช้ต่างกันกับเครื่อง Server เป็นต้น</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-4419 aligncenter" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated-Tools.png" alt="" width="769" height="419" srcset="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated-Tools.png 769w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated-Tools-600x327.png 600w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated-Tools-768x418.png 768w, https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-Method-Full-Separated-Tools-700x381.png 700w" sizes="auto, (max-width: 769px) 100vw, 769px" /></p>
<h2>Tip: ถ้าอยากทดสอบทุกกระบวนการ แต่ไม่ต่อไปถึง External API จะทำอย่างไร</h2>
<p>เป็นคำถามที่ถูกถามบ่อยเหมือนกัน และโปรแกรมเมอร์ก็มักเจอบ่อยเช่นกันว่า</p>
<ul>
<li>ถ้าจะทดสอบ Services ตัวเอง โดยยังไม่อยากไปเชื่อมต่อระบบภายนอก</li>
<li>อยากทดสอบ Services ตัวเอง แต่ระบบภายนอกยังทำไม่เสร็จ</li>
<li>อยากทดสอบ Services ตัวเอง แต่ระบบภายนอกทำงานช้า ไม่ทันใจ</li>
</ul>
<p>จากปัญหาข้างต้น เราสามารถนำวิธีการชื่อว่า Stub จาก <a href="http://www.somkiat.cc/test-double-mock-stub-and-dummy/" target="_blank" rel="noopener">Test Double</a> มาใช้ได้ คือทำ Stub ตัว External API มันซะเลย เพื่อให้ได้ค่าที่เราต้องการเสมอ และไม่ต้องออกไปรับค่าจริงจาก External API</p>
<p>ถามว่าแล้วจะทำไปทำไม ก็ต้องตอบว่า เมื่อเราทดสอบการทำงานย่อยๆแล้ว (Unit Test) และทดสอบการทำงานที่สองระบบเชื่อมถึงกันแล้ว (Integration Test) เราอยากจะสร้างความมั่นใจเพิ่มไหมล่ะ ว่า ถ้าทั้งสองการทดสอบทำงานร่วมกัน มันยังถูกต้องอยู่</p>
<p>เราจะเรียกการทดสอบแบบนี้ว่า Component Test ซึ่งในที่นี้เราต้องการทดสอบการทำงานทั้งหมดของ Service เรา แต่เราไม่ได้สนใจว่า External API จะอยู่หรือจะตาย ทำงานได้ถูกต้องหรือไม่ ดังนั้น เราจึงต้องจำลอง External API มันขึ้นมา โดยใช้ Stub นั่นเอง (เขียนเองก็ได้ หรือหา Library ฟรีมาใช้ก็ได้ เช่น  .Net Core: <a href="https://github.com/markvincze/Stubbery" target="_blank" rel="noopener">Stuberry</a>, Java: <a href="https://github.com/azagniotov/stubby4j" target="_blank" rel="noopener">Stubby4j</a>)</p>
<p><img loading="lazy" decoding="async" class="size-full wp-image-4416 aligncenter" src="https://myifew.com/wp-content/uploads/2018/02/IntegrationTest-ComponentTest.png" alt="" width="591" height="277" /></p>
<h2>สรุป</h2>
<p>การทำระบบให้รองรับ Automated Test เป็นอะไรที่สนุกเหมือนกัน ต้องวางแผนตั้งแต่แรกเลยว่าจะเป็นอย่างไร แล้วส่วนมากเราก็มักไม่ทำกันซะด้วย เพิ่งมาตามเก็บเอาตอนหลัง เหมือนที่ผมเกริ่นไปตอนต้น ว่าถ้ามีโปรเจ็คเก่าแล้วเอามาทำ เป็นอะไรที่ต้องแก้เยอะพอสมควร แต่ถ้าเป็นโปรเจ็คใหม่ ก็สามารถออกแบบให้ดีตั้งแต่ต้นได้เลย หากใครผ่านมาอ่าน มีคำแนะนำเพิ่มเติม หรือคำถาม สามารถแลกเปลี่ยนความรู้กันได้เลยนะครับ</p>
<p>สำหรับตัวอย่างการแยก Test ลองดูที่ GitHub ของผมได้ครับ มีแต่ <a href="https://github.com/ifew/netcore-lab" target="_blank" rel="noopener">.NET Core</a> นะ กำลังอิน ฮ่าๆ (<a href="https://github.com/ifew/netcore-lab/blob/master/tests/api.UnitTest/BotTest.cs">BOT UnitTest</a>, <a href="https://github.com/ifew/netcore-lab/blob/master/tests/api.IntegrationTest/BotTest.cs" target="_blank" rel="noopener">BOT IntegrationTest</a>, <a href="https://github.com/ifew/netcore-lab/blob/master/src/api/Services/BotService.cs" target="_blank" rel="noopener">BOT Service</a>)<br />
<script>
ga('set', 'page', '/testable');
ga('send', 'pageview');
</script></p>
<hr />
<p>อ่านภาคการนำไปประยุกต์ใช้ต่อได้ที่ <a href="https://myifew.com/4455/design-and-develop-code-for-testable/">ภาคปฎิบัติ: จะทำระบบให้รองรับ Automated Test ได้อย่างไร (Testable)</a></p>
]]></content:encoded>
					
					<wfw:commentRss>https://myifew.com/4414/design-project-code-structure-for-testable/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
