Tuesday, December 8, 2009

How to configure SQL Server Linked Server to Oracle

Nếu trong hệ thống của bạn sử dụng cả SQL Server và Oracle, thì đây là cách cho phép bạn thực hiện tạo 1 linked server từ SQL Server vào Oracle. Từ đó, bạn có thể thực hiện  các lệnh truy vấn trên Oracle và sử dụng dữ liệu nhận được trên SQL Server.

Các bước thực hiện như sau:

Bước 1: Cài đặt Oracle client trên SQL Server host

Bạn có thể download trình cài đặt Oracle client từ trang web của Oracle. Bạn nên sử dụng Oracle client phiên bản tương tự phiên bản của Oracle server.

Bước 2: Cấu hình kết nối đến Oracle Instance

Sử dụng “Oracle Net Configuration Assistant” để cấu hình kết nối đến Oracle server, hoặc bạn có thể thêm cấu hình vào file tnsnames.ora, format của cấu hình kết nối đến Oracle server như sau:

OracleServiceNameAlias = 
(DESCRIPTION = 
(ADDRESS_LIST = 
(ADDRESS = (PROTOCOL = TCP)(HOST = your-host-name-or-ip)(PORT = listener-port-usually-1521)) 
) 
(CONNECT_DATA = (SERVER = DEDICATED)(SERVICE_NAME = oracle-service-name) 
) 
)

Trong đó:

  • OracleServiceNameAlias: Do bạn tự đặt, được sử dụng như là Data Source khi cấu hình Linked Server.
  • your-host-name-or-ip: Là hostname hoặc địa chỉ ip của Oracle server mà bạn kết nối đến.
  • listener-port-usually-1521: Là port của Oracle server (cổng ngầm đinh của Oracle là 1521).
  • oracle-service-name: là service name của Oracle server

Bước 3: Test cấu hình đã được thiết lập

Bạn hãy dùng Command Prompt, đánh vào dòng lệnh sau: tnsping OracleServiceNameAlias

Ví dụ:

 C:\>tnsping OralceServiceNameAlias 
...
Used TNSNAMES adapter to resolve the alias 
Attempting to contact (DESCRIPTION = (ADDRESS_LIST = (ADDRESS = (PROTOCOL = TCP)(HOST = 127.0.0.1)(PORT = 1521)))(CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = OralceServiceNameAlias))) 
OK (10 msec)

Khi bạn thấy dòng trên, thì bạn cấu hình đã OK.

Bước 4: Create link from SQL Server to Oracle

Bạn sử dụng SQL Server Management Studio và chạy các câu lệnh sau:

EXEC sp_addlinkedserver @server = 'MyOracleLink', @srvproduct = 'Oracle', @provider = 'MSDAORA', @datasrc = 'OralceServiceNameAlias' 
EXEC sp_addlinkedsrvlogin 'MyOracleLink', false, 'NULL', 'ora_username', 'ora_password'

Trong đó:

  • sp_addlinkedserver thực hiện đăng ký link server vào SQL Server
  • @server=Tên của Linked Server, tên này phải chưa tồn tại trong danh sách Linked Servers
  • @srvproduct= Tên của data source kết nối đến (tùy chọn)
  • @provider = Provider sẽ sử dụng, ở đây MSDAORA = Microsoft OLE DB Provider for Oracle
  • @datasrc  = Tên của Oracle Instance Alias mà bạn đã cấu hình ở bước 2.
  • sp_addlinkedsrvlogin đăng ký account đăng nhập vào Oracle, sử dụng account ora_username/ora_password.

Bước 5: Tạo câu lệnh để test dữ liệu

SELECT * FROM OPENQUERY(MyOracleLink, 'SELECT * FROM TableTest')

Như vậy, với câu lệnh trên thực hiện được, thì quá trình cấu hình Linked Server đã hoàn tất.

Tuesday, December 1, 2009

Reset Identity Column in SQL Server

Khi thiết kế cơ sở dữ liệu, bạn có thể thiết lập một cột trong bảng với thuộc tính Is Identity = true. SQL Server sẽ tự động thiết lập giá trị tăng tự động cho cột đó.

