tunit - Reference Guide  1.0.0
Modern c++17 unit testing framework on Windows, macOS, Linux, iOS and android.
unit_test.h
Go to the documentation of this file.
1 #pragma once
4 
5 #include "event_listener.h"
7 #include "settings.h"
8 #include <chrono>
9 #include <iomanip>
10 #include <fstream>
11 #include <memory>
12 #include <random>
13 #include <string>
14 
16 namespace tunit {
18  template <typename TestClass>
19  class test_class_attribute;
21 
23  class unit_test {
24  public:
25  explicit unit_test(std::unique_ptr<tunit::event_listener> event_listener) noexcept;
26 
27  unit_test(std::unique_ptr<tunit::event_listener> event_listener, char* argv[], int argc) noexcept : arguments(argv + 1, argv + argc), name_(get_filename(argv[0])), event_listener_(std::move(event_listener)) {}
28 
30  virtual ~unit_test() {}
32 
35  int run() {
36  if (parse_arguments(this->arguments))
38 
39  if (tunit::settings::default_settings().list_tests()) {
40  std::vector<std::string> tests;
41  for (auto test_class : test_classes())
42  for(auto test : test_class.test()->tests())
43  tests.push_back(test_class.test()->name() + '.' + test.name());
44  return this->list_tests(tests);
45  }
46 
47  if (tunit::settings::default_settings().shuffle_test()) {
48  std::random_device rd;
49  std::mt19937 g = tunit::settings::default_settings().random_seed() == 0 ? std::mt19937(rd()) : std::mt19937(tunit::settings::default_settings().random_seed());
50  std::shuffle(test_classes().begin(), test_classes().end(), g);
51  }
52 
53  for (this->repeat_iteration_ = 1; this->repeat_iteration_ <= tunit::settings::default_settings().repeaat_test() || tunit::settings::default_settings().repeaat_test() < 0; ++this->repeat_iteration_) {
54  try {
55  this->event_listener_->on_unit_test_start(tunit::tunit_event_args(*this));
56 
57  this->event_listener_->on_unit_test_initialize_start(tunit::tunit_event_args(*this));
58  unit_test_initialize();
59  this->event_listener_->on_unit_test_initialize_end(tunit::tunit_event_args(*this));
60 
61  this->start_time_point_ = std::chrono::high_resolution_clock::now();
62  for (auto& test_class : test_classes())
63  if (test_class.test()->test_count())
64  test_class.test()->run(*this);
65  this->end_time_point_ = std::chrono::high_resolution_clock::now();
66 
67  this->event_listener_->on_unit_test_cleanup_start(tunit::tunit_event_args(*this));
68  unit_test_cleanup();
69  this->event_listener_->on_unit_test_cleanup_end(tunit::tunit_event_args(*this));
70 
71  this->event_listener_->on_unit_test_end(tunit::tunit_event_args(*this));
72  } catch(const std::exception&) {
74  // do error...
75  } catch(...) {
77  // do error...
78  }
79  }
80 
81  tunit::settings::default_settings().end_time(std::chrono::system_clock::now());
82  write_xml();
83 
85  }
86 
87  int repeat_iteration() const noexcept {return this->repeat_iteration_;}
88 
89  int repeat_iteration_count() const noexcept {return tunit::settings::default_settings().repeaat_test();}
90 
91  bool repeat_tests() const noexcept {return tunit::settings::default_settings().repeaat_test() != 1;}
92 
93  size_t test_cases_count() const noexcept {
94  size_t count = 0;
95  for (auto test_class : this->test_classes())
96  if (test_class.test()->test_count())
97  count ++;
98  return count;
99  }
100 
101  size_t test_count() const noexcept {
102  size_t count = 0;
103  for (auto test_class : this->test_classes())
104  count += test_class.test()->test_count();
105  return count;
106  }
107 
108  size_t aborted_test_count() const noexcept {
109  size_t count = 0;
110  for (auto& test_class : this->test_classes())
111  for (auto& test : test_class.test()->tests())
112  if (settings::default_settings().is_match_test_name(test_class.test()->name(), test.name()) && test.aborted()) count++;
113  return count;
114  }
115 
116  std::vector<std::string> aborted_test_names() const noexcept {
117  std::vector<std::string> names;
118  for (auto& test_class : this->test_classes())
119  for (auto& test : test_class.test()->tests())
120  if (settings::default_settings().is_match_test_name(test_class.test()->name(), test.name()) && test.aborted()) names.push_back(test_class.test()->name() + "." + test.name());
121  return names;
122  }
123 
124  std::chrono::milliseconds elapsed_time() const noexcept {
125  using namespace std::chrono_literals;
126  if (this->start_time_point_.time_since_epoch() == 0ms && this->end_time_point_.time_since_epoch() == 0ms) return 0ms;
127  if (this->end_time_point_.time_since_epoch() == 0ms) return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - this->start_time_point_);
128  return std::chrono::duration_cast<std::chrono::milliseconds>(this->end_time_point_ - this->start_time_point_);
129  }
130 
131  size_t ignored_test_count() const noexcept {
132  size_t count = 0;
133  for (auto test_class : this->test_classes())
134  count += test_class.test()->ignored_test_count();
135  return count;
136  }
137 
138  std::vector<std::string> ignored_test_names() const noexcept {
139  std::vector<std::string> names;
140  for (auto& test_class : this->test_classes())
141  for (auto& test : test_class.test()->tests())
142  if (settings::default_settings().is_match_test_name(test_class.test()->name(), test.name()) && test.ignored()) names.push_back(test_class.test()->name() + "." + test.name());
143  return names;
144  }
145 
146  size_t failed_test_count() const noexcept {
147  size_t count = 0;
148  for (auto& test_class : this->test_classes())
149  for (auto& test : test_class.test()->tests())
150  if (settings::default_settings().is_match_test_name(test_class.test()->name(), test.name()) && test.failed()) count++;
151  return count;
152  }
153 
154  std::vector<std::string> failed_test_names() const noexcept {
155  std::vector<std::string> names;
156  for (auto& test_class : this->test_classes())
157  for (auto& test : test_class.test()->tests())
158  if (settings::default_settings().is_match_test_name(test_class.test()->name(), test.name()) && test.failed()) names.push_back(test_class.test()->name() + "." + test.name());
159  return names;
160  }
161 
162  size_t succeed_test_count() const noexcept {
163  size_t count = 0;
164  for (auto& test_class : this->test_classes())
165  for (auto& test : test_class.test()->tests())
166  if (settings::default_settings().is_match_test_name(test_class.test()->name(), test.name()) && test.succeed()) count++;
167  return count;
168  }
169 
170  std::vector<std::string> succeed_test_names() const noexcept {
171  std::vector<std::string> names;
172  for (auto& test_class : this->test_classes())
173  for (auto& test : test_class.test()->tests())
174  if (settings::default_settings().is_match_test_name(test_class.test()->name(), test.name()) && test.succeed()) names.push_back(test_class.test()->name() + "." + test.name());
175  return names;
176  }
177 
178  protected:
179  virtual int list_tests(const std::vector<std::string>& tests) {
181  }
182 
183  virtual bool parse_arguments(const std::vector<std::string>& args) {
184  for (auto arg : args) {
185  if (arg == "--also_run_ignored_tests") tunit::settings::default_settings().also_run_ignored_tests(true);
186  else if (arg.find("--filter_tests=") == 0) tunit::settings::default_settings().filter_tests(arg.substr(15));
187  else if (arg == "--list_tests") tunit::settings::default_settings().list_tests(true);
188  else if (arg == "--output_color=true") tunit::settings::default_settings().output_color(true);
189  else if (arg == "--output_color=false") tunit::settings::default_settings().output_color(false);
190  else if (arg.find("--output_xml") == 0) { tunit::settings::default_settings().output_xml(true);
191  if (arg[12] == '=') tunit::settings::default_settings().output_xml_path(arg.substr(13));
192  } else if (arg.find("--random_seed=") == 0) tunit::settings::default_settings().random_seed(std::stoi(arg.substr(14)));
193  else if (arg.find("--repeat_tests=") == 0) tunit::settings::default_settings().repeat_tests(std::stoi(arg.substr(15)));
194  else if (arg == "--show_duration=true") tunit::settings::default_settings().show_duration(true);
195  else if (arg == "--show_duration=false") tunit::settings::default_settings().show_duration(false);
196  else if (arg == "--shuffle_tests") tunit::settings::default_settings().shuffle_test(true);
197  }
198  return false;
199  }
200 
201  private:
202  template <typename TestClass>
203  friend class tunit::test_class_attribute;
204  friend class tunit::test_class;
205  friend class tunit::test;
206  friend class tunit::base_assert;
207 
208  static void add(const tunit::registered_test_class& test_class) {test_classes().push_back(test_class);}
209 
210  void unit_test_cleanup() {
211  }
212 
213  void unit_test_initialize() {
214  }
215 
216  static std::vector<tunit::registered_test_class>& test_classes() {
217  static std::vector<tunit::registered_test_class> test_classes;
218  return test_classes;
219  }
220 
221  std::string get_filename(const std::string& path) {
222  std::string filename = path;
223  const size_t last_slash_idx = filename.find_last_of("\\/");
224  if (std::string::npos != last_slash_idx)
225  filename.erase(0, last_slash_idx + 1);
226 
227  const size_t period_idx = filename.rfind('.');
228  if (std::string::npos != period_idx)
229  filename.erase(period_idx);
230  return filename;
231  }
232 
233  std::string to_string(const std::chrono::milliseconds& ms) {
234  std::stringstream ss;
235  if (ms.count() == 0)
236  ss << 0;
237  else
238  ss << ms.count() / 1000 << "." << std::setfill('0') << std::setw(3) << ms.count() % 1000;
239  return ss.str();
240  }
241 
242  std::string to_string(const std::chrono::time_point<std::chrono::system_clock>& time) {
243  std::time_t time_t = std::chrono::system_clock::to_time_t(time);
244  std::tm tm = *std::localtime(&time_t);
245  std::stringstream ss;
246  ss << tm.tm_year + 1900 << "-" << std::setfill('0') << std::setw(2) << tm.tm_mon << "-" << std::setfill('0') << std::setw(2) << tm.tm_mday;
247  ss << "T" << std::setfill('0') << std::setw(2) << tm.tm_hour << ":" << std::setfill('0') << std::setw(2) << tm.tm_min << ":" << std::setfill('0') << std::setw(2) << tm.tm_sec;
248  return ss.str();
249  }
250 
251  std::string status_to_string(const tunit::test& test) {
252  std::stringstream ss;
253  if (test.not_started() || test.ignored()) ss << "notrun";
254  else ss << "run";
255  return ss.str();
256  }
257 
258  std::string message_to_string(const tunit::test& test) {
259  std::stringstream ss;
260  if (test.line_info() != tunit::line_info::empty())
261  ss << test.line_info().file_path() << ":" << test.line_info().line_number() << "&#0x0A;";
262  ss << "Expected: " << test.expect() << "&#0x0A;";
263  ss << "But was : " << test.actual();
264  return ss.str();
265  }
266 
267  std::string cdata_message_to_string(const tunit::test& test) {
268  std::stringstream ss;
269  if (test.line_info() != tunit::line_info::empty())
270  ss << test.line_info().file_path() << ":" << test.line_info().line_number() << std::endl;
271  ss << "Expected: " << test.expect() << std::endl;
272  ss << "But was : " << test.actual();
273  return ss.str();
274  }
275 
276  void write_xml() {
277  if (tunit::settings::default_settings().output_xml()) {
278  std::fstream file(tunit::settings::default_settings().output_xml_path(), std::ios::out | std::ios::trunc);
279  file << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" << std::endl;
280  file << "<testsuites tests=\"" << this->test_count() << "\" failures=\"" << this->failed_test_count() << "\" disabled=\"" << this->ignored_test_count() << "\" errors=\"" << 0 << "\" timestamp=\"" << to_string(tunit::settings::default_settings().start_time()) << "\" time=\"" << to_string(this->elapsed_time()) << "\" name=\"" << this->name_ << "\">" << std::endl;
281  for (auto& test_class : test_classes()) {
282  file << " <testsuite name=\"" << test_class.test()->name() << "\" tests=\"" << test_class.test()->test_count() << "\" failures=\"" << test_class.test()->failed_test_count() << "\" disabled=\"" << test_class.test()->ignored_test_count() << "\" error=\"" << test_class.test()->failed_test_count() << "\" time=\"" << to_string(test_class.test()->elapsed_time()) << "\">" << std::endl;
283  for (auto& test : test_class.test()->tests()) {
284  file << " <testcase name=\"" << test.name() << "\" status=\"" << status_to_string(test) << "\" time=\"" << to_string(test.elapsed_time()) << "\" classname=\"" << test_class.test()->name() << "\"";
285  if (!test.failed())
286  file << " />" << std::endl;
287  else {
288  file << ">" << std::endl;
289  file << " <failure message=\"" << message_to_string(test) << "\" type= \"" << "\">" << "<![CDATA[" << cdata_message_to_string(test) << "]]></failure>" << std::endl;
290  file << " </testcase>" << std::endl;
291  }
292  }
293  file << " </testsuite>" << std::endl;
294  }
295  file << "</testsuites>" << std::endl;
296  file.close();
297  }
298  }
299 
300  std::vector<std::string> arguments;
301  std::string name_ = "AllTests";
302  std::unique_ptr<tunit::event_listener> event_listener_;
303  std::chrono::high_resolution_clock::time_point end_time_point_;
304  int repeat_iteration_ = 0;
305  std::chrono::high_resolution_clock::time_point start_time_point_;
306  };
307 }
Contains tunit::registered_test_class class.
bool is_match_test_name(const std::string &test_class_name, const std::string &test_name) const noexcept
Return true if a specified test class name and specified test name match with the current filter test...
Definition: settings.h:60
Definition: test_class.h:21
int repeaat_test() const noexcept
Gets repeat tests count.
Definition: settings.h:117
Definition: test_class_attribute.h:11
unsigned int line_number() const noexcept
Gets the line number.
Definition: line_info.h:50
static tunit::settings & default_settings() noexcept
Get default settings intance.
Definition: settings.h:23
int exit_status() const noexcept
Gets exit status.
Definition: settings.h:40
std::chrono::time_point< std::chrono::system_clock > end_time() const noexcept
Gets unit test end time.
Definition: settings.h:134
The template class.
Definition: unit_test.h:23
bool output_color() const noexcept
Gets output color.
Definition: settings.h:72
static tunit::line_info empty()
Return an empty line info.
Definition: line_info.h:31
void repeat_tests(int repeat_tests) noexcept
Sets repeat tests count.
Definition: settings.h:122
bool also_run_ignored_tests() const noexcept
Gets also run ignored test.
Definition: settings.h:30
int run()
Runs all tests in this UnitTest object and prints the result.
Definition: unit_test.h:35
tunit_event_args is the base class for classes containing event data.
Definition: tunit_event_args.h:12
Represent the event listener class.
Definition: event_listener.h:15
bool output_xml() const noexcept
Gets output xml.
Definition: settings.h:80
Definition: base_assert.h:15
The tunit namespace contains a unit test library.
Definition: abort_error.h:8
Contains tunit::settings class.
Contains tunit::event_listener class.
Definition: registered_test_class.h:10
Definition: test.h:20
bool show_duration() const noexcept
Gets if show duration for each test.
Definition: settings.h:126
bool shuffle_test() const noexcept
Gets shuffle tests.
Definition: settings.h:97
std::string output_xml_path() const noexcept
Gets output xml path.
Definition: settings.h:88
const std::string & filter_tests() const noexcept
Gets filter tests.
Definition: settings.h:51
int random_seed() const noexcept
Gets random seed value.
Definition: settings.h:107
const std::string & file_path() const noexcept
Gets the file path.
Definition: line_info.h:46
bool list_tests() const noexcept
Gets list tests.
Definition: settings.h:64