Adapter Obiektów

Pomysł na ten wpis jest taki, że na początek, napiszę testy jednostkowe, które będą palić się na czerwono, w których zdefiniuje problem. Testy zapalę na zielono poprzez implementację wzorca Adapter.

Adapter przekształca interfejs klas na inny, oczekiwany przez klienta. Adapter umożliwia współdziałanie klasom, które z uwagi na niezgodne interfejsy standardowo nie mogą współdziałać ze sobą.

„Wzorce Projektowe”, Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides

Adapter dokonuje konwersji danej klasy do postaci innego interfejsu, zgodnie z oczekiwaniami klienta. Adapter pozwala na współpracę klas, które ze względu na niekompatybilne interfejsy wcześniej nie mogły ze sobą współpracować. {…}. Dzięki temu kod klienta nie musi być modyfikowany za każdym razem, kiedy będzie współpracować z innym interfejsem.

„Head First Design Patterns” Erich Freeman, Elisabeth Freeman, Bert Bates, Kathy Sierra

Adapter Obiektów

Na początek, napiszę test, w którym zdefiniuję problem. Po czym napiszę kod produkcyjny, który zapali ten test na zielono.

public class AdapterTester
{
  private readonly Client client;

  public AdapterTester()
  {
    client = new Client();
  }

  [Fact]
  public void call_request_method_in_target()
  {
    // arrange
    ITarget target = A.Fake();

    // act
    client.CallRequest(target);

    // assert
    A.CallTo(() => target.Request()).MustHaveHappened();
  }
}

Clinet posiada metodę CallRequest, która w argumencie pobiera obiekt spełniający interfejs ITarget. W teście sprawdzamy, czy w wyniku wywołania metody CallRequest została wywołana metoda Request na obiekcie target.

Aby ten test zapalił się na zielono potrzebna jest definicja interfejsu ITarget oraz implementacja klasy Client.

public interface ITarget
{
  void Request();
}

public class Client
{
  public void CallRequest(ITarget target)
  {
    target.Request();
  }
}

Teraz zdefiniuję kolejny test, którego zapalenie na zielono będzie wymagało napisania wzorca Adapter.

public class AdapterTester
{
  // ... pozostały kod jest niezmieniony
  [Fact]
  public void call_specific_request_method_in_adaptee()
  {
    // arrange
    IAdaptee adaptee = A.Fake();
    ITarget adapter = new Adapter(adaptee);

    // act
    client.CallRequest(adapter);

    // assert
    A.CallTo(() => adaptee.SpecificRequest()).MustHaveHappened();
  }
}

W teście tym pojawił się obiekt typu IAdaptee. IAdaptee posiada metodę SpecificRequest, co powoduje, że interfejs IAdaptee jest inny od ITarget. Klient nie będzie umieć współpracować z obiektem typu IAdapteeIAdaptee wymaga adaptacji do współdziałania z metodą CallRequest z klasy Client.

IAdaptee przekazany jest do konstruktora nowej klasy Adapter, która implementuje interfejs ITarget. Wewnątrz klasy Adapter nastąpi przetłumaczenie interfejsu IAdaptee na interfejs ITarget. W ostatniej linijce testu jest asercja mówiąca o tym, że w wyniku przetłumaczenia IAdaptee na ITarget, jeśli klient wywoła metodę Request na obiekcie typu ITarget to w rzeczywistości wywoła się metoda SpecificRequest na obiekcie typu IAdaptee.

Jak widać w teście, klasa Client pozostaje bez zmian — to ważne założenie. Adaptacja nowego interfejsu wykonywana jest bez najmniejszej zmiany klasy, do której ten nowy interfejs adaptujemy.

Test pali się teraz na czerwono. Kod się nie kompiluje. Definiuję teraz ten nowy interfejs, który będę adaptować.

public interface IAdaptee
{
  void SpecificRequest();
}

Teraz definiuję klasę Adapter, która w konstruktorze przyjmuje obiekt typu IAdaptee. Klasa Adapter implementuje interfejs ITarget, czyli ten pierwotny, do którego się adaptujemy.

public class Adapter : ITarget
{
  private readonly IAdaptee adaptee;

  public Adapter(IAdaptee adaptee)
  {
    this.adaptee = adaptee;
  }

  public void Request()
  {
    adaptee.SpecificRequest();
  }
}

Diagram klas wzorca Adapter Obiektów

Wzorzec Adapter Obiektów

Ja nie mam czasu nie pisać testów jednostkowych

Czasami (czasami często) słyszę, że:

…ktoś nie napisał testu jednostkowego bo nie miał na to czasu.

Gdy to słyszę to aż bolą mnie zęby. Jak można nie mieć czasu na sprawdzenie, czy nasz kod działa poprawnie? Wtedy zawsze staram się wyjaśnić, że:

… ja nie mam czasu nie pisać testów jednostkowych.

Continue reading →

Kalkulator napisany z pomocą behavior-driven development (BDD)

Postawiłem sobie za cel napisanie kalkulatora przy użyciu metody BDD a wynikami mojej pracy chcę się z wami podzielić.

Kalkulator ten będzie zachowywać się jak ten z lat dziewięćdziesiątych, pokazany poniżej na obrazku. Mam jeszcze taki sam w domu — ma już chyba 20 lat. Ten, który tutaj zaimplementuje, będzie posiadał, tak samo, jak ten oryginalny, kilka dziwnych zachowań. Na przykład, przy każdym ponownym wciśnięciu „=”, liczba na wyświetlaczu będzie się zwiększać o jeden, jeśli wcześniej wykonaliśmy działanie „1+1”.

Continue reading →

Pex For Fun

Jest ciekawa gierka dla programistów. Coś jak sudoku, czyli nie ma sensownego wytłumaczenia, dlaczego się w to gra, ale wciąga.

Pex For Fun polega na tym, aby napisać taki kod, który zwróci wyniki zgodne z oczekiwaniami. Przypomina to trochę TDD, ale nie do końca, ponieważ tutaj już mamy gotowe testy jednostkowe. Celem gry jest odgadnięcie i napisanie implementacji, która zapali wszystkie testy jednostkowe na zielono.

Kodu testów jednostkowych nie znamy. W zamian mamy tabelkę, w której są oczekiwane wartości wyjściowe dla zadanych wartości wejściowych. Sztuczka polega na tym, aby kod, który napiszemy, zwracał takie same wartości jak te oczekiwane w tabelce.

Continue reading →

O testach jednostkowych oraz o tym jak wygrałem NCrunch w DevTalk

DevTalk

Od niedawna Maciej Aniserowicz nagrywa około półgodzinne rozmowy, na tematy związane z wytwarzaniem oprogramowania. Polecam zajrzeć na stronę http://devtalk.pl/ i odsłuchać na próbę przynajmniej jeden z odcinków. Myślę, że większość z was wróci tam aby odsłuchiwać kolejne.

Zasady konkursu

W odcinku “03 – O TESTACH Z ADAMEM KOSIŃSKIM“, pojawił się konkurs, w którym można było wygrać licencję na NCrunch. Poniżej oryginał zasad konkursu ze strony:

Continue reading →