Tuy nhiên, sau một thời gian kiểm thử database, bạn muốn reset lại giá trị cho cột này về một giá trị nào đó bạn mong muốn, thì câu lệnh sau cho phép bạn thực hiện việc này.

USE [TenDatabase];

DBCC CHECKIDENT(TenBang, RESEED, Giatri);

Trong đó:

TenBang là tên của bảng có chứ Identity Column mà bạn muốn thiết lập lại giá trị.

Giatri là giá trị mà bạn khởi tạo. Nếu bạn đặt giá trị này bằng 0, thì row được insert ngay sau đó sẽ có giá trị là 1 với cột tự tăng này.

Chú ý rằng việc insert sau đó có thể gặp lỗi nếu bạn thiết lập giá trị khởi tạo đến giá trị nhỏ hơn hoặc bằng giá trị lớn nhất đang tồn tại trên cột tự tăng của bảng đó.

Thursday, August 13, 2009

Cách giải quyết Concurrency và xung đột khi tạo khóa tự động trong SQL Server

Yêu cầu

Trong yêu cầu thực tế của dự án mà tôi gặp phải, đó là trong trường hợp tạo mã khách hàng trong dữ liệu khách hàng, chúng tôi cần phải đảm bảo các yêu cầu sau:

1. Mã khách hàng được sinh tự động gồm 13 ký tự số.

2. Mã khách hàng được đánh theo nguyên tắc: [xxx]{0}[Số thứ tự tăng dần]

Trong đó: [xxx] là 3 ký tự số quy định tùy theo nơi đăng ký sẽ khách nhau; {0} là được chèn vào để đảm bảo mã khách hàng luôn là 13 ký tự số; [Số thứ tự tăng dần] là số tăng dần bắt đầu từ 1.

Phân tích

Rõ ràng, với [Số thứ tự tăng dần] thì chúng tôi đơn giản tạo 1 cột số nguyên và sử dụng tính năng Auto-increment của SQL Server. Với mã khách hàng của khách hàng mới, thì sẽ thực hiện lần lượt các công việc:

1. SELECT [Số thứ tự tăng dần] từ bảng Customers

2. [Mã khách hàng mới] = [Số thứ tự tăng dần] + 1

2. Gán các số 0 vào đầu [Mã khách hàng mới] để đủ 10 ký tự số

3. Gán 3 ký tự số [xxx] cho sẳn vào đầu [Mã khách hàng mới]

4. INSERT [Mã khách hàng mới] vào cơ sở dữ liệu

Vậy, việc giải quyết vấn đề Concurrency và xung đột sẽ như thế nào cần được đặt ra.

Cách giải quyết vấn đề

Có một cách giải quyết vấn đề đơn giản và hiệu quả là sử dụng lời gợi ý (hint) UPDLOCK. Khóa UPDATE là một shared lock mà sẽ không khóa các lệnh đọc khác. Tuy nhiên, tại một thời điểm chỉ có một xử lý (process) được giữ UPDLOCK trên 1 dòng (row), vì vậy nếu đồng thời có 2 xử lý song song đồng thời, thì xử lý thứ hai sẽ phải chờ xử lý thứ nhất hoàn thành rồi mới tiến hành giữ khóa UPDATE để thực hiện.

Và đây là đoạn code để mô tả cách giải quyết vấn đề trên:

BEGIN TRANSACTION

SELECT coalesce(MAX(CustomerIndex, 0)) + 1 FROM Customers WITH (UPDLOCK)

-- Thực hiện tạo Mã khách hàng mới dựa trên số thứ tự nhận được

INSERT Customers(...)

COMMIT TRANSACTION

Saturday, August 8, 2009

SQLite3 đã được viết bằng .NET

Noah Hart đã viết lại SQLite3 bằng C#. Mặc dù bản đầu tiên này vẫn chậm hơn so với bản gốc, tuy nhiên dự án này đã mở ra cách mới cho việc sử dụng SQLite trong C# mà không phải sử dụng P/Invoke hoặc mã lệnh không-an-toàn (unsafe code).

