DUnit — DUnit
DUnit — это автоматизированная модульная среда тестирования для Embarcadero Delphi. DUnit позволяет разработчикам Delphi (Object Pascal ) использовать разработку через тестирование.
Исходный код DUnit был написан и основан на JUnit Кент Бек и Эрих Гамма. Некоторые разработчики теперь поддерживают DUnit как проект на SourceForge.
DUnit стал стандартной частью Delphi, начиная с Delphi 2005. Два ключевых файла обеспечивают большую часть функциональных возможностей тестовой среды: TestFramework.pas и GUITestRunner.pas.
Расширения DUnit
PascalMock предоставляет расширение DUnit с классом TMockObjectTestCase, которое позволяет легко использовать Mock object в модульных тестах.
TestGrip использует DUnit для создания и поддержки модульных тестов в качестве расширения Delphi IDE
TestInsight обеспечивает тесную интеграцию с IDE, при которой тесты автоматически запускаются в фоновом режиме после изменений кода / компилирует.
Внешние ссылки
- Страница проекта DUnit на SourceForge
- Руководство по началу работы с DUnit easy
- Страница проекта PascalMock на SourceForge
- Страница TestGrip
DUnit

DUnit — инструмент для среды разработки Delphi, позволяющий производить автоматическое тестирование модулей. DUnit позволяет Delphi (Object Pascal) разработчикам использовать разработку через тестирование.
Первоначально DUnit был написан Juanco Anez и разрабатывался на основе JUnit в свою очередь написанный Kent Beck и Erich Gamma. Несколько разработчиков теперь ведут DUnit как проект на SourceForge.net.
DUnit стал стандартной частью в среде разработки Delphi 2005. Основная функциональность тестов находится в двух файлах: TestFramework.pas и GUITestRunner.pas.
Для Более ранних версий Delphi существует плагин, аналогичный тому, что включен в Delphi 2005.
Ссылки
- Проект DUnit на сайте SourceForge.net
- Войнов НиколайАвтоматизация тестирования для Delphi (2002 год). Архивировано из первоисточника 27 февраля 2012.Проверено 8 сентября 2010.
- Craig MurphyTest-Driven Development: A Practical Guide book review (англ.) . Архивировано из первоисточника 27 февраля 2012.Проверено 8 сентября 2010.
- Kris Golko To Err is Human: Automated Testing of Delphi code with DUnit (англ.) // UK-BUG magazine. — 2002. — В. Nov/Dec 2002.
- Rob Bracken Testing: Quality Time with DUnit (англ.) // The Delphi Magazine. — 2001. — В. 76.
- Программное обеспечение по алфавиту
- Экстремальное программирование
- Модульное тестирование
- Delphi
Wikimedia Foundation . 2010 .
DUnit Overview
Attention: Bear in mind that DUnit does not support the FireMonkey GUI to view your output; use DUnitX instead.
Note: DUnitX uses Delphi language features that are not supported in C++. For C++ developers, DUnit is the best tool. C++ can also be tested using other frameworks such as Google Test, available via GetIt Package Manager.
DUnit is an open-source unit test framework based on the JUnit test framework. The DUnit framework enables you to build and execute tests against Delphi Win32 applications. The RAD Studio integration of DUnit framework enables you to develop and execute tests against Delphi Win32 and C++Builder applications.
The DUnit testing framework provides its own set of methods for testing conditions. The methods represent common assertions. You can also create your own custom assertions. You can use the provided methods to test a large number of conditions.
Also, see the DUnit home page at http://dunit.sourceforge.net/
Contents
- 1 Installing DUnit
- 2 Developing Delphi DUnit Tests
- 3 Developing C++ DUnit Tests
- 4 DUnit Functions
- 5 DUnit Test Runners
- 6 See Also
Installing DUnit
To install the DUnit feature, follow these steps:
- Select Tools > Manage Platforms to open the Feature Manager Dialog.
- On the Additional Options tab, check the DUnit Testing Frameworks feature to add it to your RAD Studio installation.
- Select Apply to apply the selected changes.
When the changes are applied, the Feature Manager Dialog shows an Operation Complete screen.
Developing Delphi DUnit Tests
Every DUnit test implements a class of type TTestCase .
The following sample Delphi Win32 program defines two functions that perform simple addition and subtraction:
unit CalcUnit; interface type TCalc = class public function Add(x, y: Integer): Integer; function Sub(x, y: Integer): Integer; end; implementation function TCalc.Add(x, y: Integer): Integer; begin Result := x + y; end; function TCalc.Sub(X, Y: Integer): Integer; begin Result := x - y; end; end.
The following example shows the test case skeleton file that you need to modify to test the two functions, Add and Sub , in the preceding code.
unit TestCalcUnit; interface uses TestFramework, CalcUnit; type // Test methods for class TCalc TestTCalc = class(TTestCase) strict private aTCalc: TCalc; public procedure SetUp; override; procedure TearDown; override; published procedure TestAdd; procedure TestSub; end; implementation procedure TestTCalc.SetUp; begin aTCalc := TCalc.Create; end; procedure TestTCalc.TearDown; begin aTCalc := nil; end; procedure TestTCalc.TestAdd; var _result: System.Integer; y: System.Integer; x: System.Integer; begin _result := aTCalc.Add(x, y); // TODO: Add testcode here end; procedure TestTCalc.TestSub; var _result: System.Integer; y: System.Integer; x: System.Integer; begin _result := aTCalc.Sub(x, y); // TODO: Add testcode here end; initialization // Register any test cases with the test runner RegisterTest(TestTCalc.Suite); end.
Developing C++ DUnit Tests
Every DUnit test implements a class of type TTestCase .
The following sample C++ Win32 header file and program define two functions that perform simple addition and subtraction:
#ifndef Unit7H #define Unit7H //------------------------------------------------- class TCalc public: int Add(int x, int y); int Sub(int x, int y); >; #endif
The following example (TestUnit7.cpp) contains a Testcase for the TCalc class. The Wizard generated this example, but the user is expected to write tests that exercise the functions Add and Sub . The example illustrates the DUnit scaffolding for Unit Tests.
#include #pragma hdrstop #include class TTestTCalc : public TTestCase public: __fastcall virtual TTestTCalc(AnsiString name) : TTestCase(name) <> virtual void __fastcall SetUp(); virtual void __fastcall TearDown(); __published: void __fastcall TestAdd(); void __fastcall TestSub(); >; void __fastcall TTestTCalc::SetUp() > void __fastcall TTestTCalc::TearDown() > void __fastcall TTestTCalc::TestAdd() // int Add(int x, int y) > void __fastcall TTestTCalc::TestSub() // int Sub(int x, int y) > static void registerTests() _di_ITestSuite iSuite; TTestSuite* testSuite = new TTestSuite("Testing Unit7.h"); if (testSuite->GetInterface(iSuite)) testSuite->AddTests(__classid(TTestTCalc)); Testframework::RegisterTest(iSuite); > else delete testSuite; > > #pragma startup registerTests 33
DUnit Functions
DUnit provides a number of functions that you can use in your tests.
Checks to see if a condition was met.
Checks to see that the two items are equal.
Checks to see if items are not equal.
Checks to see that an item is not null.
Checks to see that an item is null.
Checks to see that the two items have the same value.
Checks to see that an error message emitted by the application matches a specified error message.
Checks that a routine fails.
Checks to see that a failure equals a specified failure condition.
Checks to see that a failure condition does not equal a specified failure condition.
Checks to see that two failure conditions are not the same.
Checks to see that the two error messages are not the same.
Checks that one error message does not match a specified error message.
For more information on the syntax and usage of these and other DUnit functions, see the DUnit help files in \source\dunit\doc .
DUnit Test Runners
A test runner allows you to run your tests independently of your application. In a DUnit test project, the test runner code from the DUnit framework is compiled directly into the generated executable making the test project itself a test runner. The integrated DUnit framework provides two test runners:
- The GUI Test Runner This displays test results interactively in a GUI window, with results color-coded to indicate success or failure.
- The Console Test Runner This sends all test output to the console.
The DUnit GUI Test Runner is very useful when actively developing unit tests for the code you are testing. The DUnit GUI Test Runner displays a green bar over a test that completes successfully, a red bar over a test that fails, and a yellow bar over a test that is skipped.
The DUnit Console Test Runner is useful when you want to run completed code and tests from automated build scripts.
See Also
- Unit Testing Overview
- Developing Tests
- DUnit documentation on SourceForge
- DUnit interest group
MindStream. Как мы пишем ПО под FireMonkey. Часть 3. DUnit + FireMonkey
В этой статье я хочу познакомить читателей с процессом переноса VCL кода в FireMonkey. В стандартной поставке Delphi, начиная по-моему с версии 2009, проект DUnit идёт из коробки.
Однако писался он в далекие времена VCL. И хотя и позволяет тестировать код написанный для FireMonkey (Благодаря консольному выводу), но у него нет «няшного» GUIRunner’a, к которому многие из нас привыкли, ведь в нём очень быстро и легко можно «убрать» те тесты которые мы не хотим запускать «именно сейчас».
Для тех кто совсем или мало знаком с DUnit. В обычном режиме из коробки, документация предлагает сделать File->New->Other->Unit Test->TestProject. Далее, Вам необходимо выбрать GUI или консольный вариант. Благодаря этим не столь сложным манипуляциям, у Вас появляется новый проект который должен выглядеть примерно так(по крайне мере «мое» XE7, сгенирировало именно такой код), для GUI:
program Project1Tests; < Delphi DUnit Test Project ------------------------- This project contains the DUnit test framework and the GUI/Console test runners. Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options to use the console test runner. Otherwise the GUI test runner will be used by default. > uses DUnitTestRunner, TestUnit1 in 'TestUnit1.pas', Unit1 in '..\DUnit.VCL\Unit1.pas'; begin DUnitTestRunner.RunRegisteredTests; end.
Следом добавляем TestCase, делается это также(File->New->Other->Unit Test->TestCase), в результате должно быть что-то похожее:
unit TestUnit1; < Delphi DUnit Test Case ---------------------- This unit contains a skeleton test case class generated by the Test Case Wizard. Modify the generated code to correctly setup and call the methods from the unit being tested. >interface uses TestFramework, System.SysUtils, Vcl.Graphics, Winapi.Windows, System.Variants, System.Classes, Vcl.Dialogs, Vcl.Controls, Vcl.Forms, Winapi.Messages, Unit1; type // Test methods for class TForm1 TestTForm1 = class(TTestCase) strict private FForm1: TForm1; public procedure SetUp; override; procedure TearDown; override; published procedure TestDoIt; end; implementation procedure TestTForm1.SetUp; begin FForm1 := TForm1.Create; end; procedure TestTForm1.TearDown; begin FForm1.Free; FForm1 := nil; end; procedure TestTForm1.TestDoIt; var ReturnValue: Integer; begin ReturnValue := FForm1.DoIt; // TODO: Validate method results end; initialization // Register any test cases with the test runner RegisterTest(TestTForm1.Suite); end.
В целом мой пример показывает как легко добавить тестирование, даже для Делфи7. Всё что нам надо, это — вызвать DUnitTestRunner.RunRegisteredTests;. И добавить новые файлы с TestCase в проект. Более детально вопрос тестирования с помощью DUnit рассмотрен тут.
Для реализации я решил, что необходимо просто повторить ребят которые делали DUnit.
Первая проблема(То, что TTreeNode, и TTreeViewItem «совсем не братья» даже говорить не буду, документация всех спасет) с которой я столкнулся была тут:
type TfmGUITestRunner = class(TForm) . protected FSuite: ITest; procedure SetSuite(Value: ITest); . public property Suite: ITest read FSuite write SetSuite; end; procedure RunTestModeless(aTest: ITest); var l_GUI: TfmGUITestRunner; begin Application.CreateForm(TfmGUITestRunner, l_GUI); l_GUI.Suite := aTest; l_GUI.Show; end; procedure TfmGUITestRunner.SetSuite(Value: ITest); begin FSuite := Value; // AV здесь if FSuite <> nil then InitTree; end;
Проблема как всегда, “узнается” в дебаге, ну или в документации:). В FireMonkey — Application.CreateForm();, не создает форму. Да, как ни странно. TApplication.CreateForm
Мой комментарий к комиту когда я разобрался 🙂
FSuite, Ещё не создана, так как Application.CreateForm на самом деле, если его не пнуть явно — «не создает сука, нормальных форм, а лишь ссылки на будущие классы. Что соответственно влияет на члены класса, которые совсем не nil, как им бы положено быть»
AV вылезет в System._IntfCopy(var Dest: IInterface; const Source: IInterface);
А вылезет потому что у нас в Dest будет мусор, а не interface или nil. И проявится это когда мы у предыдущего интерфейса(если он не // nil) будем вычитать 1.
Даже если мы такую строчку пропишем, это до фени
FSuite := nil;
Вот ещё одна ссылка по этому вопросу — . It doesn’t do what it says it does! Я если честно, тоже был немного в шоке, от того что метод который называется «СделатьФорму», не делает её.
Решаем проблему созданием форм явно(l_GUI := TfmGUITestRunner.create(nil) 😉 и идём дальше.
Теперь нам необходимо построить дерево тестов на основе TestCase’оф которые добавлены для тестирования. Если Вы обратили внимание, то процесс построения формы начинается с метода RunRegisteredTestsModeless:
procedure RunRegisteredTestsModeless; begin RunTestModeless(registeredTests) end;
Я решил не выносить этот метод в отдельный модуль, как создатели DUnit, поэтому для подключения fmGUITestRunner, вам необходимо указать модуль в коде проекта, ну и собственно вызвать нужный метод. В моем случае код проекта выглядит так:
program FMX.DUnit; uses FMX.Forms, // Форма тестирования u_fmGUITestRunner in 'u_fmGUITestRunner.pas' , // Тесты u_FirstTest in 'u_FirstTest.pas', u_TCounter in 'u_TCounter.pas', u_SecondTest in 'u_SecondTest.pas'; begin Application.Initialize; // Вызываем метод который я описал u_fmGUITestRunner.RunRegisteredTestsModeless; Application.Run; end.
Внимательный читатель, обратит внимание, что никаких registeredTests мы не добавляли, и совсем нигде не указывали какие тесты будут у нас добавляться. RegisteredTests это «глобальный» метод TestFrameWork, который подключен к нашей форме, возвращает он глобальную переменную — __TestRegistry: ITestSuite;
То как TestCase «попадают» в эту переменную, я оставлю за рамками этой статьи, тем более, что работу провели создатели DUnit. Однако если читатели изъявят интерес к этой теме, то отвечу в коментах. Итак, вернёмся к дереву. Метод для инициализации дерева:
procedure TfmGUITestRunner.InitTree; begin FTests.Clear; FillTestTree(Suite); TestTree.ExpandAll; end;
FTests, это список интерфейсных объектов который будет хранить список наших тестов. В свою очередь метод FillTestTree, является перегруженным, сделано это, так как мы не знаем, c корневым элементом дерева мы работаем, или с обычным узлом:
. procedure FillTestTree(aTest: ITest); overload; procedure FillTestTree(aRootNode: TTreeViewItem; aTest: ITest); overload; . procedure TfmGUITestRunner.FillTestTree(aRootNode: TTreeViewItem; aTest: ITest); var l_TestTests: IInterfaceList; l_Index: Integer; l_TreeViewItem: TTreeViewItem; begin if aTest = nil then Exit; l_TreeViewItem := TTreeViewItem.Create(self); l_TreeViewItem.IsChecked := True; // Добавляем тест в список, и в свойстве Tag сохраняем его индекс. Опыт работы с БД из прошлой работы :) l_TreeViewItem.Tag := FTests.Add(aTest); l_TreeViewItem.Text := aTest.Name; // Тут я думаю, всё ясно if aRootNode = nil then TestTree.AddObject(l_TreeViewItem) else aRootNode.AddObject(l_TreeViewItem); // ITest, содержит метод Tests, который является списком(IInterfaceList) "вложенных" тестов // Рекурсивно проходимся по всем тестам l_TestTests := aTest.Tests; for l_Index := 0 to l_TestTests.Count - 1 do FillTestTree(l_TreeViewItem, l_TestTests[l_Index] as ITest); end;
Как видим, в методе мы не только заполнили дерево, но и дали информацию каждому узлу, какой из тестов ему соответствует. Для того что бы получить тест по узлу, напишем метод NodeToTest:
function TfmGUITestRunner.NodeToTest(aNode: TTreeViewItem): ITest; var l_Index: Integer; begin assert(aNode.Tag >= 0); l_Index := aNode.Tag; Result := FTests[l_Index] as ITest; end;
Теперь добавим «знаний» тестам. В каждом тесте есть переменная GUIObject, типа TObject. SetupGUINodes мы будем вызывать на FormShow.
procedure TfmGUITestRunner.SetupGUINodes(aNode: TTreeViewItem); var l_Test: ITest; l_Index: Integer; begin for l_Index := 0 to Pred(aNode.Count) do begin // Получаем тест l_Test := NodeToTest(aNode.Items[l_Index]); assert(assigned(l_Test)); // Ассоциируем тест с необходимым узлом l_Test.GUIObject := aNode.Items[l_Index]; SetupGUINodes(aNode.Items[l_Index]); end; end;
Для того что-бы получить узел из теста напишем метод:
function TfmGUITestRunner.TestToNode(test: ITest): TTreeViewItem; begin assert(assigned(test)); Result := test.GUIObject as TTreeViewItem; assert(assigned(Result)); end;
То как я «связал» тесты с деревом, мне, да и старшему коллеге не понравилось. Зачем таким путем пошли разработчики DUnit, я догадываюсь. DUnit писался давно, и никаких Generic’ов тогда не было. В будущем мы конечно же это переделаем. В конце статьи я напишу о наших следующих доработках и «хотелках».
Итак — наше дерево строится, тесты находятся в FTests. Тесты и дерево связаны между собой. Пришло время запустить тесты, и обработать результаты. Для того что форма умела это делать, добавим ей реализацию интерфейса ITestListener, описанного в TestFrameWork:
< ITestListeners get notified of testing events. See TTestResult.AddListener() >ITestListener = interface(IStatusListener) [''] procedure TestingStarts; procedure StartTest(test: ITest); procedure AddSuccess(test: ITest); procedure AddError(error: TTestFailure); procedure AddFailure(Failure: TTestFailure); procedure EndTest(test: ITest); procedure TestingEnds(testResult :TTestResult); function ShouldRunTest(test :ITest):Boolean; end;
Добавим эти методы в описание класса, и реализуем их:
procedure TfmGUITestRunner.TestingStarts; begin FTotalTime := 0; end; procedure TfmGUITestRunner.StartTest(aTest: ITest); var l_Node: TTreeViewItem; begin assert(assigned(TestResult)); assert(assigned(aTest)); l_Node := TestToNode(aTest); assert(assigned(l_Node)); end; procedure TfmGUITestRunner.AddSuccess(aTest: ITest); begin assert(assigned(aTest)); SetTreeNodeFont(TestToNode(aTest), c_ColorOk) end; procedure TfmGUITestRunner.AddError(aFailure: TTestFailure); var l_ListViewItem: TListViewItem; begin SetTreeNodeFont(TestToNode(aFailure.failedTest), c_ColorError); l_ListViewItem := AddFailureNode(aFailure); end; procedure TfmGUITestRunner.AddFailure(aFailure: TTestFailure); var l_ListViewItem: TListViewItem; begin SetTreeNodeFont(TestToNode(aFailure.failedTest), c_ColorFailure); l_ListViewItem := AddFailureNode(aFailure); end; procedure TfmGUITestRunner.EndTest(test: ITest); begin // Закоментил, потому как тут надо обновлять общую информацию о результатах // тестов. А нам пока нечего показывать. // И если будет утверждение, то после первого захода сюда, результаты не отображаются // Пока так, однозначно TODO // assert(False); end; procedure TfmGUITestRunner.TestingEnds(aTestResult: TTestResult); begin FTotalTime := aTestResult.TotalTime; end; function TfmGUITestRunner.ShouldRunTest(aTest: ITest): Boolean; var l_Test: ITest; begin // Метод проверяет, стоит ли запускать тест. То как тесты "узнают" о "доступности" опишу ниже l_Test := aTest; Result := l_Test.Enabled end;
Объяснять тут особо нечего. Хотя если будут вопросы, то детально отвечу. В оригинале DUnitRunner при «получении» результата теста, менял картинку у соответствующего узла дерева. Я решил с картинками не морочиться, потому как из коробки их теперь нету, да и добавление картинки к узлу как-то заморочено сделано через стили. Поэтому решил ограничиться изменением FontColor и FontStyle для каждого узла.
Вроде делов на 1 минуту, а потратил пару часов, перекопав всю документацию:
procedure TfmGUITestRunner.SetTreeNodeFont(aNode: TTreeViewItem; aColor: TAlphaColor); begin // Пока не укажешь какие из настроек стиля разрешены к работе, они работать не будут aNode.StyledSettings := aNode.StyledSettings - [TStyledSetting.ssFontColor, TStyledSetting.ssStyle]; aNode.Font.Style := [TFontStyle.fsBold]; aNode.FontColor := aColor; end;
Для вывода результатов будем использовать ListView. Особенности TListView в FireMonkey таковы, что список полностью заточен под мобильные приложения. И лишился замечательного свойства Columns. Для добавления ошибок добавим метод AddFailureNode:
function TfmGUITestRunner.AddFailureNode(aFailure: TTestFailure): TListViewItem; var l_Item: TListViewItem; l_Node: TTreeViewItem; begin assert(assigned(aFailure)); l_Item := lvFailureListView.Items.Add; l_Item.Text := aFailure.failedTest.Name + '; ' + aFailure.thrownExceptionName + '; ' + aFailure.thrownExceptionMessage + '; ' + aFailure.LocationInfo + '; ' + aFailure.AddressInfo + '; ' + aFailure.StackTrace; l_Node := TestToNode(aFailure.failedTest); while l_Node <> nil do begin l_Node.Expand; l_Node := l_Node.ParentItem; end; Result := l_Item; end;
Пора запустить наши тесты, для чего добавим кнопку и метод запуска:
procedure TfmGUITestRunner.btRunAllTestClick(Sender: TObject); begin if Suite = nil then Exit; ClearResult; RunTheTest(Suite); end; procedure TfmGUITestRunner.RunTheTest(aTest: ITest); begin TestResult := TTestResult.Create; try TestResult.addListener(self); aTest.run(TestResult); finally FreeAndNil(FTestResult); end; end;
Запускаем наш Runner, нажимаем кнопку запуска тестов, в результате чего видим:

Последнее что нам осталось сделать, это обработать действия пользователя, во время изменения состояния узла:
procedure TfmGUITestRunner.TestTreeChangeCheck(Sender: TObject); begin SetNodeEnabled(Sender as TTreeViewItem, (Sender as TTreeViewItem).IsChecked); end; procedure TfmGUITestRunner.SetNodeEnabled(aNode: TTreeViewItem; aValue: Boolean); var l_Test: ITest; begin l_Test := NodeToTest(aNode); if l_Test <> nil then l_Test.Enabled := aValue; end;
Изменим состояние у чекбоксов некоторых узлов:

Код теста на котором я собственно проводил тестирования:
unit u_SecondTest; interface uses TestFrameWork; type TSecondTest = class(TTestCase) published procedure DoIt; procedure OtherDoIt; procedure ErrorTest; procedure SecondErrorTest; end; // TFirstTest implementation procedure TSecondTest.DoIt; begin Check(true); end; procedure TSecondTest.ErrorTest; begin raise ExceptionClass.Create('Error Message'); end; procedure TSecondTest.OtherDoIt; begin Check(true); end; procedure TSecondTest.SecondErrorTest; begin Check(False); end; initialization TestFrameWork.RegisterTest(TSecondTest.Suite); end.
Подведём итоги — на данном этапе, мы получили вполне рабочее приложение для тестирования кода FireMonkey, используя привычный GUIRunner. Проект открытый, так что пользоваться могут все желающие.
Планы на будущее:
Написать метод обхода дерева который будет получать лямбду. Дерево приходится обходить постоянно, а вот действия с каждой веткой разные, поэтому лямбда мне кажется уместной.
Замечания и предложения, от моего старшего коллеги:
Переделать связь Тест-Узел на TDictionary docwiki.embarcadero.com/Libraries/XE7/en/System.Generics.Collections.TDictionary
Добавить графический индикатор “прохода тестов”. Кнопки — выделить всё, снять всё и т.д. а также вывод результатов тестирования(время выполнения, количество успешных и провальных и т.д).
Добавить паттерн Декоратор для избавления от «костыля» GUIObject.
В ближайшем будущем мы начнем покрывать тестами наш основной проект — MindStream, а также по чуть-чуть будем доводить до ума Runner. Спасибо всем кто дочитал до конца. Замечания и критика, как всегда приветствуются в комментариях.
p.s. Проект располагается в репозитории MindStream\FMX.DUnit