code: from unittest.mock import MagicMock, patch
  
  import numpy as np
  import torch
  
  
  class _TestNN(torch.nn.Module):
      def __init__(self, input_size, output_size):
          super().__init__()
          self.decoder = torch.nn.Linear(input_size, output_size)
          self.forward_called = False
  
      def forward(self, nn_input, hidden):
          self.forward_called = True
          output = self.decoder(nn_input)
  
          return output, hidden
  
  
  def _print_success_message():
      print("Tests Passed")
  
  
  class AssertTest:
      def __init__(self, params):
          self.assert_param_message = "\n".join(
              [str(k) + ": " + str(v) + "" for k, v in params.items()]
          )
  
      def test(self, assert_condition, assert_message):
          assert assert_condition, (
              assert_message
              + "\n\nUnit Test Function Parameters\n"
              + self.assert_param_message
          )
  
  
  def test_create_lookup_tables(create_lookup_tables):
      test_text = """
          Moe_Szyslak Moe's Tavern Where the elite meet to drink
          Bart_Simpson Eh yeah hello is Mike there Last name Rotch
          Moe_Szyslak Hold on I'll check Mike Rotch Mike Rotch Hey has anybody seen Mike Rotch lately
          Moe_Szyslak Listen you little puke One of these days I'm gonna catch you and I'm gonna carve my name on your back with an ice pick
          Moe_Szyslak Whats the matter Homer You're not your normal effervescent self
          Homer_Simpson I got my problems Moe Give me another one
          Moe_Szyslak Homer hey you should not drink to forget your problems
          Barney_Gumble Yeah you should only drink to enhance your social skills"""
  
      test_text = test_text.lower()
      test_text = test_text.split()
  
      vocab_to_int, int_to_vocab = create_lookup_tables(test_text)
  
      # Check types
      assert isinstance(vocab_to_int, dict), "vocab_to_int is not a dictionary."
      assert isinstance(int_to_vocab, dict), "int_to_vocab is not a dictionary."
  
      # Compare lengths of dicts
      assert len(vocab_to_int) == len(int_to_vocab), (
          "Length of vocab_to_int and int_to_vocab don't match. "
          "vocab_to_int is length {}. int_to_vocab is length {}".format(
              len(vocab_to_int), len(int_to_vocab)
          )
      )
  
      # Make sure the dicts have the same words
      vocab_to_int_word_set = set(vocab_to_int.keys())
      int_to_vocab_word_set = set(int_to_vocab.values())
  
      assert not (vocab_to_int_word_set - int_to_vocab_word_set), (
          "vocab_to_int and int_to_vocab don't have the same words."
          "{} found in vocab_to_int, but not in int_to_vocab".format(
              vocab_to_int_word_set - int_to_vocab_word_set
          )
      )
      assert not (int_to_vocab_word_set - vocab_to_int_word_set), (
          "vocab_to_int and int_to_vocab don't have the same words."
          "{} found in int_to_vocab, but not in vocab_to_int".format(
              int_to_vocab_word_set - vocab_to_int_word_set
          )
      )
  
      # Make sure the dicts have the same word ids
      vocab_to_int_word_id_set = set(vocab_to_int.values())
      int_to_vocab_word_id_set = set(int_to_vocab.keys())
  
      assert not (vocab_to_int_word_id_set - int_to_vocab_word_id_set), (
          "vocab_to_int and int_to_vocab don't contain the same word ids."
          "{} found in vocab_to_int, but not in int_to_vocab".format(
              vocab_to_int_word_id_set - int_to_vocab_word_id_set
          )
      )
      assert not (int_to_vocab_word_id_set - vocab_to_int_word_id_set), (
          "vocab_to_int and int_to_vocab don't contain the same word ids."
          "{} found in int_to_vocab, but not in vocab_to_int".format(
              int_to_vocab_word_id_set - vocab_to_int_word_id_set
          )
      )
  
      # Make sure the dicts make the same lookup
      missmatches = [
          (word, id, id, int_to_vocab[id])
          for word, id in vocab_to_int.items()
          if int_to_vocab[id] != word
      ]
  
      assert (
          not missmatches
      ), "Found {} missmatche(s). First missmatch: vocab_to_int[{}] = {} and int_to_vocab[{}] = {}".format(
          len(missmatches), *missmatches[0]
      )
  
      assert (
          len(vocab_to_int) > len(set(test_text)) / 2
      ), "The length of vocab seems too small.  Found a length of {}".format(
          len(vocab_to_int)
      )
  
      _print_success_message()
  
  
  def test_tokenize(token_lookup):
      symbols = {".", ",", '"', ";", "!", "?", "(", ")", "-", "\n"}
      token_dict = token_lookup()
  
      # Check type
      assert isinstance(token_dict, dict), f"Returned type is {type(token_dict)}."
  
      # Check symbols
      missing_symbols = symbols - set(token_dict.keys())
      unknown_symbols = set(token_dict.keys()) - symbols
  
      assert not missing_symbols, f"Missing symbols: {missing_symbols}"
      assert not unknown_symbols, f"Unknown symbols: {unknown_symbols}"
  
      # Check values type
      bad_value_type = [
          type(val) for val in token_dict.values() if not isinstance(val, str)
      ]
  
      assert not bad_value_type, f"Found token as {bad_value_type[0]} type."
  
      # Check for spaces
      key_has_spaces = [k for k in token_dict.keys() if " " in k]
      val_has_spaces = [val for val in token_dict.values() if " " in val]
  
      assert (
          not key_has_spaces
      ), 'The key "{}" includes spaces. Remove spaces from keys and values'.format(
          key_has_spaces[0]
      )
      assert (
          not val_has_spaces
      ), 'The value "{}" includes spaces. Remove spaces from keys and values'.format(
          val_has_spaces[0]
      )
  
      # Check for symbols in values
      symbol_val = ()
      for symbol in symbols:
          for val in token_dict.values():
              if symbol in val:
                  symbol_val = (symbol, val)
  
      assert (
          not symbol_val
      ), "Don't use a symbol that will be replaced in your tokens. Found the symbol {} in value {}".format(
          *symbol_val
      )
  
      _print_success_message()
  
  
  def test_rnn(RNN, train_on_gpu):
      batch_size = 50
      sequence_length = 3
      vocab_size = 20
      output_size = 20
      embedding_dim = 15
      hidden_dim = 10
      n_layers = 2
  
      # create test RNN
      # params: (vocab_size, output_size, embedding_dim, hidden_dim, n_layers)
      rnn = RNN(vocab_size, output_size, embedding_dim, hidden_dim, n_layers)
  
      # create test input
      a = np.random.randint(vocab_size, size=(batch_size, sequence_length))
      # b = torch.LongTensor(a)
      b = torch.from_numpy(a)
      hidden = rnn.init_hidden(batch_size)
  
      if train_on_gpu:
          rnn.cuda()
          b = b.cuda()
  
      output, hidden_out = rnn(b, hidden)
  
      assert_test = AssertTest(
          {
              "Input Size": vocab_size,
              "Output Size": output_size,
              "Hidden Dim": hidden_dim,
              "N Layers": n_layers,
              "Batch Size": batch_size,
              "Sequence Length": sequence_length,
              "Input": b,
          }
      )
  
      # initialization
      correct_hidden_size = (n_layers, batch_size, hidden_dim)
  
      if type(hidden) == tuple:
          # LSTM
          assert_condition = hidden[0].size() == correct_hidden_size
      else:
          # GRU
          assert_condition = hidden.size() == correct_hidden_size
  
      assert_message = "Wrong hidden state size. Expected type {}. Got type {}".format(
          correct_hidden_size, hidden[0].size()
      )
      assert_test.test(assert_condition, assert_message)
  
      # output of rnn
      correct_hidden_size = (n_layers, batch_size, hidden_dim)
  
      if type(hidden) == tuple:
          # LSTM
          assert_condition = hidden_out[0].size() == correct_hidden_size
      else:
          # GRU
          assert_condition = hidden_out.size() == correct_hidden_size
  
      assert_message = "Wrong hidden state size. Expected type {}. Got type {}".format(
          correct_hidden_size, hidden_out[0].size()
      )
      assert_test.test(assert_condition, assert_message)
  
      correct_output_size = (batch_size, output_size)
      assert_condition = output.size() == correct_output_size
      assert_message = "Wrong output size. Expected type {}. Got type {}".format(
          correct_output_size, output.size()
      )
      assert_test.test(assert_condition, assert_message)
  
      _print_success_message()
  
  
  def test_forward_back_prop(RNN, forward_back_prop, train_on_gpu):
      batch_size = 200
      input_size = 20
      output_size = 10
      sequence_length = 3
      embedding_dim = 15
      hidden_dim = 10
      n_layers = 2
      learning_rate = 0.01
  
      # create test RNN
      rnn = RNN(input_size, output_size, embedding_dim, hidden_dim, n_layers)
  
      mock_decoder = MagicMock(wraps=_TestNN(input_size, output_size))
      if train_on_gpu:
          mock_decoder.cuda()
  
      mock_decoder_optimizer = MagicMock(
          wraps=torch.optim.Adam(mock_decoder.parameters(), lr=learning_rate)
      )
      mock_criterion = MagicMock(wraps=torch.nn.CrossEntropyLoss())
  
      with patch.object(
          torch.autograd, "backward", wraps=torch.autograd.backward
      ) as mock_autograd_backward:
          inp = torch.FloatTensor(np.random.rand(batch_size, input_size))
          target = torch.LongTensor(np.random.randint(output_size, size=batch_size))
  
          hidden = rnn.init_hidden(batch_size)
  
          loss, hidden_out = forward_back_prop(
              mock_decoder, mock_decoder_optimizer, mock_criterion, inp, target, hidden
          )
  
      if type(hidden_out) == tuple:
          # LSTM
          assert (
              hidden_out[0][0] == hidden[0][0]
          ).sum() == batch_size * hidden_dim, (
              "Returned hidden state is the incorrect size."
          )
      else:
          # GRU
          assert (
              hidden_out[0] == hidden[0]
          ).sum() == batch_size * hidden_dim, (
              "Returned hidden state is the incorrect size."
          )
  
      assert (
          mock_decoder.zero_grad.called or mock_decoder_optimizer.zero_grad.called
      ), "Didn't set the gradients to 0."
      assert mock_decoder.forward_called, "Forward propagation not called."
      assert mock_autograd_backward.called, "Backward propagation not called"
      assert mock_decoder_optimizer.step.called, "Optimization step not performed"
      assert type(loss) == float, "Wrong return type. Expected {}, got {}".format(
          float, type(loss)
      )
  
      _print_success_message()
  
apis: torch.nn.Linear torch.nn.CrossEntropyLoss torch.from_numpy numpy.random.rand numpy.random.randint