Dự án C#-SQLite được đưa trên trang Google Code với phiên bản của SQLite 3.6.16, mã nguồn này tuân thủ theo giấy phép (license) Create Common 3.0. C#-SQLite đã được kiểm thử với hơn 30,000 trường hợp test với chỉ có 9 trường hợp test vẫn chưa thực hiện được. Mã nhị phân hoàn chỉnh của nó là 528KB trong khi bản gốc là 506KB. Hiệu năng của nó là vẫn chậm hơn từ 3-5 lần so với bản gốc được cài đặt bằng C, nhưng mã nguồn C# hiện tại vẫn chưa được tối ưu hóa, và hiệu năng hiện tại vẫn ở mức chấp nhận được. Hiện tại, thì số dòng trên một giây được thể hiện ở bảng sau:

Test

C#-SQLite

SQLite 3

Insert

300K

1300K

Select

1500K

8450K

Update

60K

300K

Delete

250K

700K

C#-SQLite tuy có hiệu năng chậm hơn so với SQLite được cài đặt trên C, tuy nhiên nó thật sự là sự lựa chọn tốt cho các lập trình viên C#, bởi vì tránh phải sử dụng P/Invoke bởi vì nó khá chậm và không linh hoạt. Một lý do khác nữa là mã nguồn C sử dụng lệnh goto trong hầu hết mọi nơi, có thể cản trở việc ngăn chặn các ngoại lệ.

Tuesday, July 14, 2009

Sử dụng SystemParametersInfo để thay đổi thiết lập giao diện

Hàm SystemParametersInfo cho phép bạn truy cập và thay đổi hầu hết các thiết lập giao diện người sử dụng ở mức thấp (màn hình Display Properties của Windows).

Bạn có thể đọc chi tiết trên MSDN, ở đây tôi chỉ đưa một số ví dụ cơ bản:

  • SPI_GETICONTITLELOGFONT giúp bạn lấy thông tin font chữ được sử dụng cho các nhãn icon; SPI_SETICONTITLELOGFONT giúp bạn thay đổi thông tin này.
  • SPI_GETNONCLIENTMETRICS giúp bạn lấy thông tin về font chữ được sử dụng trong các màn hình của Window bao gồm captions, menus, status bars, và message boxes; SPI_SETNONCLIENTMETRICS giúp bạn thay đổi chúng.

Và một số thiết lập trong Control Panel như:

  • SPI_SETKEYBOARDDELAY và SPI_SETKEYBOARDSPEED giúp bạn thiết lập các tham số của keyboard.
  • SPI_SETDOUBLECLICKTIME giúp bạn thiết lập tốc độ double-click của con chuột.
  • SPI_SETMENUFADE giúp bạn thiết lập hoặc vô hiệu hóa menu fade animation.

Sau đây, tôi sẽ viết một đoạn chương trình demo để thay đổi thiết lập font chữ của MessageBox trên Windows.

    public class WindowsUISettings
    {
        // Khai báo sử dụng API SystemParametersInfo
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern bool SystemParametersInfo(
            int Action, int uiParam, [In, Out] NONCLIENTMETRICS pvParam, int WinIni);

        [StructLayout(LayoutKind.Sequential)]
        private class NONCLIENTMETRICS
        {
            public int cbSize = Marshal.SizeOf(typeof(NONCLIENTMETRICS));
            public int iBorderWidth;
            public int iScrollWidth;
            public int iScrollHeight;
            public int iCaptionWidth;
            public int iCaptionHeight;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfCaptionFont;
            public int iSmCaptionWidth;
            public int iSmCaptionHeight;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfSmCaptionFont;
            public int iMenuWidth;
            public int iMenuHeight;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfMenuFont;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfStatusFont;
            [MarshalAs(UnmanagedType.Struct)]
            public LOGFONT lfMessageFont;
        }


        private const int LF_FACESIZE = 32;

        // A "logical font" used by old-school windows
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private class LOGFONT
        {
            public int lfHeight;
            public int lfWidth;
            public int lfEscapement;
            public int lfOrientation;
            public int lfWeight;
            public byte lfItalic;
            public byte lfUnderline;
            public byte lfStrikeOut;
            public byte lfCharSet;
            public byte lfOutPrecision;
            public byte lfClipPrecision;
            public byte lfQuality;
            public byte lfPitchAndFamily;
            
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = LF_FACESIZE)]
            public string lfFaceName;

            // to shut it up about the warnings
            public LOGFONT(string lfFaceName)
            {
                this.lfFaceName = lfFaceName;
                lfHeight = lfWidth = lfEscapement = lfOrientation = lfWeight = 0;
                lfItalic = lfUnderline = lfStrikeOut = lfCharSet = lfOutPrecision
                = lfClipPrecision = lfQuality = lfPitchAndFamily = 0;
            }
        }

        //Khai báo SPI lấy và thay đổi thiết lập về 
        //caption, menu, status bar, scroll bar và message box 
        private const int SPI_GETNONCLIENTMETRICS = 0x0029;
        private const int SPI_SETNONCLIENTMETRICS = 0x002A;

        private NONCLIENTMETRICS currentMetrics;

        /// <summary>
        /// Lưu giữ lại các giá trị ngầm định
        /// </summary>
        private static NONCLIENTMETRICS defaultMetrics;

        private WindowsUISettings()
        { }

        public static WindowsUISettings CurrentMetrics()
        {
            //Lưu giữ lại các giá trị ngầm định
            defaultMetrics = new NONCLIENTMETRICS();
            SystemParametersInfo(SPI_GETNONCLIENTMETRICS, defaultMetrics.cbSize, defaultMetrics, 0);

            NONCLIENTMETRICS pvParam = new NONCLIENTMETRICS();
            SystemParametersInfo(SPI_GETNONCLIENTMETRICS, pvParam.cbSize, pvParam, 0);

            WindowsUISettings instance = new WindowsUISettings();
            instance.currentMetrics = pvParam;

            return instance;
        }

        /// <summary>
        /// Lấy/Thiết lập font chữ cho MessageBox
        /// </summary>
        public Font MessageFont
        {
            get
            {
                return Font.FromLogFont(currentMetrics.lfMessageFont);
            }
            set
            {
                value.ToLogFont(currentMetrics.lfMessageFont);
            }
        }

        /// <summary>
        /// Thiết lập các thay đổi
        /// </summary>
        public void Apply()
        {
            SystemParametersInfo(SPI_SETNONCLIENTMETRICS, currentMetrics.cbSize, currentMetrics, 1);
        }

        /// <summary>
        /// Đặt lại các giá trị ngầm định
        /// </summary>
        public void RestoreDefault()
        {
            SystemParametersInfo(SPI_SETNONCLIENTMETRICS, defaultMetrics.cbSize, defaultMetrics, 1);
        }
    }

Và đây là ví dụ chạy thử:

Hãy tạo 1 form mới, trên đó có 2 button bao gồm, button1 và button2. Double-click vào button1 và sau đó button2, ví dụ xử lý sự kiện Click trên 2 button sẽ như sau:

    public partial class Form1 : Form
    {
        WindowsUISettings metrics 
            = WindowsUISettings.CurrentMetrics();

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Font messageFont = new Font(
                metrics.MessageFont.FontFamily, 14);

            metrics.MessageFont = messageFont;
            metrics.Apply();

            MessageBox.Show(
            "MessageBox đã được thay đổi font size thành 14.");
        }

        private void button2_Click(object sender, EventArgs e)
        {
            metrics.RestoreDefault();
            MessageBox.Show(
            "MessageBox đã được trở về font size 8.");

        }
    }
Qua ví dụ này, chắc các bạn đã hiểu cách để lấy thông tin và thay đổi các thông số khác.

Monday, July 6, 2009

Cách chạy Crystal Report trên Windows Vista/7

Giới thiệu

Khi tôi chạy chương trình để hiển thị các báo cáo sử dụng Crystal Report XI trên Windows Vista hoặc Windows 7 thì luôn nhận được thông báo lỗi:

Description:
  Stopped working

Problem signature:
  Problem Event Name:    APPCRASH
  Application Name:    CRXISample.exe
  Application Version:    1.0.0.0
  Application Timestamp:    4a517191
  Fault Module Name:    unknown
  Fault Module Version:    0.0.0.0
  Fault Module Timestamp:    00000000
  Exception Code:    c0000005
  Exception Offset:    04b978c8
  OS Version:    6.1.7100.2.0.0.256.1
  Locale ID:    1041

