5 авг. 2010 г.

Немного про JUnit

  Использовать Unit тесты или нет, это уже решает каждый программист сам, где-то они удобны, где-то нет, поэтому это уже вопрос второй и не настолько важный, но ИМХО хотя бы знать что это такое и как это можно использовать - нужно каждому.

  Что такое JUnit? Это один из способов тестирования программ на Java. Существуют также фреймворки для других языков, но в данном примере рассмотрим JUnit.

  На просторах паутины очень много примеров и при желании можно легко найти от простых до достаточно сложных комплексных проверок. В данной статье - будет рассмотрена только основа.


  Итак предположим у нас есть задача написать математическую библиотеку с методами сложение, вычитания и какой нибудь хитрой функцией. Например такую:
public class MathTest {
  public static int inc(int a, int b){
    return a+b;
  }
  public static int dec(int a, int b){
    return a-b;
  }
  public static int f1(int a, int b){
    if (a>b){
      return a-b;
    } else {
      return a+b;
    }
  }
}
естественно её нужно проверить, самый простой и быстрый способ - это добавить точку входа main и написать все необходимые проверки, например так:
public static void main(String[] args) {
  System.out.println(MathTest.inc(2, 2));
  System.out.println(MathTest.dec(3, 2));
  System.out.println(MathTest.f1(12, 4));
  System.out.println(MathTest.f1(4, 12));
}
результатом будет:
4
1
8
16
с задачей "проверить" данный способ справляется на все 100%, но предположим что у нас таких методов десятки, или сотни... И "возможно" что при следующем внесении изменений будут затронуты те самые классы которые мы сейчас проверили. Тратить время и запускать каждый класс в отдельности никто не будет (программисты люди ленивые)...

  Вариант номер два - вынести все подобные проверки в один класс, в котором по очереди будут вызываться методы из MathTest и подобных классов + проверять "что вернулось", если данные отличаются - то генерировать сообщение об ошибке. Например так:
public static void main(String[] args) throws Exception {
  if (MathTest.inc(2, 2)!=4){ throw new Exception("MathTest.inc"); }
  if (MathTest.dec(3, 2)!=1){ throw new Exception("MathTest.dec"); }
  if (MathTest.f1(12, 4)!=8){ throw new Exception("MathTest.f1"); }
  if (MathTest.f1(4, 12)!=16){ throw new Exception("MathTest.f1"); }
  System.out.println("test ok...");
}
  Но в реальном проекте если есть хотя бы несколько десятков таких проектов - мы уже начнем выносить методы сравнения чисел, сравнения строк, разбивать на методы-блоки и т.д. и т.п. собственно даже в этом примере мы уже практически приблизились к написанию своего собственного JUnit'а (очередного велосипеда).

  Собственно для этого нам и нужна библиотека JUnit, для того что бы упростить задачу тестирования. Нам нужно скачать с офф-сайта http://www.junit.org/ библиотеку и добавить в зависимости к нашему проекту.

  Создадим новый класс наследник от junit.framework.TestCase и добавим в него методы, каждый метод-проверка должен начинаться с test.
public class MathTestUnit extends TestCase {
  public MathTestUnit() {
    super("MathTest class");
  }
  public void testInc(){
    assertEquals(4, MathTest.inc(2, 2));
    assertEquals(8, MathTest.inc(4, 4));
  }
  public void testDec(){
    assertEquals(1, MathTest.dec(3, 2));
    assertEquals(2, MathTest.dec(4, 2));
  }
  public void testF1(){
    assertEquals(8, MathTest.f1(12, 4));
    assertEquals(16, MathTest.f1(4, 12));
  }
}
в каждом методе мы выполняем по две проверки (можно одну, можно десять - это кому сколько нравится или сколько по Вашему мнению будет достаточно). Метод который мы используем assertEquals - это аналог той самой конструкции которую мы городили выше:
if (MathTest.inc(2, 2)!=4){ throw new Exception("MathTest.inc"); }
Собственно если используется Eclipse - достаточно в меню запуска класса выбрать "запустить как JUnit" и увидим окошко с результатами нашей проверки:



Через Eclipse так-же можно запустить весь пакет в котором находится несколько классов-тестов, например можно скопировать тот же самый тест и в нем специально допустить ошибку, в результате мы увидим:





При возникновении ошибки мы явно видим где и что произошло не так:
junit.framework.AssertionFailedError: expected:<16> but was:<17>
....
т.е. в нашем случае ожидалось 16, а метод вернул 17. Существуют методы assertEquals практически для всех основных типов + существуют такие как assertTrue/assertFalse, assertNull/assertNotNull и т.д. в документации все описано.


  Для тех кто любит консоль, можно написать приблизительно такую проверку:
public class ConsoleTest extends TestCase{
  public ConsoleTest(String testName) {
    super(testName);
  }
  public void testInc(){
    assertEquals(4, MathTest.inc(2, 2));
  }
  public void testDec(){
    assertEquals(1, MathTest.dec(3, 2));
  }
  public void testF1(){
    assertEquals(8, MathTest.f1(12, 4));
  }
  public static void main(String[] args) {
    TestRunner runner = new TestRunner();
    TestSuite suite = new TestSuite();
    suite.addTest(new ConsoleTest("testInc"));
    suite.addTest(new ConsoleTest("testDec"));
    suite.addTest(new ConsoleTest("testF1"));
    runner.doRun(suite);
  }
}
соответственно результатом выполнения будет нечто вроде:
...
Time: 0,003

OK (3 tests)
либо если произойдет ошибка:
...F
Time: 0,005
There was 1 failure:
1) testF1(ua.lg.moon.test.ju.ConsoleTest)junit.framework.AssertionFailedError: expected:<8> but was:<7>
at ua.lg.moon.test.ju.ConsoleTest.testF1(ConsoleTest.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at ua.lg.moon.test.ju.ConsoleTest.main(ConsoleTest.java:27)

FAILURES!!!
Tests run: 3,  Failures: 1,  Errors: 0
  И небольшой совет, для того что бы не включать проверки в основной проект (что бы на промо сервера не тянуть JUnit библиотеку), можно создать второй проект, в зависимостях которого установить Ваш основной и все JUnit проверки осуществлять во втором проекте.

Комментариев нет: