TDD với jUnit trên dự án Maven

JUnit là gì?

JUnit là một framework kiểm thử đơn vị mã nguồn mở cho Java. Nó rất hữu ích cho các nhà phát triển Java để viết và chạy các bài kiểm tra có thể lặp lại. Erich Gamma và Kent Beck ban đầu phát triển một thể hiện của kiến trúc xUnit. Như tên của jUnit được sử dụng để kiểm thử đơn vị của một đoạn mã nhỏ.

Các nhà phát triển đang tuân theo phương pháp hướng kiểm thử phải viết và thực hiện kiểm thử đơn vị trước bất kỳ mã nào.

Khi bạn đã hoàn thành mã, bạn nên thực hiện tất cả các bài kiểm tra và nó sẽ vượt qua. Mỗi khi bất kỳ mã nào được thêm vào, bạn cần phải thực thi lại tất cả các trường hợp thử nghiệm và đảm bảo không có gì bị hỏng.

Kiểm thử đơn vị là gì?

Trước khi thảo luận chi tiết về kiểm thử jUnit, bắt buộc phải hiểu kiểm thử đơn vị là gì?

Kiểm thử đơn vị được sử dụng để xác minh một đoạn mã nhỏ bằng cách tạo một đường dẫn, chức năng hoặc một phương pháp. Thuật ngữ "đơn vị" tồn tại sớm hơn so với thời đại hướng đối tượng. Về cơ bản, nó là sự trừu tượng hóa tự nhiên của hệ thống hướng đối tượng, tức là một lớp hoặc đối tượng Java (dạng khởi tạo của nó).

Kiểm thử đơn vị và tầm quan trọng của nó có thể được hiểu theo những điểm được đề cập dưới đây:

  • Kiểm thử đơn vị được sử dụng để xác định sớm các lỗi trong chu trình phát triển phần mềm.
  • Kiểm thử đơn vị sẽ bắt buộc phải đọc mã của riêng chúng ta, tức là một nhà phát triển bắt đầu dành nhiều thời gian cho việc đọc hơn là viết.
  • Các khiếm khuyết trong thiết kế mã ảnh hưởng đến hệ thống phát triển. Mã thành công tạo niềm tin cho nhà phát triển.

Tại sao bạn cần kiểm thử JUnit?

  • Nó phát hiện lỗi sớm trong mã, điều này làm cho mã của chúng tôi đáng tin cậy hơn.
  • JUnit hữu ích cho các nhà phát triển, những người làm việc trong môi trường chạy kiểm thử.
  • Kiểm thử đơn vị buộc một nhà phát triển phải đọc mã nhiều hơn là viết.
  • Bạn phát triển mã dễ đọc hơn, đáng tin cậy và không có lỗi, tạo sự tự tin trong quá trình phát triển.

JUnit API

Gói quan trọng nhất trong JUnit là junit.framework, chứa tất cả các lớp quan trọng. Một số lớp quan trọng như sau:

  • Assert: Một tập hợp các phương thức khẳng định.
  • TestCase: Một trường hợp kiểm thử xác định vật cố định để chạy nhiều kiểm thử.
  • TestResult: thu thập kết quả của việc thực thi một trường hợp kiểm thử.
  • TestSuite: là một tổ hợp các bài kiểm thử.
Lớp Assert

Lớp Assert cung cấp một tập hợp các phương thức khẳng định hữu ích cho việc viết các bài kiểm tra. Chỉ những xác nhận không thành công mới được ghi lại. Một số phương thức quan trọng của lớp Assert như sau:

  • void assertEquals(boolean expected, boolean actual)
    Kiểm tra xem hai đối tượng hoặc các biến kiểu dữ liệu nguyên thuỷ có bằng nhau không
  • void assertFalse(boolean condition)
    Kiểm tra xem một điều kiện là sai
  • void assertNotNull(Object object)
    Kiểm tra xem một đối tượng không phải là rỗng
  • void assertNull(Object object)
    Kiểm tra xem một đối tượng phải là rỗng
  • void assertTrue(boolean condition)
    Kiểm tra xem một điều kiện là đúng
  • void fail()
    Kiểm thử thất bại không có thông báo

Áp dụng TDD với JUnit trong dự án Maven

Khi tạo dự án Maven thì trong cấu trúc dự án của Maven thì đã có jUnit ngay trong dự án. Maven tự động tạo một lớp tên là AppTest.java trong thư mục /src/test là lớp kiểm thử cho lớp App.java trong thư mục /src/main.

Ví dụ ta có lớp QuadraticEquation2 (phương trình bậc 2) có sơ đồ lớp như sau:

Vấn đề cần xác định của chúng ta là làm thế nào biết được phương thức solveEquation() chạy đúng? Ta áp dụng TDD vào việc này như sau:

  1. Viết mã lệnh lớp QuadraticEquation2 rỗng (trong thư mục /src/main)
    public class QuadraticEquation2 {
        private double a;
        private double b;
        private double c;
    
        public PTB2(double a, double b, double c) throws Exception {   
        }
    
        public double[] solveEquation() {
            return null;
        }
    }​
  2. Thêm kiểm thử cho lớp QuadraticEquation2Test (trong thư mục /src/test)
    import org.junit.Test;
    import static org.junit.Assert.assertTrue;
    import org.junit.Assert;
    import java.util.Arrays;
    
    public class QuadraticEquation2Test {
        @Test
        public void solveEquationTest1() {
            try {
                final QuadraticEquation2 b21 = new QuadraticEquation2(1, 2, 1);
                final double[] result = b21.giaiPT();
                final double[] expected = { -1 };
                assertTrue(Arrays.equals(expected, result));
            } catch (final Exception e) {
                // TODO: handle exception
            }
        }
    
        @Test
        public void solveEquationTest2() {
            try {
                final QuadraticEquation2 b21 = new QuadraticEquation2(1, 1, 9);
                final double[] result = b21.giaiPT();
                Assert.assertNull(result);
            } catch (final Exception e) {
                // TODO: handle exception
            }
        }
    
        @Test
        public void solveEquationTest3() {
            try {
                final QuadraticEquation2 b21 = new QuadraticEquation2(1, -4, 3);
                final double[] result = b21.giaiPT();
                final double[] expected = { 1, 3 };
                assertTrue(Arrays.equals(expected, result));
            } catch (final Exception e) {
                // TODO: handle exception
            }
        }
    
        @Test
        public void solveEquationTest4() {
            try {
                final QuadraticEquation2 b21 = new QuadraticEquation2(1, -6, 5);
                final double[] result = b21.giaiPT();
                final double[] expected = { 1, 5 };
                assertTrue(Arrays.equals(expected, result));
            } catch (final Exception e) {
                // TODO: handle exception
            }
        }
    
        @Test
        public void validQuadraticEquation2Test() {
            try {
                new QuadraticEquation2(0, -6, 5);
            } catch (final Exception e) {
                assertTrue(e.getMessage().toString().equals("Invalid Quadratic Equation 2"));
            }
        }
    }
  3. Chạy kiểm thử -> Kiểm thử thất bại
    $ mvn test​

Vấn đề cần xác định của chúng ta là làm thế nào biết được phương thức solveEquation() chạy đúng? Ta áp dụng TDD vào việc này như sau:

      1. Viết mã lệnh lớp QuadraticEquation2 rỗng (trong thư mục /src/main)
        public class QuadraticEquation2 {
            private double a;
            private double b;
            private double c;
        
            public PTB2(double a, double b, double c) throws Exception {   
            }
        
            public double[] solveEquation() {
                return null;
            }
        }​
      2. Thêm kiểm thử cho lớp QuadraticEquation2Test (trong thư mục /src/test)
        import org.junit.Test;
        import static org.junit.Assert.assertTrue;
        import org.junit.Assert;
        import java.util.Arrays;
        
        public class QuadraticEquation2Test {
            @Test
            public void solveEquationTest1() {
                try {
                    final QuadraticEquation2 b21 = new QuadraticEquation2(1, 2, 1);
                    final double[] result = b21.giaiPT();
                    final double[] expected = { -1 };
                    assertTrue(Arrays.equals(expected, result));
                } catch (final Exception e) {
                    // TODO: handle exception
                }
            }
        
            @Test
            public void solveEquationTest2() {
                try {
                    final QuadraticEquation2 b21 = new QuadraticEquation2(1, 1, 9);
                    final double[] result = b21.giaiPT();
                    Assert.assertNull(result);
                } catch (final Exception e) {
                    // TODO: handle exception
                }
            }
        
            @Test
            public void solveEquationTest3() {
                try {
                    final QuadraticEquation2 b21 = new QuadraticEquation2(1, -4, 3);
                    final double[] result = b21.giaiPT();
                    final double[] expected = { 1, 3 };
                    assertTrue(Arrays.equals(expected, result));
                } catch (final Exception e) {
                    // TODO: handle exception
                }
            }
        
            @Test
            public void solveEquationTest4() {
                try {
                    final QuadraticEquation2 b21 = new QuadraticEquation2(1, -6, 5);
                    final double[] result = b21.giaiPT();
                    final double[] expected = { 1, 5 };
                    assertTrue(Arrays.equals(expected, result));
                } catch (final Exception e) {
                    // TODO: handle exception
                }
            }
        
            @Test
            public void validQuadraticEquation2Test() {
                try {
                    new QuadraticEquation2(0, -6, 5);
                } catch (final Exception e) {
                    assertTrue(e.getMessage().toString().equals("Invalid Quadratic Equation 2"));
                }
            }
        }
      3. Chạy kiểm thử -> Kiểm thử thất bại
        $ mvn test​

      4. Viết mã lệnh cho lớp QuadraticEquation2 và phương thức solveEquation()
        public class QuadraticEquation2 {
            private double a;
            private double b;
            private double c;
        
            public QuadraticEquation2(double a, double b, double c) throws Exception {
                if (a == 0) {
                    throw new Exception("Invalid Quadratic Equation 2");
                }
                this.a = a;
                this.b = b;
                this.c = c;
            }
        
            public void setA(double a) throws Exception {
                if (a == 0)
                    throw new Exception("Can't set a=0");
                this.a = a;
            }
        
            public void setB(double b) {
                this.b = b;
            }
        
            public void setC(double c) {
                this.c = c;
            }
        
            public double[] giaiPT() {
                double delta = Math.pow(b, 2) - (4 * a * c);
                if (delta == 0) {
                    double[] rs = { -b / (2 * a) };
                    return rs;
                } else if (delta > 0) {
                    double[] rs = { (-b - Math.pow(delta, 0.5)) / (2 * a), (-b + Math.pow(delta, 0.5)) / (2 * a) };
                    return rs;
                }
                return null;
            }
        }​
      5. Chạy kiểm thử xem có qua hết kiểm thử chưa?
        $ mvn test​

      6. Tái cấu trúc mã nguồn: Bây giờ bạn đã có một khối kiểm thử để đảm bảo rằng việc triển khai của bạn khớp với các thông số kỹ thuật, bạn có thể điều chỉnh và cấu trúc lại việc triển khai mà bạn có để đảm bảo rằng nó sạch sẽ và có cấu trúc theo cách bạn muốn mà không phải lo lắng rằng bạn sẽ phá vỡ những gì bạn vừa viết.