Mô tả lỗi

Lỗi này xuất hiện do tính năng DEP (Data Execution Prevention) trên Windows Vista/7 khi được kích hoạt (enabled) thì sẽ ngăn chặn (block) tất cả các chương trình  đang cố gắng chạy mà không nằm trong danh sách tin cậy (trusted list).

Khắc phục lỗi

Lỗi này có thể được khắc phục bằng cách vô hiệu hóa tính năng DEP, tuy nhiên khi tính năng này được khóa thì có thể dẫn đến một số nguy hại đối với máy tính của bạn. Do đó, ở đây tôi sẽ đưa ra cách vô hiệu hóa tính năng này và có thể kích hoạt tính năng này nếu cần thiết.

Hình sau minh họa khi kích hoạt DEP và khi vô hiệu hóa DEP, ngầm định là DEP được kích hoạt.

DEP_Enabled DEP_Disabled

Kích hoạt DEP (Enabled)

1. Mở màn hình Command Prompt: Nhấn tổ hợp phím Windows+R; nhấn cmd và Enter.

2. Đánh dòng lệnh bcdedit.exe /set {current} nx OptIn và nhấn Enter.

3. Bạn có thể nhận được thông điệp báo thành công.

4. Khởi động lại máy.

Vô hiệu hóa DEP (Disabled)

1. Mở màn hình Command Prompt: Nhấn tổ hợp phím Windows+R; nhấn cmd và Enter.

2. Đánh dòng lệnh bcdedit.exe /set {current} nx AlwaysOff và nhấn Enter.

3. Bạn có thể nhận được thông điệp báo thành công.

4. Khởi động lại máy.

Kết luận

Cách vô hiệu hóa DEP có thể giúp nhiều chương trình chạy tốt trên môi trường Windows Vista hoặc Windows 7, tuy nhiên nó cũng có thể đem đến rắc rối với virus. Do đó, bạn nên cẩn thận khi sử dụng cách này.

Về Crystal Report bạn sẽ gặp vấn đề này trên hầu hết các phiên bản khi chạy trên Windows Vista hoặc Windows 7, tuy nhiên hiện nay phiên bản Crystal Report 2008 V1 (SP1) đã được khắc phục lỗi này. Do đó, bạn nên dùng CR 2008 V1 nếu có thể.

Friday, May 22, 2009

How to publish a ClickOnce Application without Visual Studio

When i search a solution for the question “How to change .exe.config.deploy”, i cannot have a good answer. So, i try to create my solution and it is successful.

In this solution, you can deploy a ClickOnce Application to customer’s server, it execute with requests:

  • Modify config in app.config, example: Connection String, Web Reference URL,…
  • Change start location of a signed ClickOnce Application.
  • Change Version of product.
  • Publish application without VS and .NET Framework SDK.
1. Create necessary files for ClickOnce Server

        To publish the ClickOnce Application to a server, you must have necessary files includes: MyApp.application (my application sample is MyApp), index.html and setup.exe.

        Open the source code in Visual Studio and publish ClickOnce Application to local IIS . Next, open  publish folder (C:\Inetpub\wwwroot\MyApp) and copy only files MyApp.application, index.html and setup.exe to the deployment folder (MyAppClickOnce). This folder will be provided the customer who will publish application to his server.

  2. Copy the contents of the application to the deployment folder

       Copy the contents of the application from the build output folder (bin\Release) to folder’s name [ApplicationName_Version(a_b_c_d)] to the deployment folder (MyAppClickOnce\MyApp_1_0_0_0).

       Copy the publisher certificate that was generated by Visual Studio when you first published to the deployment folder.

       Open folder [Microsoft Visual Studio 8]\SDK\v2.0\Bin and copy file mageui.exe  to the deployment folder.

       Now, you can compress the deployment folder to your customer with install guide and he should be install a ClickOnce Application without Visual Studio and .NET Framework SDK 2.0.

