Category: Developer

งานพัฒนาระบบ, เขียนโปรแกรม

  • แนวทางการเขียน Unit Tests ที่ดีสำหรับ C # (2)

    ในบทความก่อนหน้า ได้พูดถึงการเลือก Testing framework ที่เหมาะสม การเขียน unit test โดยใช้แนวคิดแบบ “AAA” (Arrange, Acr, Assert) และการกำหนดชื่อ method ที่สื่อความหมายเข้าใจได้ง่าย

    Test Initialize & Cleanup

    เมื่อ unit tests ทำการทดสอบ methods ภายใน class ก็จำเป็นต้องสร้าง instance ของ class นั้นๆขึ้นมาก่อน ซึ่งจะเกิดขึ้นหลายๆครั้งใน unit test เพื่อประหยัดเวลาและทรัพยากรของระบบ เราสามารถใช้ [TestInitialize] attribute ของ MSTest เพื่อบอกว่า method นั้นใช้สำหรับกำหนดค่าเริ่มต้น สร้าง instance ของ class ก่อนที่จะเริ่ม run unit tests (หรือ [SetUp] ใน NUnit หรือถ้าใช้ xUnit.net ให้สร้างใน constructor )

    เมื่อการทดสอบจบลง เราจะต้องทำการ cleanup object ต่างๆที่ใช้ในการทดสอบ โดยการใช้ [TestCleanup] attribute ในกรณีที่ใช้ MSTest ([TearDown ใน NUnit หรือ implement IDisposable interface สำหรับกรณีที่ใช้ xUnit.net)

    ตัวอย่างด้านล่าง จะกำหนดให้ method “Initialize” เป็น method ที่ใช้สำหรับสร้าง instance ของ class ที่จะใช้ในการทดสอบ ซึ่งจะถูกเรียกใช้ก่อนการทดสอบจะเริ่มทำงาน

    ILogger _log;
    ICalc _calc;
    
    [TestInitialize]
    public void Initialize()
    {
    	_log = Mock.Of<ILogger<Calc>>();
    	_calc = new Calc(_log);
    }
    
    [TestMethod]
    public void Divide_ShouldThrowArgumentException_IfDivideByZero()
    {
    	double result = _calc.Divide(10, 5);
    	result.ShouldBe(2);
    }
    
    [TestCleanup]
    public void Cleanup()
    {
    	// Optionally dispose or cleanup objects
            _calc = null;
    	...
    }

    Shouldly Assertion Framework

    Shouldly framework จะช่วยให้ unit test ทำได้ง่ายและเข้าใจได้ง่ายขึ้น มันจะช่วยผู้พัฒนาในการเขียนการยืนยันผลการทำงานของ unit tests ซึ่งเมื่อกลับมาดู unit test อีกครั้งสามารถอ่าน code แล้วเข้าใจวัตถุประสงค์และความคาดหวังของการทดสอบ

    เปรียบเทียบความแตกต่างระหว่าง การใช้ Shouldly กับ ไม่ใช่ Shouldly

    With Shouldly

    result.ShouldBe(2);

    Without Shouldly

    Assert.AreEqual(2, result);

    Shouldly สามารถใช้ในการตรวจสอบ exception ว่าเกิดตามที่คาดหวังไว้หรือไม่ และสามารถตรวจสอบ meesage ของ exception ได้

    [TestMethod]
    public void Divide_ShouldThrowArgumentException_IfDivideByZero()
    {
    	Should.Throw<ArgumentException>(() => _calcs.Dicide(10, 0))
    	      .Message
                  .ShouldBe("Divide by zero.");
    }

    Moq Mocking Framework

    การสร้าง object จำลองสำหรับ object ที่ไม่ใช่ object หลักที่จะทำการทดสอบ แทนที่การเรียกใช้ object จริงๆ เช่น logging utility หรือ database จะทำให้การทดสอบทำได้อย่างมีประสิทธิภาพขึ้น ซึ่งการทำแบบนี้ เราต้องใช้ Mocking framework มาช่วย โดยตัวที่ได้รับความนิยมตัวนึงก็คือ Moq framework

    การใช้ Moq framework จะช่วยให่เราสามารถ mock class ต่างๆโดยใช้เพียง interface. ตัวอย่างเช่น การใช้ mock ของ logging utility ใน unit test แทนที่จะสร้างและเรียกใช้ logging utility ตัวจริง

    ILogger log = Mock.Of<ILogger<Calc>>();

    We can also verify if a mock object was invoked inside a method and the amount of times it was invoked:

    [TestMethod]
    public void Divide_LogInformationMethod()
    {
    	ILogger log = Mock.Of<ILogger<Calc>>();
    	ICalc calc = new Calc(log);
    	double result = calc.Divide(10,5);
    	Mock.Get(log).Verify(x => x.Log(
    		LogLevel.Information, 
        	It.IsAny<EventId>(), 
        	It.IsAny<FormattedLogValues>(), 
        	It.IsAny<Exception>(), 
        	It.IsAny<Func<object, Exception, string>>()), 
        	Times.Once);
    }

    อ้างอิง : https://kiltandcode.com/2019/06/16/best-practices-for-writing-unit-tests-in-csharp-for-bulletproof-code/

  • แนวทางการเขียน Unit Tests ที่ดีสำหรับ C #

    ในความคิดของผู้เขียน ผู้พัฒนาส่วนใหญ่มักไม่ชอบที่จะเขียน Unit testing บางคนอาจจะคิดว่ามันน่าเบื่อ หรือบางคนก็ไม่เห็นคุณค่าในการเขียน code เพื่อตรวจสอบการทำงานของ code อื่นๆ

    Unit testing คือแนวทางในการเขียน code เพื่อตรวจสอบว่าหน่วยการทำงานใน แอปพลิเคชันซอฟต์แวร์ ว่าทำงานตามที่ตั้งใจไว้หรือไม่ เช่น หากตั้งใจที่จะให้ตัวอักษรตัวแรกของคำเป็น พิมพ์ใหญ่ unit test จะต้องสามารถตรวจสอบว่าตัวอักษรตัวแรกเป็นตัวพิมพ์ใหญ่โดยไม่สนใจอักษรตัวอื่นๆ

    ข้อดีอย่างหนึ่งของการเขียน unit tests ใน C # คือ เราสามารถใช้ Test Explorer ใน Visual Studio เพื่อตรวจสอบว่าการทดสอบทั้งหมดผ่านหรือไม่ โดยที่ไม่ต้องกังวลว่าการเปลี่ยนแปลงล่าสุดที่เขียน จะทำให้ระบบทำงานไม่ได้หรือไม่

    การเลือก Testing Framework

    Testing frameworks ช่วยให้เราสามารถใช้ attribute ในการเขียน unit tests [TestClass] หรือ [TestMethod] ซึ่ง attribute เหล่านี้บอกให้ Text Explorer ใน Visual Studio รู้จัก class หรือ method นั้นคือ unit test และจะรันโค้ดเพื่อตรวจสอบการทำงานว่าผ่านหรือไม่ผ่าน

    Testing frameworks สำหรับ. NET ที่ได้รับความนิยมคือ

    • MSTest
    • xUnit.NET
    • NUnit

    การเลือก Testing frameworks ก็เหมือนกับการเลือกรถ บางอันมีประสิทธิภาพสูง บางอันให้ความสะดวกสบาย แต่สุดท้ายทั้งหมดก็ทำงานเพื่อบรรลุเป้าหมายเดียวกัน

    NUnit เป็น test framework แรกๆที่ได้รับความนิยมสำหรับผู้พัฒนา .NET, เมื่อเปรียบเทียบกับความสามารถของ MSTest ( test framework ของ Microsoft) โดยส่วนใหญ่ก็จะเหมือนกับใน NUnit และ MSTest เป็น test framework ที่ถูกสร้างขึ้นและสามารถใช้งานได้ใน Visual Studio สำหรับ xUnit.NET เป็นน้องใหม่ใน test framework ที่นำเสนอคุณสมบัติเพิ่มเติมและวิธีการที่ง่ายขึ้นในการเขียน unit tests ที่พัฒนาโดย NET Core

    โดยส่วนใหญ่จะไม่ใช้เวลามากเกินไปในการพิจารณา test framework เพื่อใช้ในโครงการหากโครงการมีการกำหนด test framework ที่เฉพาะเจาะจง ก็ให้ใช้ test framework นั้น ถ้าไม่ได้กำหนด ก็ให้ทำการศึกษาและตัดสินใจเลือก test framework ที่เหมาะกับโครงการ

    AAA ( Arrange, Act, Assert )

    วิธีการเขียน unit test แบบ ‘AAA’ (Arrange, Act, Assert) เป็นวิธีปฏิบัติในการเขียน unit test และช่วยให้เราสามารถเขียน unit test ในรูปแบบที่ทำซ้ำและเข้าใจได้

    Arrange

    เราจะเริ่มเขียน unit test โดยการจัดเรียง objects ที่ต้องการทดสอบ ซึ่งอาจจะเป็นการ initializing class หรือ การเตรียมข้อมูลในฐานข้อมูลสำหรับทดสอบ หรือการ mocking object โดยการใช้ interface

    ตัวอย่างด้านล่าง มีกำหนดค่า mock object ของ ILogger<Calc> ให้กับ interface “ILogger”

    // Arrange
    ILogger log = Mock.Of<ILogger<Calc>>();
    ICalc calc = new Calc(log);

    Act

    เราจะดำเนินการ และเรียกใช้ส่วนของ code หรือ method ที่ต้องการทดสอบโดยการผ่านค่า parameter ให้กับ method และเก็บผลลัพธ์ใน valiable ที่กำหนด

    ตัวอย่างด้านล่าง เป็นการเรียกใช้ method “Divide” โดยส่ง parameter ไป 2 ตัว และเก็บผลลัพธ์การทำงานในตัวแปร result

    // Act
    int result = calc.Divide(10, 5);

    Assert

    สุดท้าย จะเป็นการยืนยันว่าผลลัพธ์ที่ได้จากการทดสอบเป็นไปตามที่ได้คาดหวังไว้หรือไม่

    ตัวอย่างด้านล่าง ตัวแปร “result” ควรที่จะมีค่าเท่ากับ 2

    // Assert
    Assert.AreEqual(2, result);

    การตั้งชื่อ

    เมื่อแนวคิดของ ‘AAA’ เป็นแนวปฏิบัติที่ได้รับความนิยม แต่ในส่วนของการกำหนดชื่อของ method ยังคงแตกต่างกันไปและขึ้นอยู่กับผู้พัฒนา ทีม และองค์กร ซึ่งการกำหนดแนวทางการตั้งชื่อเพื่อใช้กับ unit tests ให้มีความสอดคล้องและความชัดเจน ไปในทางเดียวกัน เป็นความคิดที่ดี ซึ่งการตั้งชื่อควรประกอบไปด้วย

    • method ที่ทดสอบ (เช่น ‘Add’)
    • คำอธิบายสั้น ๆ เกี่ยวกับการทดสอบ (เช่น ‘ShouldThrowArgumentException’)
    • ความคาดหวังของการทดสอบนี้ (เช่น ‘IfDivideByZero’)
    public void Divide_ShouldThrowArgumentException_IfDivideByZero()
    {
        ...
    }

    การกำหนดชื่อในรูปแบบนี้ จะเหมาะสำหรับการดูใน Text Explorer ซึ่งสามารถติดตามและเข้าใจได้อย่างง่ายดายว่า unit tests แต่ละหน่วยคืออะไรโดยไม่ต้องเปิดการทดสอบหน่วย

    แนวทางการเขียน Unit Tests ที่ดีสำหรับ C # ยังไม่หมดเพียงเท่านี้ ในบทความต่อไป เราจะมาดูการทำ Test Initialize & Cleanup และ ตัวช่วยอื่นๆที่ใช้ในการเขียน unit tests

    อ้างอิง : https://kiltandcode.com/2019/06/16/best-practices-for-writing-unit-tests-in-csharp-for-bulletproof-code/

  • เขียน 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/

  • ข้อมูลใน DropdownList แรกจนถึง DropdownList สุดท้ายกับค่าที่เหลืออยู่ของเขา ด้วย LINQ

    ในช่วงที่ผ่านมา ได้มีการพัฒนาระบบโดยมีการนำ LinQ มาใช้นิด ๆ หน่อย ๆ (มือใหม่หัดใช้ >< ) ซึ่งมีอยู่ฟังก์ชันนึงที่เกี่ยวกับ การ Bind ค่า DropdownList กรณีที่มี DropdownList หลาย ๆ ตัวและไม่อยากให้ค่าที่เคยเลือกจาก DropdownList ก่อนหน้าไปแสดงใน DropdownList ถัดไปอีก น่าสนใจทดลองใช้แล้วทำงานได้ดี ผู้เขียนจึงนำมาเขียนเพื่อไว้เตือนความจำ จะได้กลับมาอ่าน แนวคิดและวิธีการดังนี้ค่ะ

    ในขั้นตอนแรก สร้าง Data ที่ชื่อว่า MyList เก็บข้อมูล id และชื่อ เพื่อเตรียมเป็น DataSource สำหรับ Bind ให้กับ DropdownList1 ดังนี้

    class MyList
    {
        private string id = string.empty;
        private string name = string.empty;
    
        public MyList(string _id, string _name)
        {
            id = _id;
            name = _name;
        }
    
        public string ID
        {
            get{return id;}
            set {id = value;}
        }
    
        public string Name
        {
            get{return name;}
            set {name = value;}
        }
    }

    ต่อมากำหนดค่าให้กับ MyList เพื่อผูกให้กับ DropdownList1

    List<MyList> mylist = new List<MyList>{
                        new MyList(1,'name1'),
                        new MyList(2,'name2'),
                        new MyList(3,'name3'),
                        new MyList(5,'name5'),
                        new MyList(10,'name10')
                        };

    ผลลัพธ์ของ DropdownList1 ได้ดังนี้

    ID          Name
    1 name1
    2 name2
    3 name3
    5 name5
    10 name10

    ต่อมา กำหนดว่า DropdownList1 เลือกค่าเป็น ID = ‘2’

    string mySelectedValue = '2';

    จากนั้น ที่ Event SelectedIndexChange ของ DropdownList1 ให้ใส่ Code ดังนี้

    var myDataSource = from d in MyList
    where !mySelectedValue.Contains(d.ID)
    select d;

    จากนั้นผูก myDataSource ให้กับ DropdownList2 ค่าล่าสุดใน DropdownList2 จะแสดงดังด้านล่าง โดยที่ ID = ‘2’ จะหายไป เนื่องจากถูกเลือกไปแล้ว ดังนี้

    ID          Name
    1           name1
    3           name3
    5           name5
    10          name10

    กรณีที่มี DropdownList3 ก็ให้ทำเหมือนขั้นตอนก่อนหน้า ตัวอย่างกำหนดค่าการเลือกของ DropdownList1 = ‘2’ และ DropdownList2 = ‘5’ ดังนี้

    string mySelectedValue = '2,5';

    จากนั้น ที่ Event SelectedIndexChange ของ DropdownList2 ให้ใส่ Code แบบเดิม

    var myDataSource = from d in MyList
    where !mySelectedValue.Contains(d.ID)
    select d;

    จากนั้นผูก myDataSource ให้กับ DropdownList3 ค่าล่าสุดใน DropdownList3 จะแสดงดังด้านล่าง โดยที่ ID = ‘2’ และ ‘5’ จะหายไป เนื่องจากถูกเลือกไปแล้ว ดังนี้

    ID           Name
    1            name1
    3            name3
    10           name10

    จะเห็นได้ว่าเราใช้ Query เดียวกันในหลาย ๆ DropdownList ดังนั้นสามารถทำ Query นี้ให้เป็นฟังก์ชันเพื่อเรียกใช้งานได้

    หวังว่าจะเป็นประโยชน์ไม่มากก็น้อยสำหรับผู้อ่านนะคะ \(@^0^@)/

  • Crystal Report : Suppress การซ่อน/แสดง Section

    ปกติแล้วการจัดรายงานใน Crystal Report จะมีการแบ่งเป็น 5 ส่วนดังนี้ค่ะ

    Report Header Section

    เป็นส่วนที่อยู่บนส่วนหัวรายงาน แสดงแค่หน้าแรกเพียงหน้าเดียวเท่านั้น

    Page Header Section

    เป็นส่วนที่แสดงต่อจากส่วน Report Header Section แสดงอยู่ทุกหน้า

    Details Section

    ส่วนแสดงรายละเอียดที่ต้องการ สามารถจัดกลุ่มข้อมูล แบ่งรายงานเป็น 2 ส่วนและอื่น ๆ ได้

    Page Footer Section

    เป็นส่วนที่แสดงในรายงานทุกหน้าอยู่บนส่วนท้ายของรายงาน

    Report Footer Section

    เป็นส่วนที่อยู่ส่วนท้ายสุดของรายงานจะแสดงแค่หน้าสุดท้ายเท่านั้น

    ซึ่งในแต่ละ Section นั้นสามารถมี Section ย่อย ๆ ได้มากกว่า 1 Section ย่อยดังภาพ

    ซึ่งเราสามารถแสดงหรือซ่อน Section ย่อย ๆ เหล่านี้ได้ตามเงื่อนไขที่เราต้องการ โดยใช้เมนู “Suppress” จัดการ ดังนี้ค่ะ

    คลิกขวาที่ Section ใดก็ได้ เลือกเมนู “Section Expert”

    จะพบกับหน้าจอ Section Expert ดังภาพโดยหน้าจอจะแบ่งเป็น 2 ส่วน

    👈 ซ้ายมือ เป็นส่วนแสดงรายการ section ทั้งหมดของเราที่มีสามารถเพิ่ม ลบ หรือย้าย Section ขึ้นและลงตามที่ต้องการได้

    👉 ขวามือ จะแบ่งเป็น Tab ย่อย ๆ ในวันนี้ส่วนที่ผู้เขียนต้องการนำเสนออยู่ที่ Tab “Common”

    ที่ Tab “Common” หาบรรทัดที่ชื่อว่า “Suppress (No Drill-Down)”

    ดูที่ด้านขวา

    ถ้าสัญลักษณ์ “x-2” เป็น สีฟ้า แสดงว่า เรา ไม่ได้ มีการเขียนคำสั่งให้ซ่อนตามเงื่อนไข

    ถ้าสัญลักษณ์ “x-2” เป็น สีแดง แสดงว่า มี การเขียนคำสั่งให้ซ่อนตามเงื่อนไข

    จากรูปด้านล่างสามารถอธิบายได้ว่า ให้ซ่อน Page Header a โดยไม่มีเงื่อนไข

    ส่วนรูปด้านล่างสามารถอธิบายได้ว่า ให้ซ่อน Page Header l โดยมีเงื่อนไข ซึ่งสามารถกดเข้าไปดูเงื่อนไขที่เขียนไว้ได้

    ส่วนของคำสั่งที่เขียนไว้

    สามารถอธิบายได้ดังนี้

    เงื่อนไขคือ ถ้า Formula Field “@hdLang” มีค่าเป็น “E” และ ไม่ใช่รายงานหน้าแรก

    ผลลัพธ์ที่ต้องการ

    ✅ถ้าตรงตามเงื่อนไข ให้แสดง Page Header l

    ❌ถ้าไม่ตรงตามเงื่อนไข ให้ซ่อน Section นี้

    เพียงเท่านี้ ก็สามารถจัดการการซ่อนหรือแสดง Section ตามเงื่อนไขต่าง ๆ ได้ตามต้องการ o(*°▽°*)o d=====( ̄▽ ̄*)b

  • Crystal Report : Grid กับการจัดรูปแบบรายงาน

    ปกติแล้วการจัดรายงานใน Crystal Report ตัวหน้ากระดาษหรือหน้าจอนั้น จะเป็นหน้ากระดาษสีขาวปกติ ทำให้การจัดข้อความที่มีระยะเยื้องนั้นจัดได้ค่อนข้างยาก ตรงกันรึยังนะ หรือยังไม่ตรง นี่คือ 1 ในปัญหาของผู้เขียนเช่นกัน 🤣

    ดังนั้นวันนี้ ผู้เขียนจะมาแนะนำการตั้งค่า Grid เพื่อให้การขยับ Object ในรายงานของเราง่ายขึ้น ขั้นตอนดังนี้ค่ะ

    1️⃣ คลิกขวาพื้นที่โล่ง ๆ เลือกเมนู “Design”


    2️⃣ ต่อด้วยเมนู “Default Setting”



    3️⃣ ใน Tab “Layout” สังเกตส่วนด้านขวาที่ชื่อว่า “Grid”
    👀 จะพบ Checkbox “Show Grid” ให้ ✅ ไว้
    👀 ในส่วนของ Grid Size เราสามารถกำหนดได้ว่าจะให้ตัวกระดาษรายงานของเราแสดง Grid ถี่แค่ไหน ในที่นี้ผู้เขียนให้ความละเอียดมากสุดเลยกำหนดไว้ที่ 0.157



    4️⃣หลังจากกำหนดค่าทุกอย่างเสร็จแล้วกลับมาหน้ารายงานเดิมจะพบว่า หน้าจอเราดำมืด ไม่ต้องตรงใจค่ะ เป็นเพราะค่า Grid ที่เรากำหนดไว้ถี่มาก หน้าจอเลยกลายเป็นท่านเปา วิธีการก็คือ ไปขยายหน้าจอค่ะ โดยไปที่เมนูบาร์ด้านบน จะพบค่าที่ Default อยู่ที่ 200% ให้ขยายเป็น 400% หรือตามที่ต้องการเลยค่ะ



    ✨หน้าจอหลังจากขยายก็จะเห็นดังภาพนะคะ เราสามารถจัด Object ให้ตรงตามที่เราต้องการได้เล้ย ^^



  • Windows Terminal (1)

    เบื่อ cmd ใช้ Windows Terminal แทนกันดีกว่า… ให้ดูรูปก่อน

    สวยงามตระการตา!!!

    บางคนใช้แล้วอาจจะมีความสุข

    เริ่มได้

    • เหมาะสำหรับ Windows 10 version 1909 ขึ้นไป
    • ติดตั้ง Git
    • ติดตั้ง Windows Terminal จาก Microsoft Store หรือ จาก Github

    ถ้าหากติดตั้งจาก Github ต้องติดตั้ง Desktop Bridge VC++ v14 Redistributable Package ด้วย และโปรแกรมจะไม่อัพเดตตัวเองต้องโหลดมาปรับรุ่นเองทุกครั้ง

    ระวัง!!!
    • ติดตั้งแล้วเปิดใช้งานจะได้หน้าตาประมาณนี้
    • เราจะเปลี่ยนหน้าตากันเริ่มจากพิมพ์คำสั่งต่อไปนี้ (ต้องเชื่อมต่ออินเตอร์เน็ต)
    Install-Module posh-git -Scope CurrentUser
    Install-Module oh-my-posh -Scope CurrentUser

    Posh-Git เอาไว้แสดงข้อมูลของ Git ใน prompt
    Oh-My-Posh เป็น theme สวยๆ ของ powershell นั่นเอง

    • ต่อด้วยคำสั่ง
    Set-ExecutionPolicy Unrestricted -Scope CurrentUser
    • ตรวจสอบโฟลเดอร์สำหรับเก็บ Profile ของ PowerShell ด้วยคำสั่ง
    echo $PROFILE
    • สร้างแฟ้ม $PROFILE (ไฟล์ขื่อ Microsoft.PowerShell_profile.ps1 ในโฟลเดอร์ C:\Users\haruo\OneDrive\Documents\WindowsPowerShell\) โดยมีข้อความต่อไปนี้
    Import-Module posh-git
    Import-Module oh-my-posh
    Set-Theme Paradox
    • ปิดแล้วเปิดใหม่ก็จะได้ดังภาพ
    • จะเห็นว่ามีเครื่องหมาย  อยู่ที่ prompt ด้วยจำเป็นต้องลงฟอนท์เพิ่มเติมนั่นคือฟอนท์ Powerline ซึ่งสามารถติดตั้งโดยโหลด Cascadia Code มาติดตั้ง
    • เมื่อติดตั้งแล้วให้เปลี่ยนฟอนท์ของ Windows Terminal โดยคลิก แล้วเลือก Settings
    • จะเป็นการเปิดการตั้งค่า default ที่เรียกใช้งานอยู่ ด้วย default text editor
    • เลื่อน cursor ลงมาประมาณบรรทัดที่ 38 แล้วกด enter เพิ่มข้อความว่า
    "fontFace": "Cascadia Code PL",
    "fontSize": 10,
    • save แล้วไปดูผลได้เลย
    • สวยแล้ว!
    • จบขอให้สนุก
    • Oh-My-Posh ยังมี Theme อื่นๆ ลองเข้าไปเลือกดูได้
    • เปลี่ยน theme ได้โดยแก้แฟ้ม $PROFILE เปลี่ยนจาก Paradox เป็นอย่างอื่นเช่น Darkblood เป็นต้น save ปิดแล้วเปิด Windows Terminal ใหม่
  • [บันทึกกันลืม] แก้ปัญหา server certificate verification failed บน gitlabs

    ปัญหาคือ: จะ git push จาก Linux/Ubuntu มาเก็บใน gitlabs ขององค์กร

    git push -u origin master

    แต่ติดปัญหาว่า

    fatal: unable to access 'https://gitlab.xxx.xxx.xxxx/userid/repo.git/': server certificate verification failed. CAfile: /etc/ssl/certs/ca-certificates.crt CRLfile: none

    วิธีแก้ปัญหา (แบบรีบ ๆ)

    git config --global http.sslVerify false

    ก็จะใช้งานได้แล้ว

    *** เออ “ก็ง่าย ๆ” แต่ไม่มีบอกไว้ให้หาง่าย ๆ นิ ***

  • วิธีสร้างตาราง HTML Table จาก CSV File

    โจทย์คือ ต้องสร้างระบบ เพื่อให้ผู้ใช้ สร้าง โครงสร้างแบบฟอร์ม ด้วย Excel แล้วต้องแปลงให้เป็น Web Page ซึ่ง จะต้องการตกแต่งด้วย Bootstrap

    หน้าตาต้นแบบเป็นแบบนี้ (ในที่นี้ใช้ Google Sheets แต่ก็ทำวิธีการเดียวกันกับ MS Excel)

    ถ้า ใช้วิธี Save As เป็น HTML ตรง ๆ จะได้หน้าตาประมาณนี้

    ซึ่งจะยุ่งยากมาก ในการจัดการ และการควบคุมการแสดงผล

    วิธีการคือ !!!

    1 Save As เป็น CSV

    2 เปิดเว็บ
    http://www.convertcsv.com/csv-to-html.htm

    3 แล้ว Upload ไฟล์ หรือจะ Copy Paste ก็ได้

    4 ก็จะได้ plain HTML

    5 เอาไปประกอบกับ Bootstrap ได้สบาย (ในภาพ ใช้ django template tag ด้วย เลยได้ widget ตามที่กำหนด)

    หวังว่าจะเป็นประโยชน์ครับ