<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Testing on yothinix</title><link>https://yothinix-com.pages.dev/tags/testing/</link><description>Recent content in Testing on yothinix</description><generator>Hugo</generator><language>en-us</language><lastBuildDate>Fri, 18 Feb 2022 16:00:53 +0700</lastBuildDate><atom:link href="https://yothinix-com.pages.dev/tags/testing/index.xml" rel="self" type="application/rss+xml"/><item><title>ใครว่า E2E เทสรันช้า เทคนิคง่ายๆ ที่จะทำให้ Cypress รันเร็วติดจรวด</title><link>https://yothinix-com.pages.dev/improve-cypress-test-performance/</link><pubDate>Fri, 17 May 2019 00:00:00 +0700</pubDate><guid>https://yothinix-com.pages.dev/improve-cypress-test-performance/</guid><description>เกริ่นก่อนว่าที่ Pronto Tools เราเปลี่ยนมาใช้ Cypress มาได้ซักพักแล้วครับแล้วเราชอบมันมาก เราเลยไม่ลังเลเลยที่จะย้ายและเพิ่ม E2E เทสมาอยู่บน Cypress ซึ่งพอเทสมันเพิ่มขึ้นอย่างรวดเร็ว เวลาที่ใช้รันมันก็เพิ่มขึ้นอย่างมีนัยยะสำคัญ</description><content:encoded><![CDATA[<p>เกริ่นก่อนว่าที่ Pronto Tools เราเปลี่ยนมาใช้ Cypress มาได้ซักพักแล้วครับแล้วเราชอบมันมาก เราเลยไม่ลังเลเลยที่จะย้ายและเพิ่ม E2E เทสมาอยู่บน Cypress ซึ่งพอเทสมันเพิ่มขึ้นอย่างรวดเร็ว เวลาที่ใช้รันมันก็เพิ่มขึ้นอย่างมีนัยยะสำคัญ</p>
<h1 id="ทำไม-e2e-เทสถงรนชา-">ทำไม E2E เทสถึงรันช้า 🐢</h1>
<p>ปัญหาหลักเลยคือ E2E เทสจะค่อนข้างมี Step ที่ทำงานซ้ำๆ เกิดขึ้นบ่อยๆ เพื่อ Setup ข้อมูลให้พร้อมที่จะเทสใน TestCase หลัก ปัญหาหลักๆ ก็คืองานซ้ำๆ นั้นถ้ามันใช้เวลารันนาน ถ้ามันยิ่งมีจำนวนมากขึ้นก็จะยิ่งทำให้ชุดทดสอบเราทำงานช้าขึ้นเรื่อยๆ ลองมาดูตัวอย่างของเทสกันครับ</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;Pricing plan page&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">(</span><span class="s1">&#39;Product Plan Section&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see Product title&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">signUp</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-title&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;Basic Plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see price&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">signUp</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-price&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;$599&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>จากโค้ดตัวอย่างข้างต้น จะเป็นการเทสว่าหน้า Pricing Plan เราเนี่ยมันมีชื่อ Plan กับราคาอยู่ ซึ่งจะเห็นว่าการจะทดสอบ TestCase นึงนี่ เราต้องเสียเวลาไปรัน command cy.signUp() ซึ่งเป็น command ที่เราสร้างให้เอาไว้สมัครสมาชิกเข้า service เรา ซึ่งเวลาสมัครใช้งานเนี่ย มันจะมีการทำงานหลายอย่างเกิดขึ้นไม่ว่าจะเป็น สร้าง subscription user, ส่งอีเมล์ยืนยันการสมัคร, ฯลฯ แต่ละอย่างนี่ใช้เวลาทั้งนั้นครับ</p>
<h1 id="เปลยนวธคด-">เปลี่ยนวิธีคิด 🤔</h1>
<p>จาก Test Suite ข้างบนนะครับ พอมี TestCase ใหม่เพิ่มขึ้นด้วยสัญชาติญาณ Copy-Paste ของ Developer สิ่งที่เราจะทำก็คือ Copy step ของเทสเคสเก่าออกมาแล้วแก้ในส่วนที่จะ Assertion ซึ่งก็ยิ่งเป็นการเพิ่มเวลาที่ใช้รัน cy.signUp() ให้เพิ่มขึ้นไปอีกตัว จนเขียนเทสแก้ทีนึงก็ไปกดน้ำ ชงกาแฟรอได้เลยกว่าจะรันเสร็จ</p>
<p>พอเอาเรื่องนี้ไปปรึกษาเทพบอส Automate Tester ประจำทีมเราที่มีประสบการณ์การทำ Automate test มาอย่างโชคโชน เทสบอสก็ให้คำแนะนำที่โคตรเทพกลับมาว่า</p>
<blockquote>
<p><em>แทนที่เราจะ sign up ใหม่ทุกครั้ง ทำไมเราไม่ setup test data แล้ว login แทนหละ</em></p>
</blockquote>
<p>ประโยคอาจจะไม่ตามนี้เป๊ะๆ แต่ไอเดียมันประมาณนี้แหละ พอได้ยินแบบนั้นหน้าตาเทสเราเปลี่ยนไปเลยครับ</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;Pricing plan page&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">(</span><span class="s1">&#39;Product Plan Section&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see Product title&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">login</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-title&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;Basic Plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see price&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">login</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-price&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;$599&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>เสียดายมันทำ highlight สีไม่ได้แต่จุดที่เปลี่ยนคือแทนที่เราจะทำ command cy.signUp() ซึ่งใช้เวลานานมากในการทำงานอย่างที่กล่าวไว้ข้างต้น เราก็ setup test data ในเครื่องเป้าหมายให้เรียบร้อยก่อน ในทีนี้คือ User และข้อมูลที่เราจะ assert ที่ครอบคลุมเกือบทุกเคส หลังจากนั้นเราเปลี่ยนไปใช้ command cy.login() ซึ่งเป็น command ที่ใช้เวลาในการทำงานน้อยกว่า sign up เยอะระดับนึงเลย (เพราะไม่ต้องกรอกข้อมูลอะไรนอกจาก user / pass แล้วกด login</p>
<h1 id="dont-repeat-yourself-">Don’t Repeat Yourself 👣</h1>
<p>พอ Test Suite มันออกมาหน้าตาแบบนี้แล้ว ด้วยสัญชาติญาณ Developer ซึ่งเกลียด Duplication เข้าไส้ เราเลยจะย้ายโค้ดซ้ำๆ มาไว้ที่เดียวกัน ซึ่ง Cypress มีจุดให้เรา setup data ก่อนจะรันเทสทุกครั้งครับ ตรงนั้นครับเรียกว่า <strong>beforeEach</strong>() ซึ่งพอย้ายแล้ว Test Suite เราก็จะออกมาหน้าตาประมาณนี้</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;Pricing plan page&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">(</span><span class="s1">&#39;Product Plan Section&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    
</span></span><span class="line"><span class="cl">    <span class="nx">beforeEach</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">login</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see Product title&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-title&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;Basic Plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see price&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-price&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;$599&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>ซึ่งจะเห็นว่าแต่ละ TestCase มันก็ยังรัน cy.login() และ cy.visit() ให้ครับ แต่ตัว test suite หน้าตาเราจะสะอาดขึ้นมาเพราะโค้ดซ้ำๆ ถูกย้ายไปไว้ใน <strong>beforeEach</strong> แล้ว</p>
<h1 id="ไปใหสดแลวหยดท-before-">ไปให้สุดแล้วหยุดที่ before 🔥</h1>
<p>พอเราย้ายโค้ดที่ login มาไว้ใน beforeEach แล้วจะเห็นว่าถึงแม้มันจะลดโค้ดซ้ำๆ ได้ แต่มันก็ยังทำซ้ำๆ อยู่ๆ ดี หมายความว่า เราต้องเสียเวลา login ทุกครั้งที่เริ่มทุก TestCase เลยเกิดไอเดียว่า แล้วถ้ามัน login แค่ครั้งแรกครั้งเดียวละ มันต้องรันเร็วขึ้นแน่ๆ เลย</p>
<p>โชคดีที่นอกจาก Cypress จะมี <strong>beforeEach</strong> ให้ใช้แล้ว ยังมี <strong>before</strong> ให้ใช้ด้วยครับ ซึ่ง <strong>before</strong> จะต่างจาก <strong>beforeEach</strong> คือมันจะรันแค่ตอนเริ่มต้นของ context ครั้งเดียวเท่านั้น พอเรามีความรู้ตรงนี้ปั๊ป โค้ดมันเลยถูกปรับให้เป็นแบบนี้ครับ</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;Pricing plan page&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">(</span><span class="s1">&#39;Product Plan Section&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">before</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">login</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>    
</span></span><span class="line"><span class="cl">    <span class="nx">beforeEach</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see Product title&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-title&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;Basic Plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see price&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-price&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;$599&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>แต่ว่าสิ่งที่เกิดขึ้นไม่เป็นแบบที่เราคิดครับ พอเราเปลี่ยนมาถึงจุดนี้ TestCase พังกระจายเลยครับ และบอกว่ามันไม่ได้ login ซึ่งทำให้เราประหลาดใจมาก เพราะเอ๊ะมัน login แล้วนิ มันน่าจะทำงานได้นะ</p>
<p>สิ่งที่เกิดขึ้นก็คือ Cypress มันฉลาดมากครับ โดยก่อนจะเริ่มแต่ละ TestCase มันจะทำการ Clear browser state ให้ซึ่ง state ที่ว่านั้นก็คือ Cookie นั้นเองครับ เพราะว่าเวลาเรา Login web service เนี่ย มันต้องทำการเก็บ state ว่ามัน Login อยู่ไว้ซักที่ครับ ในเคสของเรา webservice เรา based on Django Web Framework ครับ โดยหลังจาก login ตัว state จะถูกเก็บไว้ที่ Cookie ที่ชื่อ sessionid และอีกอันนึงคือ csrftoken ครับ</p>
<p>พอเรารู้แบบนี้ สิ่งที่เราคิดต่อมาคือ เราจะทำยังไงให้ Cypress มันไม่ clear cookie ก่อนจะเริ่มรันแต่ละเทส หลังจากค้น Document มาเราก็ไปเจอ command ข้างล่างนี้ครับ</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">Cypress</span><span class="p">.</span><span class="nx">Cookies</span><span class="p">.</span><span class="nx">preserveOnce</span><span class="p">()</span>
</span></span></code></pre></div><p>โดย command นี้จะมีหน้าที่บอกว่าก่อนเริ่มต้น TestCase ใหม่ ไม่ต้อง reset cookie 2 ตัวที่ระบุทิ้งไว้นะโว้ย เราเลยเอามาปรับใช้กับ Test Suite ของเราออกมาหน้าตาเป็นแบบนี้ครับ</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;Pricing plan page&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">(</span><span class="s1">&#39;Product Plan Section&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">before</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">login</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">beforeEach</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Cypress</span><span class="p">.</span><span class="nx">Cookies</span><span class="p">.</span><span class="nx">preserveOnce</span><span class="p">(</span><span class="s1">&#39;sessionid&#39;</span><span class="p">,</span> <span class="s1">&#39;csrftoken&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see Product title&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-title&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;Basic Plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see price&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-price&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;$599&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><p>ทีนี้เราก็สามารถทำให้ Test Suite เรา Login ครั้งเดียวแล้วรัน TestCase ทั้งหมดใน context ได้แล้วครับ</p>
<h1 id="เอามนไปอยรวมกบ-testcase-อน-">เอามันไปอยู่ร่วมกับ TestCase อื่น 👩‍👩‍👧‍👧</h1>
<p>พอเรา Optimize Test Suite นี้จนไวขนาดนี้แล้วก็ได้เวลารัน Test Suite ทั้งหมดแล้ว ซึ่งพอเรารันครั้งแรกก็เจอปัญหาเลยครับ เพราะว่ามันไม่ได้เคลียร์ cookie ให้ ทำให้ Test Suite ที่เกี่ยวกับ Sign up จริงๆ พังหมดเลยเพราะ state มันไม่ได้เริ่มต้นที่ unauthenticate user</p>
<p>ความคิดแรกที่คิดคือ เราต้อง Clear Cookie ตอนรันจบ Test Suite นี้ ซึ่งสัญชาติญาณแรกคือมี <strong>before</strong> ก็ต้องมี <strong>after</strong> แต่พอเราเอาไปใส่ <strong>after</strong> เทพบอสคนเดิมก็ได้ให้คำแนะนำที่ดีมากมาอีกแล้วว่า</p>
<blockquote>
<p><em>ถ้าเราเอา step clearCookie นี่ไปไว้ใน after ถ้า TestCase เราพังระหว่างทางแล้วใครจะเป็นคน clearCookie หล่ะ มันเลยควรจะไปอยู่ใน before ก่อนทุกๆ Step ใน TestSuite เรา จะเป็นการรับประกันว่ามันจะถูก clearCookie แน่นอนก่อนเริ่ม Test Suite นี้</em></p>
</blockquote>
<p>จากความรู้ตรงนี้ Test Suite สุดท้ายของเราเลยออกมาหน้าตาแบบนี้ครับ</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-javascript" data-lang="javascript"><span class="line"><span class="cl"><span class="nx">describe</span><span class="p">(</span><span class="s1">&#39;Pricing plan page&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nx">context</span><span class="p">(</span><span class="s1">&#39;Product Plan Section&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nx">before</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">clearCookies</span><span class="p">()</span>      
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">login</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">beforeEach</span><span class="p">(()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">Cypress</span><span class="p">.</span><span class="nx">Cookies</span><span class="p">.</span><span class="nx">preserveOnce</span><span class="p">(</span><span class="s1">&#39;sessionid&#39;</span><span class="p">,</span> <span class="s1">&#39;csrftoken&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">visit</span><span class="p">(</span><span class="s1">&#39;/plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see Product title&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-title&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;Basic Plan&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">    <span class="nx">it</span><span class="p">(</span><span class="s1">&#39;Should see price&#39;</span><span class="p">,</span> <span class="p">()</span> <span class="p">=&gt;</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">      <span class="nx">cy</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;.plan-price&#39;</span><span class="p">).</span><span class="nx">should</span><span class="p">(</span><span class="s1">&#39;contain&#39;</span><span class="p">,</span> <span class="s1">&#39;$599&#39;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="p">})</span>
</span></span><span class="line"><span class="cl">  <span class="p">})</span>
</span></span><span class="line"><span class="cl"><span class="p">})</span>
</span></span></code></pre></div><h1 id="recap-">Recap 🛡</h1>
<p>ก่อนจะจบเรามาทวนกันก่อนครับว่าใน blog นี้เรารู้อะไรบ้าง</p>
<ul>
<li>ใน E2E เราควรลด Operation ที่ใช้เวลานานให้น้อยที่สุดเราจะได้ Feedback อย่างรวดเร็ว</li>
<li>Cypress มี <strong>beforeEach</strong> ให้ใช้กรุ๊ป command ที่เราต้องการรันก่อนทุก TestCase แต่ถ้าอยากรันครั้งเดียวใน <strong>Context</strong> จะมี command <strong>before</strong> ให้</li>
<li>เรารู้จักว่า Cypress จะจัดการเกี่ยวกับ Cookie ให้เราเสมอ ถ้าอยากเก็บ Cookie ไว้ให้ใช้ Cypress.<strong>Cookies.preserveOnce</strong> แต่ถ้าอยากลบให้ใช้ cy.<strong>clearCookies()</strong></li>
</ul>
<p>ก็หวังว่า blog นี้จะมีประโยชน์นะครับสำหรับใครที่เริ่มรู้สึกว่า E2E รันช้าเหลือเกิน แล้วพบกันใหม่ครับ!</p>
<hr>
<p>Original post at: <a href="https://yothinix.medium.com/%E0%B9%83%E0%B8%84%E0%B8%A3%E0%B8%A7%E0%B9%88%E0%B8%B2-e2e-%E0%B9%80%E0%B8%97%E0%B8%AA%E0%B8%A3%E0%B8%B1%E0%B8%99%E0%B8%8A%E0%B9%89%E0%B8%B2-%E0%B9%80%E0%B8%97%E0%B8%84%E0%B8%99%E0%B8%B4%E0%B8%84%E0%B8%87%E0%B9%88%E0%B8%B2%E0%B8%A2%E0%B9%86-%E0%B8%97%E0%B8%B5%E0%B9%88%E0%B8%88%E0%B8%B0%E0%B8%97%E0%B8%B3%E0%B9%83%E0%B8%AB%E0%B9%89-cypress-%E0%B8%A3%E0%B8%B1%E0%B8%99%E0%B9%80%E0%B8%A3%E0%B9%87%E0%B8%A7%E0%B8%95%E0%B8%B4%E0%B8%94%E0%B8%88%E0%B8%A3%E0%B8%A7%E0%B8%94-6ef6cc775ca0">https://yothinix.medium.com/ใครว่า-e2e-เทสรันช้า-เทคนิคง่ายๆ-ที่จะทำให้-cypress-รันเร็วติดจรวด-6ef6cc775ca0</a></p>
]]></content:encoded></item><item><title>TPSE CONFERENCE 2013 ตอนที่ 2 : ROBOT FRAMEWORK</title><link>https://yothinix-com.pages.dev/tpse-conference-2013-2-robot-framework/</link><pubDate>Sun, 24 Nov 2013 00:00:00 +0700</pubDate><guid>https://yothinix-com.pages.dev/tpse-conference-2013-2-robot-framework/</guid><description>&lt;p&gt;ต่อจาก &lt;a href="https://yothinix-com.pages.dev/tpse-conference-2013-1-keynote/"&gt;โพสที่แล้ว&lt;/a&gt; เลยนะครับเพื่อไม่ให้เป็นการเสียเวลา&lt;/p&gt;
&lt;h2 id="robot-framework-generic-test-automation-framework-for-acceptance-testing-and-acceptance-test-driven-development-atdd"&gt;Robot Framework: Generic test automation framework for acceptance testing and acceptance test-driven development (ATDD)&lt;/h2&gt;
&lt;p&gt;สำหรับ Session นี้ผมถือว่าเป็น Session ที่สนุกที่สุดของงานนี้แล้วโดย Speaker เป็นพี่รูฟ &lt;strong&gt;@roofimon&lt;/strong&gt; โดย Session นี้คนเยอะมากๆเข้ามาแทบไม่ขาดสายจนที่นั่งไม่พอเลยทีเดียวสำหรับ Robot Framework อธิบายง่ายๆคือเป็น Automate test framework ตัวนึงที่ช่วยให้งานของ Tester นั้นง่ายขึ้นแต่ก่อนจะเริ่มนั้นพี่รูฟถามว่า &amp;ldquo;&lt;em&gt;ใครรู้สึกตัวว่าอยู่ผิดห้องมั้ยครับ ?&lt;/em&gt;&amp;rdquo; ประมาณ 4-5 รอบเลย&lt;/p&gt;
&lt;p&gt;พี่รูฟเริ่มต้นด้วยการอธิบายคอนเซปของ Requirement ลูกค้าว่าก็เหมือนการสอบสมัยเรียนมหาลัยโดยอาจารย์จะมีโจทย์ที่อลังการงานสร้างที่สุดเท่าที่คิดออกมาให้เราพร้อมกับ&amp;hellip; กระดาษหนึ่งแผ่นหน้าที่ของเราคือเขียนคำตอบไปให้มันตรงกับความต้องการของอาจารย์ให้มากที่สุดโดยพี่รูฟยกตัวอย่างเช่นถ้าสมมติอาจารย์เฉลย 80 แล้วเราตอบ 160/2 แทนที่เราจะได้ซักครึ่งคะแนนแต่กลับกลายเป็นว่าเราไม่ได้เลย &amp;ldquo;&lt;strong&gt;สัส มึงทำผิด !!&lt;/strong&gt;&amp;rdquo; พี่รูฟยังยกตัวอย่างข้อสอบ Tense ภาษาอังกฤษที่จะมีการ Guideline มาเป็น Passage แล้วให้เราหยอดคำลงไปในช่องว่าง (ซึ่งยังพอมั่วได้แม่นกว่า) ซึ่งถ้าเปรียบเทียบกับ S/W แล้วการที่เรามี Guideline ในการตรวจรับที่ชัดเจนจะทำให้เรามีความสุขกับงานที่ทำมากกว่า&lt;/p&gt;
&lt;p&gt;หลังจากนั้นพี่รูฟพูดถึงบริษัท &lt;strong&gt;Odd-e&lt;/strong&gt; นิดหน่อยอันนี้ผมขอข้ามละกันเพราะไม่เกี่ยวกับเนื้อหาประเด็นต่อไปที่พี่รูฟพูดถึงคือถึงแม้เราจะใช้ Agile มาช่วยในการทำงานแล้วก็ตามเราก็ยังคงเจอปัญหา Requirement แบบนี้อยู่เสมอเนื่องจากเรามักจะคิดว่า &amp;ldquo;&lt;em&gt;สิ่งที่เรารู้เป็นสิ่งที่ลูกค้าอยากได้&amp;rdquo; ทำให้เกิดปัญหาเมื่อถึงตอนจบแล้วลูกค้าจำไม่ได้แล้วตอบเรากลับมาว่า &amp;ldquo;ผมไม่ได้พูดอย่างนั้นนะ&lt;/em&gt;&amp;rdquo; ซึ่งพี่รูฟก็ได้ชี้ให้เห็นว่าแท้จริงแล้วนั้นของที่ถูกต้องมันอยู่ในมือของ Tester ไม่ใช่ Programmer แต่เนื่องจาก Programmer เป็น &amp;ldquo;&lt;strong&gt;สิ่งมีชีวิตที่คิดว่าตัวเองฉลาดที่สุดในโลก&lt;/strong&gt;&amp;rdquo; มีความผยองและเป็นอย่างนี้มาหลายชั่วอายุคนปัญหานี้จึงยังเกิดขึ้นอยู่เสมอวิธีแก้คือ &lt;strong&gt;ให้ Programmer หา Tester มาเป็นแฟน&lt;/strong&gt;&amp;hellip; ไม่ใช่ !!! แต่ให้เราเขียน Test ตั้งแต่ขั้น Design กันเลยทีเดียว&lt;/p&gt;</description><content:encoded><![CDATA[<p>ต่อจาก <a href="/tpse-conference-2013-1-keynote/">โพสที่แล้ว</a> เลยนะครับเพื่อไม่ให้เป็นการเสียเวลา</p>
<h2 id="robot-framework-generic-test-automation-framework-for-acceptance-testing-and-acceptance-test-driven-development-atdd">Robot Framework: Generic test automation framework for acceptance testing and acceptance test-driven development (ATDD)</h2>
<p>สำหรับ Session นี้ผมถือว่าเป็น Session ที่สนุกที่สุดของงานนี้แล้วโดย Speaker เป็นพี่รูฟ <strong>@roofimon</strong> โดย Session นี้คนเยอะมากๆเข้ามาแทบไม่ขาดสายจนที่นั่งไม่พอเลยทีเดียวสำหรับ Robot Framework อธิบายง่ายๆคือเป็น Automate test framework ตัวนึงที่ช่วยให้งานของ Tester นั้นง่ายขึ้นแต่ก่อนจะเริ่มนั้นพี่รูฟถามว่า &ldquo;<em>ใครรู้สึกตัวว่าอยู่ผิดห้องมั้ยครับ ?</em>&rdquo; ประมาณ 4-5 รอบเลย</p>
<p>พี่รูฟเริ่มต้นด้วยการอธิบายคอนเซปของ Requirement ลูกค้าว่าก็เหมือนการสอบสมัยเรียนมหาลัยโดยอาจารย์จะมีโจทย์ที่อลังการงานสร้างที่สุดเท่าที่คิดออกมาให้เราพร้อมกับ&hellip; กระดาษหนึ่งแผ่นหน้าที่ของเราคือเขียนคำตอบไปให้มันตรงกับความต้องการของอาจารย์ให้มากที่สุดโดยพี่รูฟยกตัวอย่างเช่นถ้าสมมติอาจารย์เฉลย 80 แล้วเราตอบ 160/2 แทนที่เราจะได้ซักครึ่งคะแนนแต่กลับกลายเป็นว่าเราไม่ได้เลย &ldquo;<strong>สัส มึงทำผิด !!</strong>&rdquo; พี่รูฟยังยกตัวอย่างข้อสอบ Tense ภาษาอังกฤษที่จะมีการ Guideline มาเป็น Passage แล้วให้เราหยอดคำลงไปในช่องว่าง (ซึ่งยังพอมั่วได้แม่นกว่า) ซึ่งถ้าเปรียบเทียบกับ S/W แล้วการที่เรามี Guideline ในการตรวจรับที่ชัดเจนจะทำให้เรามีความสุขกับงานที่ทำมากกว่า</p>
<p>หลังจากนั้นพี่รูฟพูดถึงบริษัท <strong>Odd-e</strong> นิดหน่อยอันนี้ผมขอข้ามละกันเพราะไม่เกี่ยวกับเนื้อหาประเด็นต่อไปที่พี่รูฟพูดถึงคือถึงแม้เราจะใช้ Agile มาช่วยในการทำงานแล้วก็ตามเราก็ยังคงเจอปัญหา Requirement แบบนี้อยู่เสมอเนื่องจากเรามักจะคิดว่า &ldquo;<em>สิ่งที่เรารู้เป็นสิ่งที่ลูกค้าอยากได้&rdquo; ทำให้เกิดปัญหาเมื่อถึงตอนจบแล้วลูกค้าจำไม่ได้แล้วตอบเรากลับมาว่า &ldquo;ผมไม่ได้พูดอย่างนั้นนะ</em>&rdquo; ซึ่งพี่รูฟก็ได้ชี้ให้เห็นว่าแท้จริงแล้วนั้นของที่ถูกต้องมันอยู่ในมือของ Tester ไม่ใช่ Programmer แต่เนื่องจาก Programmer เป็น &ldquo;<strong>สิ่งมีชีวิตที่คิดว่าตัวเองฉลาดที่สุดในโลก</strong>&rdquo; มีความผยองและเป็นอย่างนี้มาหลายชั่วอายุคนปัญหานี้จึงยังเกิดขึ้นอยู่เสมอวิธีแก้คือ <strong>ให้ Programmer หา Tester มาเป็นแฟน</strong>&hellip; ไม่ใช่ !!! แต่ให้เราเขียน Test ตั้งแต่ขั้น Design กันเลยทีเดียว</p>
<p><img src="/tpse-conference-2013-2-robot-framework/screen-shot-2012-02-06-at-2-58-23-pm_hu_83f88d776d9ff02d.webp"
       srcset="/tpse-conference-2013-2-robot-framework/screen-shot-2012-02-06-at-2-58-23-pm_hu_79a8cd65b123e29f.webp 480w, /tpse-conference-2013-2-robot-framework/screen-shot-2012-02-06-at-2-58-23-pm_hu_83f88d776d9ff02d.webp 960w"
       sizes="(max-width: 960px) 100vw, 960px"
       width="960" height="652"
       alt=""
       loading="lazy" decoding="async"></p>
<p>ต่อมาพี่รูฟก็ได้พาเราไปรู้จักกับ <strong>Acceptance Test Driven Development</strong> โดยเริ่มต้นบอกไว้ก่อนเลยว่าเราไม่สามารถจะทำ <strong>ATDD</strong> ได้ในตอนแรกทั้งหมดทีเดียวซึ่งการทำ Agile ก็จะเข้ามาช่วยในจุดนี้โดยเราจะเลือก Requirement ที่สำคัญที่สุดมาทำก่อนและต้องสามารถทำเสร็จได้ภายใน 2 อาทิตย์ด้วย (ตรงนี้ฮามากเพราะดูเหมือนพี่รูฟจะโดนปรับถ้าเผลอพูดศัพท์เกี่ยวกับ Agile ขึ้นมาแต่ก็มีเหตุผลที่น่าสนใจตามมาว่าเพราะในช่วง 2 สัปดาห์นั้นคนเรายังสามารถจำสิ่งที่ตัวเองพูดได้อยู่) โดยเอามาเข้า <strong>Discuss in workshop</strong> ซึ่งสามารถทำได้ตรงช่วง Sprint Planning Part 1 โดยใน Workshop นี่คนทั้งหมดที่มีส่วนร่วมจะต้องมาคุยกันแล้วถามไปเรื่อยๆว่าสิ่งที่ลูกค้าอยากได้คืออะไรโดยถ้าเริ่มคุยกันไม่รู้เรื่องพี่รูฟแนะนำว่าให้จบด้วยคำว่า &ldquo;<em>ยกตัวอย่างเช่น</em>&rdquo; เพื่อให้คนที่อธิบายได้จำลองตัวเองไปเป็นคนผู้ใช้งาน (<strong>Walkthrough Scenario</strong>) ซึ่งผลลัพธ์ที่ได้ออกมาก็คือ <strong>Test Scenario</strong> หรือตัวอย่าง Scenario ทั้งหมดที่ผู้ใช้ต้องการออกมาเป็นชุดๆเลยนอกจากนั้นแล้วพี่รูฟยังบอกถึงความแตกต่างระหว่างตัวอย่างดีและไม่ดีโดยการเล่าขึ้นมาแบบลอยๆนั้นเทียบไม่ได้กับตัวอย่างแบบ Concrete (ผมก็ไม่รู้จะใช้คำไหนอธิบาย) โดยผู้เล่าจะเล่าออกมาเป็นเหตุการณ์ประมาณว่าเมื่อถึงหน้านี้จะกรอกชื่อ&hellip; กรอกอะไรลงไป&hellip; ได้หน้าจอ&hellip; ถ้าสำเร็จ&hellip; ถ้าไม่สำเร็จ&hellip; เป็นต้น</p>
<p>หลังจากนั้นแล้วทีมก็จะเอา Test Scenario ที่ได้นี้มาเขียนลงใน <strong>Acceptance Criteria</strong> ซึ่งเป็นเหมือนเส้นที่คอยวัดว่าของที่เราสร้างตรงกับสิ่งที่ลูกค้าอยากได้หรือยัง (และไว้ป้องกัน Bug ด้วย) ซึ่งการมี Acceptance Criteria นั้นจะช่วยป้องกันไม่ให้ Programmer เขียนโปรแกรมออกทะเลที่มีพายุตั้งเค้าอยู่โดยยกตัวอย่างเช่นการ Login เพื่อเข้าใช้งานเราก็จินตนาการตามไปว่ามี 2 Field คือ Email กับ Password ซึ่งเราก็ได้ฟังตัวอย่างการไล่รายละเอียดของแต่ละ Scenario ไปจนได้ออกมาเป็น Acceptance Criteria ดังตารางข้างล่าง</p>
<p><img src="/tpse-conference-2013-2-robot-framework/Screen-Shot-2565-02-18-at-15.57.59_hu_69c036d06920afc3.webp"
       srcset="/tpse-conference-2013-2-robot-framework/Screen-Shot-2565-02-18-at-15.57.59_hu_69c036d06920afc3.webp 480w"
       sizes="(max-width: 960px) 100vw, 960px"
       width="480" height="263"
       alt=""
       loading="lazy" decoding="async"></p>
<p>ซึ่งถ้าเราทำได้กับ Scenario ทั้งหมดความชัดเจนในเป้าหมายของ Programmer ก็จะมากขึ้นด้วยแต่พี่รูฟก็ย้ำกับเราอีกทีว่าเรา &ldquo;ทำแบบนี้ทั้งหมด&rdquo; ไม่ได้โดยต้อง</p>
<ul>
<li>เลือกเอางานที่สำคัญที่สุด โดยให้ลูกค้าเลือก</li>
<li>งานนั้นต้องเล็กพอจะเสร็จใน 2 สัปดาห์ (1 Sprint)</li>
<li>เอามาคุยกันจนได้ Acceptance Criteria แล้วค่อยเริ่มงาน</li>
</ul>
<p>อย่างไรก็ตามการทำแบบนี้พี่รูฟก็ยังบอกว่ามันยังคงเหนื่อยอยู่แต่ &ldquo;<strong>เหนื่อยน้อยกว่า</strong>&rdquo; ไม่ทำและนอกจากนั้นแล้วจะทำให้ทีมมีโฟกัสที่ชัดเจน, คนทำเสร็จดีใจและลูกค้าได้ของตามต้องการจนมีอารมณ์เลือกของตัวถัดไปที่อยากได้ถูกแต่เนื่องจากงานนี้มีคำว่า Practical อยู่พี่รูฟจึงยังยกตัวอย่างอีกว่าถ้าระบบเล็กๆนั้นไม่มีปัญหาที่จะใช้บริการของ Tester ในแบบ <strong>Monkey see, monkey do</strong> ซึ่งเป็นงานที่น่าเบื่อและแพงมากการที่เราเอากระบวนการ Automate มาช่วยในจุดนี้ไม่ได้เป็นการลดภาระของ Tester แต่จะทำให้เกิด Value ในตัว Tester มากกว่าเดิมเพราะขยับมาก่อน Implement และเป็นการ <strong>Preventive Mode</strong> หรือกันไว้ดีกว่าแก้นั่นเองทำให้ Tester สามารถ<u>เอาเวลาไปทำประโยชน์อย่างอื่น</u>ได้มากขึ้นด้วย</p>
<p>สำหรับ Tools นั้นแน่นอนว่าจั่วหัวไว้แล้วว่า <strong>Robot Framework</strong> แต่พี่รูฟเกริ่นด้วย <strong>Selenium</strong> ซึ่งเป็น Tool ที่ใครทำ UX Test ต้องเคยได้ยินแน่นอนซึ่งผมก็เพิ่งรู้ตอนนี้เองว่า Selenium มันมีข้อเสียตรงที่จะเขียน Script เพื่อสั่งมันจริงๆได้ยากมากถึงแม้จะรองรับหลายภาษาก็ตามโดยวิธีแก้ปัญหานี้คือหลายๆคนพยายามจะเขียน Script มาครอบ Selenium อีกชั้นนึงเพื่อให้ใกล้กับภาษาคนที่ฝั่ง Business อ่านออกมากขึ้น</p>
<p>มาต่อที่ Robot Framework ตัวนี้ก็เป็นตัวที่สามารถครอบ Selenium ดังที่กล่าวไปแล้วโดยมันจะทำหน้าที่ในการแปลง Acceptance Criteria หรือ Test Scenario ให้ไปเป็น <strong>Keyword</strong> ซึ่งเราสามารถเขียนมันออกมาเป็น HTML Table ธรรมดาๆได้โดย 1 Scenario จะแทนด้วย 1 Table นั้นเองซึ่งข้อดีที่มันเป็น HTML ธรรมดานั้นที่ก็คือเราสามารถใช้ VCS เช่น Git มาควบคุมมันได้ด้วย</p>
<p><img src="/tpse-conference-2013-2-robot-framework/Screen-Shot-2565-02-18-at-15.58.53_hu_fc128bf3c013ef62.webp"
       srcset="/tpse-conference-2013-2-robot-framework/Screen-Shot-2565-02-18-at-15.58.53_hu_fc128bf3c013ef62.webp 480w"
       sizes="(max-width: 960px) 100vw, 960px"
       width="480" height="167"
       alt=""
       loading="lazy" decoding="async"></p>
<p>จากตารางจะเห็นได้ว่า Step ที่อยู่ใน Test Case นั้นมันเป็นภาษาที่เราให้ใครอ่านก็รู้เรื่อง ซึ่งเป็นภาษา Level บนสุดของการเขียน Test แล้ว (พี่รูฟยังบอกอีกว่ามี Tool บางตัวเขียนเป็นภาษาได้เลย เอาเข้าไป !!) ในส่วนของ Robot Architecture นั้นพี่รูฟอธิบายว่าตัว Test Scenario ที่เป็นตารางของเรานี้จะอยู่ชั้นบนสุดซึ่งก็คือ Test Data ซึ่งในชั้น Robot Framework จะเป็นตัวไปเรียก Library ให้ไปสั่ง Tool เช่น Selenium ในการทำงาน โดยข้อดีของ Robot ยังไม่จบแค่นี้ สำหรับอะไรที่เราทำซ้ำๆ เราสามารถดึงมันออกมาแยกเก็บไว้ได้แล้วเรียกใช้ทีหลังสิ่งนั้นเรียกว่า Resource</p>
<p><img src="/tpse-conference-2013-2-robot-framework/1_6M7_pT_2HJR-o-AXgkHU0g_hu_87ad2729b3b8c310.webp"
       srcset="/tpse-conference-2013-2-robot-framework/1_6M7_pT_2HJR-o-AXgkHU0g_hu_564e160d81c99573.webp 480w, /tpse-conference-2013-2-robot-framework/1_6M7_pT_2HJR-o-AXgkHU0g_hu_87ad2729b3b8c310.webp 960w"
       sizes="(max-width: 960px) 100vw, 960px"
       width="960" height="720"
       alt=""
       loading="lazy" decoding="async"></p>
<p>ช่วงท้ายๆ ก่อนถามตอบพี่รูฟพูดถึง Specification Pyramid (จากหนังสือ Agile Testing) ซึ่งหาภาพประกอบได้ยากมากประมาณว่าในส่วนของ Rule กับ Workflow นั้น Tester จะสามารถทำตรงนี้ได้เก่งมาก (ส่วนบนของพีระมิด) และในส่วนของ Technical Activity ซึ่งก็คือฐานของพีระมิดนั้นก็เป็นส่วนที่ Programmer ต้องทำให้สอดรับกันพอดี (Unit Test) อีกเรื่องคือ Principle of Symmetric Change ซึ่งเขียนไว้ว่า One small change in the business domain results in one small change in software. ซึ่งพี่รูฟก็บอกว่าในสมัยอดีตนั้นจริงอยู่แต่ปัจจุบันมันกลับด้านกันแล้ว!!</p>
<p>ปล.ผมว่ามันเริ่มยาวเกินไปละสำหรับ 1 Session ไม่ได้สรุปแต่เก็บเต็มเม็ดเต็มหน่วยเลย&hellip;</p>
<hr>
<p>Original post at: <a href="https://yothinix.blogspot.com/2013/11/tpse-conference-2013-2-robot-framework.html">https://yothinix.blogspot.com/2013/11/tpse-conference-2013-2-robot-framework.html</a></p>
]]></content:encoded></item></channel></rss>