3. Publish application
  • Open mageui.exe, on menu File –> New –> Application Manifest.
  • In the tab “app1.exe.manifest”, select “Name” from list in the left, input name of application into Name textbox  (MyApp) and Version textbox (1.0.0.0).
  • In the tab “app1.exe.manifest”, select “Files” from list in the left,
    • At “Application directory:”, select folder containing the application files (MyApp_1_0_0_0) .
    • Click check on checkbox “When populating add the .deploy extension to any files that does not have it”.
    • Click button “Populate”.
  • On menu of mageui, select File –> Save.
    • In the screen “Signing Options”, select option “Sign with certificate file”, click button […] to select publisher certificate. Click OK to next.
    • In the screen “Save As”, save with default file name to folder containing application files (MyApp_1_0_0_0).
  • On menu of mageui, select File –> Open.
  • Select file MyApp.application in the deployment folder.
  • In the tab “MyApp.application”, select “Name” from list in the left, change Version to 1.0.0.0
  • In the tab “MyApp.application”, select “Deployment Options”, modify “Start Location” to new path or url of the current server.
  • In the tab “MyApp.application”, select “Application Reference”, click button “Select Manifest…” to select file manifest that is created above.
  • On menu of mageui, select File –> Save.
  • In the screen “Signing Options”, select option “Sign with certificate file”, click button […] to select publisher certificate. Click OK to save.

Now, you can install ClickOnce Application from new location.

P.S: I am sorry about my English. :)

Wednesday, May 6, 2009

Test-Driven Development (TDD) trong .NET bằng ví dụ – Phần 3

Phần này sẽ cài đặt các Test 3 và Test 4 trong loại bài về Test-Driven Development trong .NET qua ví dụ. Các phần trước bao gồm:

Test-Driven Development (TDD) trong .NET bằng ví dụ

Test-Driven Development (TDD) trong .NET bằng ví dụ – Phần 2

Test 3: Push một đối tượng đơn, Pop đối tượng đó và xác nhận IsEmpty là true.

Trong Test case này, ta thêm một phương thức mới cho lớp Stack có nhiệm vụ lấy ra phần tử trên đỉnh của Stack. Nhưng trước tiên, ta cần tiến thành cài đặt unit test cho test case này, đoạn code như sau:

    [Test]
    public void Pop()
    {
        stack.Push("first element");
        stack.Pop();
        Assert.IsTrue(stack.IsEmpty,
            "Sau khi Push - Pop. IsEmpty phải bằng true.");
    }

Và tất nhiên, ta phải cài đặt một phương thức Pop trong lớp Stack.

    public void Pop()
    {
    }

Tuy nhiên, nếu bây giờ bạn tiến hành complite và chạy test, thì sẽ trả về kết quả fail. Do đó, ta bổ sung thêm m_IsEmpty = true để hoàn thành test case này.

    public void Pop()
    {
        m_IsEmpty = true;
    }

Kết quả là chúng ta đã hoàn thành unit test cho Test case 3. Tiếp theo, ta sẽ cài đặt test case 4.

Test 4: Push một đối tượng đơn, ghi nhớ đối tượng đó; Pop đối tượng, và xác nhận hai đối tượng này là giống nhau.

Trong unit test của test case này, chúng ta sẽ thực hiện so sánh nội dung của 2 phần tử, đó là phần từ đưa vào Stack với phần tử lấy ra từ Stack ngay sau khi đưa vào. Do đó, ta sẽ đặt tên là PushPopContentCheck:

    [Test]
    public void PushPopContentCheck()
    {
        int expected = 1234;
        stack.Push(expected);   
        int actual = (int) stack.Pop();
        Assert.AreEqual(expected, actual);
    }

Tiếp theo, ta cần phải thay đổi lại phương thức Pop của lớp Stack.

    public object Pop()
    {
        m_IsEmpty = true;
        return null;
    }

Sau khi build thành công và chạy test, thì kết quả test trả về sẽ error vì lúc này ta chưa trả về giá trị trên đỉnh của Stack. Do đó, để pass cho unit test này, chúng ta lưu lại giá trị nhận được từ Push và trả lại giá trị trong Pop. Đoạn code sau khi thay đổi sẽ như sau:

