Author: wilaiwan.h

  • เขียน Unit test ทดสอบการทำงานกับฐานข้อมูลที่ใช้ Entity Framework Core

    ในบทความนี้ จะนำเสนอการเขียน unit test เพื่อทดสอบการทำงานของ method ที่ใช้งาน Entity Framework Core ซึ่งตัว Entity Framework Core มาพร้อมความสามารถที่สามารถใช้งาน in-memory store ซึ่งสามารถใช้ในการทดสอบได้ หรือจะใช้ mock framework ในการทดสอบก็ได้ โดยในบทความนี้จะแสดงให้เห็นทั้งสองแนวทาง

    In Memory Database

    In Memory Database เป็นการสร้าง database จำลองขึ้นมาใช้บน memory แทนที่การใช้ database ตัวจริง ทำให้เราสามาถเขียน unit test โดยที่ไม่ไปกระทบกับ database จริงๆ โดยการเพิ่ม Microsoft.Entityframework.InMemory NuGet package เข้าไปใน test projet

    ในส่วนของ DbContext class จะต้องมี constructor ที่รับ DbContectOption เป็น parameter

    var options = new DbContextOptionsBuilder<MyDbContext>()
                    .UseInMemoryDatabase(Guid.NewGuid().ToString("N")).Options;
    
    var dbContext = new MyDbContext(options);

    จากตัวอย่างด้านบน เราใช้ UseInMemoryDatabase extension method -ของ DbContextOptionsBuilder ซึ่งรับ parameter 1 ตัวเพื่อกำหนดชื่อของ database โดยเราจะใช้ Guid จากนั้นก็สามารถเขียน unit test ได้ดังนี้

    [TestClass]
    public class MyRepositoryTest
    {
            private MyDbContext CreateDbContext()
            {
                var options = new DbContextOptionsBuilder<MyDbContext>()
                    .UseInMemoryDatabase(Guid.NewGuid().ToString("N")).Options;
    
                var dbContext = new MyDbContext(options);
                return dbContext;
            }
    
           
            [TestMethod]
            public void It_should_add_a_product_successfully()
            {
                //Arrange
                var dbContext = CreateDbContext();
                var rep = new MyRepository(dbContext);
                var product = new Product 
                { 
                   ProductName="Toyota Altis",
                   UnitPrice=900000 
                };
    
                //Act
                var result = rep.Add(product);
               
                //Assert
                Assert.IsTrue(result.ProductID > 0);
    
                //Clean up
                dbContext.Dispose();
            }
    
            [TestMethod]
            public void It_should_update_a_product_successfully()
            {
                //Arrange
                var expected = 950000;
                var dbContext = CreateDbContext();
                var rep = new MyRepository(dbContext);
                var product = new Product 
                { 
                   ProductName="Toyota Altis",
                   UnitPrice=900000 
                };
    
                //Act
                var result = rep.Add(product);
                result.UnitPrice = expected;
                rep.Update(result);
                result = dbContext.Products.First(p=>p.ProductID == result.ProductID);
    
                //Assert
                Assert.AreEqual(expected,result.UnitPrice);
    
                //Clean up
                dbContext.Dispose();
            }
    
            [TestMethod]
            public void It_should_remove_a_product_successfully()
            {
                //Arrange
                var dbContext = CreateDbContext();
                var rep = new MyRepository(dbContext);
                var product = new Product 
                { 
                   ProductName="Toyota Altis",
                   UnitPrice=900000 
                };
    
                //Act
                var result = rep.Add(product);
                rep.Delete(result.ProductID);
                var isExists = dbContext.Products.Any(p => p.ProductID == result.ProductID);
    
                //Assert
                Assert.IsFalse(isExists);
    
                //Clean up
                dbContext.Dispose();
            }
    
        }

    จากตัวอย่างด้านบน CreateDbContext method ทำหน้าที่ในการสร้าง DbContext object ที่ทำงานกับ in-memory database แทนที่จะใช้งาน database ตัวจริง หลังจากนั้นก็สามารถทำตามที่ต้องการได้ เหมือนกับการใช้งาน DbContext ปกติ

    Using Mock Frameworks

    อีกทางเลือกหนึ่ง เราสามารถใช้ mock frameworks ในการเขียน unit tests ซึ่งเราจะใช้ Moq mock frameworks ซึ่งเป็น mock frameworks ที่ได้รับความนิยมสำหรับ .Net platform โดยก่อนที่จะเริ่มเขียน unit test เราจะต้องกำหนดให้ DbSet pproperty ของ DbContext เป็น virtual ซึงจะทำให้ Moq framework สามารถสร้าง fake object และ override property นี้ได้ และดำเนินการดังนี้

    1. สร้าง generic collection สำหรับ DbSet property แต่ละตัว
    2. Config fake DbSet ใน mock framework
    3. Setup และ forward DbSet property เหล่านั้นไปที่ generic collection
      • Provider
      • Expression
      • ElementType
      • GetEnumerator()
    [TestClass]
    public class MyRepositoryTest
    {
            private DbSet<T> CreateDbSet<T>(IQueryable<T> collection) where T:class
            {
                var stubDbSet = new Mock<DbSet<T>>();
                stubDbSet.As<IQueryable<T>>().Setup(m => m.Provider).Returns(collection.Provider);
                stubDbSet.As<IQueryable<T>>().Setup(m => m.Expression).Returns(collection.Expression);
                stubDbSet.As<IQueryable<T>>().Setup(m => m.ElementType).Returns(collection.ElementType);
                stubDbSet.As<IQueryable<T>>().Setup(m => m.GetEnumerator()).Returns(collection.GetEnumerator());
                return stubDbSet.Object;
            }
    
            [TestMethod]
            public void list_all_available_products_from_the_data_store()
            {
                //Arrange
                var expected = new List<Product>
                {
                    new Product {ProductName ="Toyota yaris"},
                    new Product {ProductName ="Toyota altis"},
                    new Product {ProductName ="Toyota camry"}
                };
    
                var productDbSet = CreateDbSet(expected.AsQueryable());
    
                var stubContext = new Mock<MyDbContext>();
                stubContext.Setup(o=>o.Products).Returns(productDbSet);
                var rep = new MyRepository(stubContext.Object);
    
                //Act
                var actual = (List<Product>)rep.GetAll();
    
                //Assert
                CollectionAssert.AreEquivalent(expected, actual);
    
            }
    
    
            [TestMethod]
            public void return_a_product_for_given_product_id()
            {
                //Arrange
                var expected = "Toyota altis";
                var expected = new List<Product>
                {
                    new Product {ProductName ="Toyota yaris"},
                    new Product {ProductName ="Toyota altis"},
                    new Product {ProductName ="Toyota camry"}
                };
    
                var productDbSet = CreateDbSet(collection.AsQueryable());
    
                var stubContext = new Mock<MyDbContext>();
                stubContext.Setup(o => o.Products).Returns(productDbSet);
                var rep = new MyRepository(stubContext.Object);
    
                //Act
                var actual = rep.GetById(2);
    
                //Assert
                Assert.AreEqual(expected, actual.ProductName);
    
            }        
        }

    อ้างอิง : https://www.bitiniyan.com/2019/02/02/how-to-write-unit-tests-with-entity-framework-core/

  • Puppeteer ควบคุมและบันทึกการใช้งาน chrome สำหรับ UI testing

    Puppeteer เป็น Node library ที่มีชุดของ API สำหรับการควบคุม Google Chrome หรือ Chromium ผ่าน DevTool protocol ทำงานในรูปแบบ headless โดย default แต่สามารถกำหนดให้ทำงานแบบ full (non-headless) Chrome ได้  ที่สำคัญไม่ต้องทำงานผ่าน Web Driver อีกต่อไป

    puppeteer สามารถทำงานได้ทุกอย่างที่สามารถทำได้โดย manual บน browser เช่น

    • สร้าง screenshots และ PDFs ของ page
    • Automate form submission
    • UI testing
    • keyboard input

    การติดตั้ง Puppeteer

    การติดตั้ง Puppeteer เพื่อใช้งานใน project สามารถทำได้ผ่านทาง NPM โดยใช้คำสั่ง

    npm i puppeteer

    เมื่อทำการติดตั้ง Puppeteer จะทำการ downloads Chromium version ล่าสุดซึ่งสามารถทำงานร่วมกับ api ได้อย่างสมบูรณ์ (ประมาณ 170 Mb สำหรับ Mac, 282 Mb สำหรับ Linux และ 280 Mb สำหรับ Windows) ถ้าไม่ต้องการ download Chromium ในระหว่างการติดตั้ง สามารถยกเลิกได้โดยการกำหนดค่า “PUPPETEER_SKIP_CHROMIUM_DOWNLOAD”  environment variables

    การใช้งาน Puppeteer

    Puppeteer API ให้ความสามารถในการสร้าง instance ของ  browser, เปิด webpage และบันทึก screenshot โดยสร้าง file ชื่อ example.js และเขียน code ดังนี้

    เรียกใช้งาน script  บน command line โดยใช้คำสั่ง

    node example.js

    Puppeteer กำหนด default ขนาดของ page ที่ 800 x 600px  และสามารถเปลี่ยน page size โดยใช้ page.setViewport() ซึ่งสามารถศึกษาเพิ่มเติมการใช้งาน Puppeteer API ได้ที่ https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md

    ผลของการ run script  จะทำการบันทึก screenshot ของ https://example.com ไปที่ file “example.png”

    จะเห็นว่าในระหว่างการ run script ไม่ได้ปรากฎ browser ขึ้นมาให้เห็น เนื่องจากโดยค่า default จะทำงานเป็น headless mode แต่สามารถเปลี่ยนการทำงานได้โดยกำหนด headless = false หากต้องการดูการทำงาน

    const browser = await puppeteer.launch({
    	             headless: true
    	         });  // default is true

     

    อ้างอิง

    https://github.com/GoogleChrome/puppeteer/

  • Visual test automation : Appraise test page

    จากบทความ Visual test automation ที่ได้กล่าวถึง Appraise ที่ใช้สำหรับทำการทดสอบการแสดงผลแบบอัตโนมัติในเบื้องต้น ตั้งแต่การติดตั้ง ตัวอย่าง test page, test fixture การเรียกใช้งานการทดสอบ และผลการทดสอบ บทความนี้จะมาลงรายละเอียดในการสร้าง test page

    Creating test pages

    Appraise สามารถกำหนดรูปแบบข้อกำหนดการทดสอบสำหรับการทดสอบส่วนแสดงผลได้ไม่ยุ่งยาก โดย Appraise จะทำการอ่านข้อมูลนำเข้า และ ผลลัพธ์ที่คาดหวัง จาก file ที่เขียนในรูปแบบ Markdown แล้วส่งต่อให้กับระบบทำการทดสอบ จากนั้นจะบันทึกภาพการแสดงผลที่เกิดขึ้น เปรียบเทียบภาพการแสดงผลที่ได้จริงกับผลลัพธ์ที่คาดหวัง โดยมีสิ่งที่จำเป็นที่จะต้องกำหนดสำหรับการทดสอบ 3 ส่วน คือ

    1. input
    2. expect output
    3. fixture

    Input (parameters ของ example)

    input parameters อยู่ในรูปแบบ text JSON หรือ YAML  ในการกำนด input parameters สำหรับแต่ละการทดสอบหรือ example จะต้องกำหนด block ของ example และกำหนดชื่อให้กับ example ในส่วนเริ่มต้น block ในรูปแบบ example=”ชื่อ”

    จากรูปข้างต้น จะเห็นว่ามี example ชื่อ “first” ถูกกำหนดรูปแบบเป็น YAML ซึ่ง Appraise จะรู้ว่าจะนำข้อมูลไปได้อย่างไร ในแต่ละ test page สามารถมี eaxmple ได้มากกว่าหนึ่ง example โดยที่แต่ละ example จะต้องมีชื่อที่ไม่ซ้ำกัน ซึ่งจำเป็นในการใช้สำหรับเปรียบเทียบกับผลลัพธ์ที่คาดหวังด้วย

    Expect output

    ecpect output – ผลลัพธ์ที่คาดหวัง ซึ่งอยู่ในรูปแบบ file รูปภาพ .png  การกำหนดผลลัพธ์ที่คาดหวังใน test page ทำได้โดยระบุ

    ![ชื่อ example](ชื่อ file รูปภาพ .png ที่เป็นผลลัพธ์ที่คาดหวัง)

    ซึ่งจะอยู่ส่วนใดๆใน test page ก็ได้ ไม่ว่าจะอยู่ก่อน example input ก็ได้ โดยที่ความเชื่อมโยงระหว่าง input กับ expect output ก็คือชื่อของ example ที่กำหนดใน []

    ในการสร้าง test page อาจจะไม่ต้องกำหนด expect output ก่อนก็ได้เช่นกัน เมื่อทำการ run test ครั้งแรก ผลที่ออกมาจะ failed เนื่องจากไม่ได้ระบุ expect output แต่สามารถที่จะบันทึกผลที่ได้จากการ run test ใช้เป็น expect output สำหรับการ run test ครั้งต่อไปได้

    Fixture

    fixture คือส่วนของ code ที่ Apprise ใช้ในการเชื่อมต่อกับระบบที่จะทดสอบ รวมทั้งกำหนดการใช้งานและการประมวลผล input ในการทดสอบ โดยทั่วไป examples ที่เชื่อมโยงกันจะใช้ fixture เดียวกัน

    การกำหนด fixture ทำโดยการระบุ fixture =”ชื่อ fixture” ในส่วนของ header ของ example block

    จากรูปข้างต้น จะเห็นว่ามี example ชื่อ “first” มีการกำหนด fixture คือ “hello.js”

    จากตัวอย่าง fixture ด้านบน เป็น fixture ที่มีการสร้าง output page ออกมาโดยตรงโดยไม่ได้เชื่อมต่อกับระบบใดๆ ซึ่งในการใช้งานจริง จะต้องเชื่อมต่อกับระบบหรือส่วนที่จะทำการทดสอบจริงที่ทำการ render ผลลัพธ์ออกมา

    • input parameters จาก test example ส่งผ่านมายัง fixture ผ่านทาง argument ตัวแรก
    • fixture return object ซึ่งมี properties 2 ตัวคือ contentType และ content อย่างไรก็ตาม fixture ยังสามารถ return ข้อมูลแบบอื่นได้เช่นกัน เช่น remote URL หรือ file ใน temporary folder ก็ได้เช่นกัน
    • contentType จาก fixture ด้านบนคือ text/html  ซึ่งสามารถเป็น contenType อื่นใดก็ได้ที่ browser สามารถ render ได้ รวมทั้ง SVG, PDF หรือ image

     

    อ้างอิง 

    https://github.com/AppraiseQA/appraise
    https://github.com/AppraiseQA/appraise/blob/master/examples/creating-test-pages.md

  • Visual test automation

    ในการพัฒนา software นั้น เรื่อง Look and Feel เป็นสิ่งที่สำคัญมาก ๆ software ที่ทำงานได้ดียังไม่พอ ต้องดูดีสวยงามและใช้งานง่ายอีกด้วย คำถามที่น่าสนใจคือ จะทำการทดสอบในส่วนของการแสดงผลอย่างไรบ้าง

    โดยปกติในการทำการทดสอบการแสดงผล จะใช้คนเป็นผู้ทดสอบ มานั่งดู มาใช้งาน แล้วพิจารณาและตัดสินว่า ถูกหรือไม่ ทำให้ต้องใช้เวลาและกำลังคนในการทดสอบค่อยข้างมาก เพราะว่าในส่วนของการแสดงผลนั้นมันยากมาก ๆ สำหรับการทดสอบ มีหลายสิ่งอย่างให้พิจารณา ยกตัวอย่างเช่น ในแต่ละ element แต่ละส่วนงานทำงานและแสดงผลได้อย่างถูกต้องแต่เมื่อนำมารวมกัน กลับทำงานไม่ถูกต้อง หรือ แสดงผลผิดพลาด การลดเวลาและต้นทุนในการทดสอบลงโดยใช้การทดสอบแบบอัตโนมัติ เป็นสิ่งจำเป็น และต้องมีเครื่องมือช่วย

    ในบทความนี้ขอแนะนำ Appraise ซึ่ง Appraise นั้นได้นำแนวทางของ Specification by Example มาใช้ นั่นหมายความว่า ในแต่ละ test case ต้องมี concrete example ที่ชัดเจน นำ test case เหล่านี้ไปทดสอบแบบอัตโนมัติ ทำการทดสอบด้วย Google Chrome Headless ซึ่งจะทำการ snapshot ส่วนที่ต้องการเป็นรูปภาพและเปรียบเทียบกับผลที่คาดหวังหรือไม่ ถ้าผลออกมาไม่ตรงกับที่คาดหวัง Appraise จะแสดงผลที่แตกต่างออกมาให้เห็น จากนั้นจะให้คนมา approve ว่าผลที่แตกต่างถูกหรือไม่ต่อไป ถ้าทำการยอมรับความแตกต่างก็จะบันทึกผลใหม่ให้ทันที ซึ่งง่ายต่อการดูแลรักษา test case อย่างมาก

    เริ่มต้นการใช้งาน Appraise

    ทำการติดตั้ง Appraise ด้วย NPM

    npm install appraise -g

    จะทำการติดตั้ง Appraise เป็น global command line uitlity อย่างไรก็ตาม สามารถติตั้งเป็นแบบ NPM package dependency โดยใช้ NPM script เรียกใช้ commamd line ก็ได้เช่นกัน

    สร้าง test page

    เริ่มต้นสร้าง folder “examples” เป็นที่เก็บ test page  (default folder สำหรับเก็บ test page ของ Appraise คือ “examples”)  Appraise ใช้  Github-flavoured Markdown syntax  ในการประมวลผล test page

    สร้าง file : hello-world.md ใน folder “examples” และเพิ่มข้อความดังนี้


    This paragraph is just an intro, it will be ignored for testing
    ~~~yaml example="first" fixture="hello.js"
    color: blue
    ~~~

    สร้าง test fixture

    test fixture ใช้สำหรับ Appraise สำหรับประมวลผล example ไปเป็น test result โดยสร้าง file : hello.js ใน folder “examples”และเพิ่ม code ดังนี้

    ทำการ run ทดสอบ

    ทดสอบโดยใช้คำสั่ง

    appraise run

    ซึ่งจะได้ผลดังนี้

    Appraise บันทึกผลการทดสอบไว้ใน folder “results” ซึ่งได้ผลการทดสอบ file : result/hello-world.htm ดังนี้

    จะเห็นได้ว่าผลการทดสอบ failed เนื่องจาก test case นี้ยังไม่ได้กำหนด expected result หรือผลที่คาดหวัง อย่างไรก็ตาม ถ้าพิจารณาผลการทำงานว่าใช้ได้ Appraise มีทางเลือกให้สามารถ capture ผลเพื่อใช้ในการทดสอบครั้งต่อไป โดยทำการ approve ผลการทำงานนี้ก่อนเพื่อทำการยอมรับผลนี้ ด้วยคำสั่ง

    appraise approve --page "hello-world"

    Appraise จะทำการบันทึกผลเป็นรูปภาพลงใน  folder “examples” และแก้ไข hello-world.md กำหนด expected result ดังนี้

    จากนั้นมาเริ่มทดสอบอีกครั้งโดยใช้คำสั่ง

    appraise run

    ซึ่งจะได้ผลดังนี้

    เข้าไปดูผลการทดสอบ file : result/hello-world.htm อีกครั้งดังนี้

    หากทำการแก้ไข test fixture โดยการเพิ่มขนาดตัวอักษร จากนั้นมาเริ่มทดสอบอีกครั้งโดยใช้คำสั่ง

    appraise run

    ก็จะได้ผลดังนี้

    ซึ่งแสดงให้เห็นว่า ผลการทดสอบ failed จากขนาดตัวอักษรที่เปลี่ยนไป ไม่เป็นไปตามที่ต้องการ

     

    อ้างอิง
    https://github.com/AppraiseQA/appraise

    แนะนำ Appraise สำหรับ Visual Testing แบบอัตโนมัติ


  • Automation Testing

    ในการพัฒนา application ในปัจจุบันที่มีความเปลี่ยนแปลงอย่างรวดเร็ว ปัญหาที่จะพบตามมาด้วยก็คือ bug ของตัวระบบอันเนื่องมาจากความเร่งรีบในการพัฒนา ซึ่งการที่จะลด bug ที่อาจจะเกิดขึ้นก็จำเป็นที่จะต้องมีการทดสอบ (testing) ในส่วนต่างๆทั้งหมดเพื่อหาความผิดพลาด หรือผลกระทบที่เกิดขึ้นจากการเปลี่ยนแปลง เพิ่มเติม code ใหม่ๆ เข้าสู่ระบบหรือ application

    การทำ testing ส่วนต่างๆซ้ำทั้งหมดเพื่อหาความผิดพลาดที่เกิดขึ้นจากการเปลี่ยนแปลง เพิ่มเติม code ใหม่ๆ ต้องใช้เวลาค่อนข้างมาก อาจจะทำให้ผู้พัฒนาเลือกที่จะทำการ testing เฉพาะส่วน และนั่นอาจนำมาซึ่ง bug ที่เกิดจากส่วนอื่นๆที่ไม่ได้ทดสอบ การทำการทดสอบแบบอัตโนมัติทำให้สามารถลดเวลาใน testing ลงแต่ยังคงไว้ซึ่งการทดสอบ ส่วนต่างๆทั้งหมด

    สิ่งจำเป็นที่ควรจะรู้ในการทำ automation test คือการเขียนชุดทดสอบโดยเฉพาะ unit test ที่ดี

    • แต่ละ test case ต้องทำงานได้รวดเร็ว
    • แต่ละ test case มีความเป็นอิสระแก่กัน นั่นคือแต่ละ test case จะไม่เกี่ยวข้องกัน ทำให้สามารถทดสอบแบบสุ่มและแบบขนานได้ แต่สิ่งที่พบเจอเป็นประจำคือ ผลจาก test case ที่ 1 เป็น input ของ test case ที่ 2 แล้วผลจาก test case ที่ 2 เป็น input ของ test case ที่ 3 และ …. ซึ่งถ้าเป็นแบบนี้หมายความว่าแต่ละ test case ผูกมัดกันไปหมด และต้องทำงานแบบเรียงลำดับกันด้วย
    • อีกสิ่งหนึ่งที่สำคัญคือ แต่ละ test case จะต้องไม่มีการทำงานร่วมกับระบบ Network และ Database ใด ๆ ทั้งสิ้น และใช้ Mock หรือ Fake ทำการควบคุมสิ่งที่ควบคุมไม่ได้ ทำให้สามารถเขียน test case ได้ดีขึ้น เนื่องจากการทำงานกับ Network และ Database อาจเกิดเหตุการณ์ที่ไม่สามารถควบคุมได้ทำให้ ผลของการทดสอบผิดพลาด ซึ่งแต่ละ test case ต้องสามารถทำงานซ้ำ ๆ และควรได้ผลเหมือนเดิมทุกครั้ง ไม่ใช่ทำงานผ่านบ้างไม่ผ่านบ้าง ไม่มีความน่าเชื่อถือ สุดท้ายก็จะเลิกใช้ เลิกทำและกลับไปทำแบบเดิมก่อนหน้านี้
    • โครงสร้างของชุดการทดสอบต้องดีด้วย เริ่มตั้งแต่ชื่อของ test case ต้องสื่อถึงสิ่งที่ต้องการจะทดสอบ ทั้ง input และ expected result ชื่อยาวไม่มีปัญหากับเรื่องของ performance ว่าจะช้าเนื่องจากมันอยู่ในส่วนของการทดสอบไม่ใช่ระบบงานจริง โครงสร้างหลัก ๆ ของแต่ละ test case ควรประกอบไปด้วย การกำหนดหรือจัดการค่าเริ่มต้นที่ต้องการ, การกระทำที่ต้องการทดสอบ และ ตรวจสอบผลการทำงานว่าเป็นไปตามที่คาดหวังหรือไม่ อย่าลืมว่าชุดการทดสอบ มันคือเอกสารชนิดหนึ่งที่ใช้อธิบายการทำงานภายในของระบบ ทั้ง class และ function ต่าง ๆ เพื่อให้คนอื่น ๆ เข้ามาอ่านอีกด้วย
    • การคิดก่อนทำ นั่นคือก่อนที่จะเริ่มต้นทำ จะต้องเข้าใจปัญหาหรือมีเป้าหมายก่อน จากนั้นจึงแบ่งปัญหาใหญ่ออกเป็นปัญหาย่อยเพื่อให้แก้ไขปัญหาได้ง่ายขึ้น

    การทดสอบจะง่ายหรือไม่นั้นสิ่งที่สำคัญมาก ๆ คือโครงสร้างของ code ที่พัฒนานั้นได้คิดหรือออกแบบการทดสอบกันอย่างไรหรือไม่ ซึ่งถ้าไม่ได้คิดถึงการทดสอบตั้งแต่แรก การทดสอบจะทำได้ยากมาก

     

    แหล่งอ้างอิง : http://www.somkiat.cc/write-automation-test/

  • การพัฒนา Unit Testing โดย MSTest test library (.NET Core)

    บทความนี้นำเสนอขั้นตอนการพัฒนา Unit Test อย่างง่ายโดยแสดงให่้เห็นการพัฒนาทีละขั้นตอน สำหรับระบบที่พัฒนาบน .NET Core โดยใช้ MSTest เพื่อทำความเข้าใจการพัฒนาแบบ test-driven development (TDD) ซึ่งในขั้นแรก เริ่มต้นด้วยการสร้าง source project ชื่อ “CalcTool” ใช้สำหรับการทดสอบ อยู่ภายใต้ folder “TestSample” ซึ่งขั้นตอนการสร้าง project สามารถดูได้จาก บทความ การพัฒนา Unit Testing โดย xUnit test library (.NET Core) ซึ่งจะได้ class “Calc” ซึ่งประกอบไปด้วย method “AbsAddByOne(int value)” ที่ยังไม่ได้เขียน code การทำงาน

    สร้าง test project ที่ใช้ MSTest library โดยเปิด command prompt เข้าไปที่ folder “TestSample” และสร้าง sub folder ชื่อ “CalcTool.MSTests” จากนั้นเข้าไปที่ folder “CalcTool.MSTests” ทำการสร้าง .NET Core project โดยใช้คำสั่ง

    dotnet new mstest

    โดยคำสั่งนี้จะทำการสร้าง test project ที่ใช้ MSTest test library และกำหนดค่า test runner

    <ItemGroup>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"/>
        <PackageReference Include="MSTest.TestAdapter" Version="1.1.11"/>
        <PackageReference Include="MSTest.TestFramework" Version="1.1.11"/>
    </ItemGroup>
    

    เพิ่ม reference ไปยัง project ที่ต้องการทดสอบซึ่งในที่นี้คือ CalcTool project โดยใช้คำสั่ง

    dotnet add reference ../CalcTool/CalcTool.csproj

    จากนั้นให้ execute คำสั่ง dotnet restore เพื่อ restore NuGet package ที่จำเป็นต้องใช้ในแต่ละ project

    เริ่มพัฒนา unit testing โดยลบ file “UnitTest1.cs” และสร้าง file ใหม่โดยใช้ชื่อว่า “CalcTest.cs” และเขียน code ดังนี้

    using System;
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    
    namespace CalcTool.MSTest
    {
        [TestClass]
        public class CalcTest
        {
            [TestMethod]
            public void AbsAddByOneTest()
            {
                var c = new Calc();
                var result = c.AbsAddByOne(5);
    
                Assert.AreEqual(result, 6);
            }
        }
    }
    

    *[TestClass] attribute ใช้เพื่อบอกว่ามี unit test อยู่ใน class นั้น
    *[TestMethod] attribute ใช้เพื่อกำหนดว่า method นั้นๆเป็นแบบ single test

    ทำการทดสอบโดยการ execute dotnet test ซึ่งจะทำการ build และ start MSTest test runner ซึ่งพบว่าผลการทดสอบ fail เนื่องจากยังไม่ได้ implement code ใน method “AbsAddByOne” ของ class “Calc” ดังนั้นกลับไปที่ method “AbsAddByOne” เพิ่ม code ลงไปเพื่อให้ผลการทดสอบถูกต้อง/ผ่าน (test pass)

            public int AbsAddByOne(int value)
            {
                return Math.Abs(value) + 1;
                //throw new NotImplementedException("Not implement");
            }
    

    กลับไปที่ folder “CalcTool.MSTests” และ execute dotnet test ซึ่งจะทำการ build และ start MSTest test runner ซึ่งพบว่า ผ่านการทดสอบ (test pass)

    MSTest มี attribute ที่ใช้กำหนด suite of tests ซึ่ง execute code เดียวกันแต่มี input ที่มีค่าแตกต่างกันนั่นคือ [DataTestMethod] attribute และใช้ [DataRow] attribute ในการกำหนดค่าของ input

            [DataTestMethod]
            [DataRow(5)]
            [DataRow(-5)]
            public void AbsAddByOneTest2(int value)
            {
                var c = new Calc();
                var result = c.AbsAddByOne(value);
    
                Assert.AreEqual(result, 6);
            }
    

    การพัฒนาในรูปแบบ test-driven development (TDD) จะทำเป็นรอบๆในลักษณะนี้โดย เพิ่ม unit testing , เพิ่ม code ใหม่เข้าไป ทดสอบ unit test จนผ่านและเริ่มรอบใหม่ จนกระทั่งได้ test ชุดสุดท้ายและโปรแกรม/library ที่สมบูรณ์ ก็เป็นอันจบการพัฒนาในรูปแบบ test-driven development (TDD)

  • การพัฒนา Unit Testing โดย xUnit test library (.NET Core)

    บทความนี้นำเสนอขั้นตอนการพัฒนา Unit Test อย่างง่ายโดยแสดงให่้เห็นการพัฒนาทีละขั้นตอน สำหรับระบบที่พัฒนาบน .NET Core โดยใช้ xUnit เพื่อทำความเข้าใจการพัฒนาแบบ test-driven development (TDD) ซึ่งในขั้นแรก เริ่มต้นด้วยการสร้าง source project ที่จะใช้สำหรับการทดสอบ สร้าง folder “TestSample” และ sub folder ชื่อ “CalcTool”  จากนั้นเปิด command prompt เข้าไปที่ folder “CalcTool” เพื่อทำการสร้าง .NET Core project โดยใช้คำสั่ง

    dotnet new classlib

    rename class1.cs เป็นชื่อ calc.cs และเขียน code ดังนี้

    using System;
    
    namespace CalcTool
    {
        public class Calc
        {
            public int AbsAddByOne(int value)
            {
                throw new NotImplementedException("Not implemented");
            }
        }
    }
    

    สร้าง test project โดยกลับไปที่ folder “TestSample” และสร้าง sub folder ชื่อ “CalcTool.Tests” จากนั้นเปิด command prompt เข้าไปที่ folder “CalcTool.Tests” เพื่อทำการสร้าง .NET Core project โดยใช้คำสั่ง

    dotnet new xunit

    โดยคำสั่งนี้จะทำการสร้าง test project ที่ใช้ xunit test library และกำหนดค่า test runner ไว้ใน CalcTool.Tests.csproj

    file : CalcTool.Tests.csproj
    
    <Project Sdk="Microsoft.NET.Sdk">
    
    <PropertyGroup>
        <TargetFramework>netcoreapp1.1</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0"/>
        <PackageReference Include="xunit" Version="2.2.0"/>
        <PackageReference Include="xunit.runner.visualstudio" Version="2.2.0"/>
    </ItemGroup>
    
    </Project>
    
    

    เพิ่ม reference ไปยัง project ที่ต้องการทดสอบซึ่งในที่นี้คือ CalcTool project โดยใช้คำสั่ง

    dotnet add reference ../CalcTool/CalcTool.csproj

    ก่อนที่จะ build CalcTool project หรือ CalcTool.Tests project ต้อง execute คำสั่ง dotnet restore เพื่อ restore NuGet package ที่จำเป็นต้องใช้ในแต่ละ project

    เริ่มพัฒนา unit testing โดยลบ file “UnitTest1.cs” และสร้าง file ใหม่โดยใช้ชื่อว่า “CalcTest.cs” และเขียน code ดังนี้

    using System;
    using Xunit;
    using CalcTool;
    
    namespace CalcTool.Tests
    {
        public class CalcTest
        {
            [Fact]
            public void AbsAddByOneTest()
            {
                var c = new Calc();
                var result = c.AbsAddByOne(5);
    
                Assert.Equal(result, 6);
            }
        }
    }
    

    *[Fact] attribute ใช้กำหนดว่า method นั้นๆเป็นแบบ single test

    ทำการทดสอบโดยการ execute dotnet test ซึ่งจะทำการ build และ start xUnit test runner ซึ่งพบว่าผลการทดสอบ fail เนื่องมาจากยังไม่ได้ implement code ใน method “AbsAddByOne” ของ class “Calc” ดังนั้นกลับไปที่ method “AbsAddByOne” เพิ่ม code ลงไปเพื่อให้ผลการทดสอบถูกต้อง/ผ่าน (test pass)

            public int AbsAddByOne(int value)
            {
                return Math.Abs(value) + 1;
                //throw new NotImplementedException("Not implement");
            }
    

    กลับไปที่ folder “CalcTool.Tests” และ execute dotnet test ซึ่งจะทำการ build และ start xUnit test runner ซึ่งพบว่า ผ่านการทดสอบ (test pass)

    ใน xUnit ยังมี attribute ที่ใช้กำหนด suite of tests ซึ่ง execute code เดียวกันแต่มี input ที่มีค่าแตกต่างกันนั่นคือ [Theory] attribute และใช้ [InlineData] attribute ในการกำหนดค่าของ input

            [Theory]
            [InlineData(5)]
            [InlineData(-5)]
            public void AbsAddByOneTest2(int value)
            {
                var c = new Calc();
                var result = c.AbsAddByOne(value);
    
                Assert.Equal(result, 6);
            }
    

    การพัฒนาในรูปแบบ test-driven development (TDD) จะทำเป็นรอบๆในลักษณะนี้โดย เพิ่ม unit testing , เพิ่ม code ใหม่เข้าไป ทดสอบ unit test จนผ่านและเริ่มรอบใหม่ จนกระทั่งได้ test ชุดสุดท้ายและโปรแกรม/library ที่สมบูรณ์ ก็เป็นอันจบการพัฒนาในรูปแบบ test-driven development (TDD)

  • การสร้างเอกสารด้วย Sphinx

    sphinx คือเครื่องมือที่ช่วยในการสร้างเอกสารที่พัฒนาโดย Georg Brandl เดิมที sphinx ได้รับการพัฒนาเพื่อใช้สร้างเอกสารสำหรับ Python แต่สามารถใช้งานกับภาษาอื่นๆได้เช่นกัน

    sphinx ใช้ reStructuredText (ดูข้อมูลเพิ่มเติมเกี่ยวกับ reStructuredText ได้ที่ บทความ ก่อนหน้า) ในการกำหนดรูปแบบของเอกสาร และชุดเครื่องมือในการ parsing และ translating เอกสารในรูปแบบ  reStructuredText ไปเป็นรูปแบบที่ต้องการเช่น html หรือ pdf เป็นต้น

    ในการติดตั้งใช้งาน sphinx นั้นจำเป็นต้องต้องติดตั้ง Python เนื่องจาก sphinx ได้รับการพัฒนาโดยใช้ Python language ทำให้การใช้งาน sphinx ต้องติดตั้ง Python ด้วย โดย Python ที่ใช้ต้องเป็น Python version 2.7 เป็นอย่างน้อย

    การติดตั้ง Python บน windows  ทำได้โดยเข้าไป download “Python windows installer” ที่ https://www.python.org/ และทำการติดตั้ง หลังจากติดตั้งเรียบร้อย จะต้องทำการกำหนดค่า Python executable directory ใน PATH environment variable เพื่อที่จะสามารถ run Python และ package command เช่น sphinx-build ได้จาก command prompt

    Installing Sphinx with pip

    การติดตั้ง Sphinx ทำได้โดยการใช้ “pip”  ซึ่ง pip เป็นเครื่องมือที่ใช้ในการ download และติดตั้ง 3rd-party libraries สำหรับ Python ซึ่งจะถูกรวมอยู่ใน Python official installation ตั้งแต่ version Python-3.4.0

    การติดตั้ง sphinx โดยใช้คำสั่งดังนี้บน command prompt

    C:\> pip install sphinx
    

    หลังจากติดตั้งเรียบร้อย ให้พิมพ์คำสั่ง sphinx-build -h บน command prompt ถ้าทุกอย่างถูกต้อง จะแสดงข้อมูล Sphinx version number และ list ของ option ต่างๆสำหรับคำสั่งนี้

    Setting up the documentation sources

    การพัฒนาเอกสารด้วย sphinx นั้น เริ่มแรกเราจะต้องกำหนดพื้นที่สำหรับพัฒนาเอกสารและจัดเก็บ config ที่ใช้สำหรับ sphinx ซึ่ง sphinx มีคำสั่ง sphinx-quickstart ซึ่งทำหน้าที่กำหนด source directory และสร้าง default config file “conf.py” ที่จำเป็นให้ โดยใช้คำสั่งดังนี้

    C:\> sphinx-quickstart

    sphinx-quickstart script สร้างโครงสร้าง folder พร้อมทั้ง file เริ่มต้นรวมทั้ง Makefile และ make.bat ซึ่งจะใช้ในการ build (parsing และ translating โดยที่ถ้าพบส่วนที่ไม่ตรงตามข้อกำหนด syntax จะแสดง warning หรือ error พร้อมทั้งรายละเอียดของ line เช่นเดียวกับการ build “code program”)

    หลังได้โครงสร้าง folder สำหรับพัฒนาเอกสารเรียบร้อย ก็สามารถเริ่มต้นเขียนเอกสารโดยใช้ reStructuredText ในการกำหนดรูปแบบการแสดงผลของ text จากนั้นเมื่อต้องการสร้างเอกสารในรูปแบบที่ต้องการเช่น html document ก็จะต้องทำการ build โดยใช้คำสั่ง

    C:\> make html

    ผลลัพธ์ที่ได้จากการใช้คำสั่งนี้คือ HTML document ใน folder ที่กำหนด (ใช้คำสั่ง make โดยไม่ระบุ argument เพื่อแสดงประเภทของเอกสารทั้งหมดที่สามารถสร้างได้)

     

    อ้างอิง : http://www.sphinx-doc.org/en/1.5.1/tutorial.html

  • การกำหนดรูปแบบเอกสารด้วย reStructuredText

    การสร้างเอกสารด้วย reStructuredText syntax ทำให้เอกสารที่พัฒนาง่ายในการอ่าน จัดรูปแบบได้ตามที่เห็น รูปแบบของ markup เป็น plaintext ซึ่งจะใช้ parser ในการแปลงให้อยู่ในรูปแบบเอกสารที่ต้องการเช่น html หรือ pdf เป็นต้น

    การกำหนดรูปแบบ (Text Formatting)

    1. Paragraphs

    Paragraphs คือ block พื้นฐานของ reStructuredText ซึ่งแต่ละ paragraphs จะถูกแยกด้วย blank line 1 บรรทัด หรือมากกว่าหนึ่งก็ได้ ซึ่งย่อหน้ามีผลกับ reStructuredText ดังนั้นในการกำหนดบรรทัดใน paragraph เดียวกันจะต้องมี ย่อหน้าด้สนซ้ายเท่ากันในระดับเดียวกัน.

    2. Inline markup และ special character

    เราสามารถใช้ตัวอักษรพิเศษในการกำหนดรูปแบบข้อความมีอยู่ด้วยกันหลายตัว  ตัวอักษรพิเศษ * ถูกใช้ในการกำหนดรูปแบบตัวอักษรตัวหนา และ ตัวอักษรตัวเอียง ดังตัวอย่างด้านล่าง

    • ตัวหนา **ภาษาไทย**  จะแสดงผลคือ  ภาษาไทย
    • ตัวเอียง *ภาษาไทย*   จะแสดงผลคือ  ภาษาไทย

    ตัวอักษรพิเศษ backquote  ` จะใช้ในการกำหนดตัวอย่าง  code โดยการใช้งานดังนี้

    ``code sample``

    แต่ถ้าต้องการใช้ * และ `  ในเอกสารด้วยซึ่งจะทำให้สับสนกับ inline markup ได้ ดังนั้นจึงใช้  backslash นำหน้าสำหรับ * และ ` ที่จะใช้แสดงผลของเอกสาร

    3. Lists and Quote-like blocks

    การกำหนด list สามารถทำได้โดยการใช้ * นำหน้า

    * bulleted list
    * bulleted list
    * bulleted list

    สำหรับ numbered list ก็ให้ใช้ตัวเลขนำหน้า

    1. numbered list
    2. numbered list

    ถ้าต้องการ list ที่กำหนดเลขโดยอัตโนมัติให้ใช้ # นำหน้า

    #. numbered list
    #. numbered list

    การกำหนด list ซ้อนกันหลายๆชั้นสามารถทำได้ แต่ต้องแยกแต่ละ list ออกด้วย blank line

    *. bulleted list
    *. bulleted list
        *. bulleted list
        *. bulleted list
    *. bulleted list

    4. Source code

    การกำหนด code block ทำได้โดยใช้ special marker :: ที่ตอนจบของ paragraph และจะต้องแยกจากส่วนอื่นด้วย blank line เช่นเดียวกับ paragraph ทั่วไป

    This is a normal text paragraph. The next paragraph is a code sample::
    
                public class reStructuredTest {
                        public reStructuredTest(){
                        }
                }
    

    5. Table

    การสร้างตารางทำได้ในสองรูปแบบคือแบบ grid table และ simple table โดยการสร้างตารางแบบ grid table ทำได้ด้วยการวาดตารางด้วยตัวเอง

    +------------------------+------------+----------+----------+
    | Header row, column 1   | Header 2   | Header 3 | Header 4 |
    | (header rows optional) |            |          |          |
    +========================+============+==========+==========+
    | body row 1, column 1   | column 2   | column 3 | column 4 |
    +------------------------+------------+----------+----------+
    | body row 2             | ...        | ...      |          |
    +------------------------+------------+----------+----------+
    

    สำหรับ Simple tables จะเขียนง่ายกว่ารูปแบบแรก แต่มีข้อมจำกัดคือ จะต้องมีมากกว่า 1 row และ column แรกไม่อนุญาติให้มีหลายบรรทัด

    =====  =====  =======
    A      B      A and B
    =====  =====  =======
    False  False  False
    True   False  False
    False  True   False
    True   True   True
    =====  =====  =======
    

    6. Hyperlinks

    การกำหนด hyperlink ทำได้โดยการกำหนดรูปแบบดังนี้

    `Link text <http://example.com/>`_

    section header กำหนดโดยใช้การทำ underlining โดยที่ความยาวของ underlining จะต้องเท่ากับจำนวนตัวอักษรของ header

    =================
    This is a heading
    =================
    

    การกำหนดระดับของ header จะทำได้โดย

    • # ร่วมกับ overline  สำหรับกำหนด  part header
    • * ร่วมกับ overline สำหรับกำหนด chapter header
    • = สำหรับกำหนด section header
    • - สำหรับกำหนด subsection header
    • ^ สำหรับกำหนด subsubsection header
    • ” สำหรับกำหนด paragraph header

     

    อ้างอิง

    1. http://www.sphinx-doc.org/en/1.5.1/rest.html
    2. http://thomas-cokelaer.info/tutorials/sphinx/rest_syntax.html