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