using System;

public class Stack
{
    private bool m_IsEmpty = true;
    private object m_Element;

    public bool IsEmpty
    {
        get {
            return m_IsEmpty;
        }
    }

    public void Push(object element)
    {
        m_IsEmpty = false;
        m_Element = element;
    }

    public object Pop()
    {
        m_IsEmpty = true;
        object top = m_Element;
        m_Element = null;
        return top;
    }
}

Lúc này, ta thấy phương thức IsEmpty chỉ cần kiểm tra xem phần tử đang được lưu có bằng null hay không. Do đó, ta sẽ thay đổi ở phương thức IsEmpty như sau:

using System;

public class Stack
{
    private object m_Element = null;

    public bool IsEmpty
    {
        get {
            return (m_Element == null);
        }
    }

    public void Push(object element)
    {
        m_Element = element;
    }

    public object Pop()
    {
        object top = m_Element;
        m_Element = null;
        return top;
    }
}

Lúc này đã tốt hơn với code của Stack vì ta thấy rằng lúc này chỉ cần cập nhập lại một biến thay vì hai như trước đây.  Đến lúc này, khi thực hiện đưa vào 1 phần tử thì nó vẫn nằm trên đỉnh của Stack vì lúc lấy ra ta lấy đúng phần tử đã đưa vào. Tuy nhiên, sẽ có vấn đề lúc đưa nhiều hơn 1 phần từ vào Stack, ta sẽ được thấy điều này qua các test case tiếp theo.

Test-Driven Development (TDD) trong .NET bằng ví dụ – Phần 2

Trong Phần 1 chúng ta đã hoàn thành Test 1, bây giờ tiếp theo phần này chúng ta sẽ hoàn thành Test số 2.

Test 2:  Push một đối tượng đơn vào Stack và xác nhận IsEmpty là false

Bây giờ chúng ta sẽ viết code để test yêu cầu này.

    [Test]
    public void PushOne()
    {
        Stack stack = new Stack();
        stack.Push("first element");
        Assert.IsFalse(stack.IsEmpty,
            "Sau khi Push. IsEmpty phải bằng false.");
    }

Và tất nhiên, ta sẽ viết phương thức Push cho lớp Stack.

    public void Push(object obj)
    {
    }

Bây giờ, ta thực hiện chạy NUnit Test với PushOne và cho kết quả sau:

Passed: 1 Failed: 1 Errors: 0 Inconclusive: 0 Invalid: 0 Skipped: 0 Time: 0.078125

StackFixture.PushOne:  Sau khi Push. IsEmpty phải bằng false.
  Expected: False
  But was:  True

Kết quả test fail bởi vì sau khi Push thì m_IsEmpty = false, nhưng ở đây vẫn đặt là true. Do đó, ta thực hiện thay đổi phương thức Push như sau:

    public void Push(object obj)
    {
        m_IsEmpty = false;
    }

Trước khi cài đặt Test Case tiếp theo, chúng ta cần hiệu chỉnh code test để tránh trùng lặp code. Do đó, ta sẽ tạo thêm các phương thức Init để làm việc này với thuộc tính được khai báo là [Setup], nó sẽ tự động khởi tạo các dữ liệu cần thiết trước khi chạy mỗi Test Case. Đây là mã nguồn sau khi hiệu chỉnh:

using System;
using NUnit.Framework;

[TestFixture]
public class StackFixture
{
    private Stack stack;

    [SetUp]
    public void Init()
    {
        stack = new Stack();
    }

    [Test]
    public void Empty()
    {
        Assert.IsTrue(stack.IsEmpty);
    }

    [Test]
    public void PushOne()
    {
        stack.Push("first element");
        Assert.IsFalse(stack.IsEmpty,
            "Sau khi Push. IsEmpty phải bằng false.");
    }
}

Trên đây là một ví dụ tuyệt vời cho nguyên tắc thứ 2 của TDD: Loại trừ trùng lặp.

Bây giờ, kết quả chạy của các test case đã được cài đặt đều pass. Do đó, ta sẽ chuyển qua Test Case tiếp theo.

Test-Driven Development (TDD) trong .NET bằng ví dụ

Test-Driven Development (TDD) như được định nghĩa trong Wiki, là một kỹ thuật phát triển phần mềm chia nhỏ một chức năng thành các pha phát triển nhỏ dựa trên việc định nghĩa các test case. Mỗi pha tạo ra các đoạn code cần thiết để có thể đạt được các pha test. Cuối cùng, người lập trình hiệu chỉnh (refactor) code cho khớp với các thay đổi. Một khái niệm quan trọng của TDD là phải chuẩn bị các bản test trước khi lập trình để thuận tiện cho việc nhanh chóng đáp ứng các thay đổi. Chú ý rằng test-driven development là một phương pháp thiết kế phần mềm, không chỉ là một phương pháp của việc kiểm thử.

Trong bài viết này, chúng tôi sẽ sử dụng một ví dụ là xây dựng một Stack sử dụng Test-Driven Development (TDD) theo từng bước một.

Nhim v

Stack là một cấu trúc dữ liệu mà được truy cập theo kiểu tuần tự vào sau ra trước. Các hoạt động bao gồm: Push, Pop, Top IsEmpty. Push thêm một phần từ vào đỉnh của Stack, Pop lấy một phần tử trên đỉnh của Stack, Top trả lại phần tử trên đỉnh của Stack. IsEmpty trả về true khi Stack rỗng, ngược lại là false.

Như vậy, danh sách test gồm:

  • Tạo một Stack và xác nhận IsEmpty là true.
  • Push một đối tượng đơn vào Stack và xác nhận IsEmpty là false.
  • Push một đối tượng đơn, Pop đối tượng đó và xác nhận IsEmpty là true.
  • Push một đối tượng đơn, ghi nhớ đối tượng đó; Pop đối tượng, và xác nhận hai đối tượng này là giống nhau.
  • Push ba đối tượng, ghi nhớ chúng; Pop từng đối tượng một, và xác nhận chúng được xóa theo đúng thứ tự.
  • Pop một Stack không có thành phần nào.
  • Push một đối tượng đơn và sau đó gọi Top. Xác nhận IsEmpty là false.
  • Push một đối tượng đơn, nhớ nó; sau đó gọi Top. Xác nhận đối tượng trả lại là giống với đối tượng đã được đưa vào.
  • Gọi Top trên một Stack rỗng.

Ở đây, chúng ta sẽ chọn test case đơn giản nhất để thực hiện đầu tiên, đó chính là test case đầu tiên trên danh sách ở trên.

Cài đặt

Tiếp theo, chúng ta sẽ cài đặt từng test case trong danh sách ở trên.

Test 1: Tạo một Stack và xác nhận IsEmpty là true.

Chúng ta sẽ tạo 1 file source code test gọi là StackFixture.cs, trong đó sẽ chứa các test case được cài đặt.

Đầu tiên, chúng ta khai báo

  using System; 
  using NUnit.Framework; 

  [TestFixture] 
  public class StackFixture 
  { /*...*/ }	
  

Tiếp theo, chúng ta viết phương thức test cho test case này, tên là Empty.

    [Test]
    public void Empty()
    {
        Stack stack = new Stack();
        Assert.IsTrue(stack.IsEmpty);
    }

Với bản test ở trên, chúng ta sẽ thực hiện cài đặt code cho Property là IsEmpty của lớp Stack mà ta đang cài đặt.

using System;

public class Stack
{
    private bool m_IsEmpty;

    public bool IsEmpty
    {
        get {
            return m_IsEmpty;
        }
    }
}

Như vậy, việc cài đặt cho test case đầu tiên đã hoàn thành. Khi bắt đầu, bạn nên tập trung vào test case mà bạn đang viết và không nghĩ đến các test case khác. Điều đó sẽ giúp bạn kiểm soát được tốt hơn khi mà lượng test case lớn hơn.

 

Thursday, March 12, 2009

Thay đổi VS Color Schemes

Hãy vào trang VS Color Schemes để tham khảo cách thực hiện thay đổi Color Scheme của Visual Studio gồm cả 2005 và 2008 cũng như SQL Server Management Studio.