UFO ET IT

CodeIgniter에서 PHPUnit을 어떻게 사용합니까?

ufoet 2020. 12. 25. 00:15
반응형

CodeIgniter에서 PHPUnit을 어떻게 사용합니까?


PHPUnit, SimpleTest 및 기타 단위 테스트 프레임 워크에 대한 기사를 읽고 읽었습니다. 그들은 모두 훌륭하게 들립니다! 마침내 https://bitbucket.org/kenjis/my-ciunit/overview 덕분에 Codeigniter와 함께 작동하는 PHPUnit을 얻었습니다.

이제 내 질문은 어떻게 사용합니까?

내가 보는 모든 튜토리얼에는 assertEquals(2, 1+1)다음 과 같은 추상적 인 용도가 있습니다 .

public function testSpeakWithParams()
{
    $hello = new SayHello('Marco');
    $this->assertEquals("Hello Marco!", $hello->speak());
}

예측 가능한 문자열을 출력하는 함수가 있다면 좋습니다. 일반적으로 내 앱은 데이터베이스에서 데이터를 가져와 일종의 테이블에 표시합니다. 그렇다면 Codeigniter의 컨트롤러를 어떻게 테스트합니까?

Test-Driven Development를하고 싶습니다. 그리고 PHPUnits 사이트에서 튜토리얼을 읽었지만 다시 한번 예제가 너무 추상적으로 보입니다. 내 codeigniter 함수의 대부분이 데이터를 표시합니다.

실용적인 응용 프로그램과 PHPUnit 테스트의 예가 담긴 책이나 훌륭한 튜토리얼이 있습니까?


테스트 작성 및 단위 테스트 CodeIgniter 코드가 비 CI 코드 테스트와 다르지 않아야하는 기본 구조 / 구문을 이해하는 것 같습니다.

얼마 전에 PHPUnit에 대해 비슷한 질문이있었습니다. 정식 교육을받지 않은 사람으로서 저는 처음에는 단위 테스트 사고 방식에 들어가는 것이 추상적이고 부자연스러워 보였습니다. 나는 이것에 대한 주된 이유가-제 경우에는 아마도 질문에서 당신도 마찬가지 일 것입니다-당신이 지금까지 당신의 코드에서 우려를 분리하기 위해 정말로 일하는 데 집중하지 않았기 때문이라고 생각합니다 .

대부분의 방법 / 기능이 여러 가지 개별 작업을 수행 할 가능성이 있기 때문에 테스트 주장은 추상적으로 보입니다. 성공적인 테스트 정신은 코드에 대한 생각의 변화를 필요로합니다. "작동합니까?"라는 측면에서 성공에 대한 정의를 중단해야합니다. 대신 "작동하는지, 다른 코드와 잘 작동하는지, 다른 응용 프로그램에서 유용하게 사용할 수 있도록 설계되었으며 작동하는지 확인할 수 있습니까?"라고 질문해야합니다.

예를 들어, 다음은 지금까지 코드를 작성한 방법에 대한 간단한 예입니다.

function parse_remote_page_txt($type = 'index')
{
  $remote_file = ConfigSingleton::$config_remote_site . "$type.php";
  $local_file  = ConfigSingleton::$config_save_path;

  if ($txt = file_get_contents($remote_file)) {
    if ($values_i_want_to_save = preg_match('//', $text)) {
      if (file_exists($local_file)) {
        $fh = fopen($local_file, 'w+');
        fwrite($fh, $values_i_want_to_save);
        fclose($fh);
        return TRUE;
      } else {
        return FALSE;
      }
  } else {
    return FALSE;
  }  
}

여기서 무슨 일이 일어나고 있는지는 중요하지 않습니다. 이 코드를 테스트하기 어려운 이유를 설명하려고합니다.

  • 값을 생성하기 위해 싱글 톤 구성 클래스를 사용하고 있습니다. 함수의 성공 여부는 싱글 톤의 값에 따라 다르며, 다른 값으로 새 구성 개체를 인스턴스화 할 수없는 경우이 함수가 완전히 분리되어 올바르게 작동하는지 어떻게 테스트 할 수 있습니까? 더 나은 옵션은 $config값을 제어 할 수있는 구성 객체 또는 배열로 구성된 인수를 함수에 전달하는 것입니다. 이것은 광범위하게 " 종속성 주입 (Dependency Injection) " 이라고 불리며이 기술에 대한 논의가 웹 전체에 걸쳐 있습니다.

  • 중첩 된 IF문을 확인하십시오. 테스트는 어떤 종류의 테스트로 모든 실행 가능한 라인을 다루고 있음을 의미합니다. IF 문을 중첩하면 새 테스트 경로가 필요한 새 코드 분기를 만드는 것입니다.

  • 마지막으로,이 함수가 한 가지 (원격 파일의 내용을 구문 분석하는)를 수행하는 것처럼 보이지만 실제로 여러 작업을 수행하는 방법을 보십니까? 관심사를 열심히 분리하면 코드를 훨씬 더 테스트 할 수 있습니다. 이 작업을 수행하는 훨씬 더 테스트 가능한 방법은 ...


class RemoteParser() {
  protected $local_path;
  protected $remote_path;
  protected $config;

  /**
   * Class constructor -- forces injection of $config object
   * @param ConfigObj $config
   */
  public function __construct(ConfigObj $config) {
    $this->config = $config;
  }

  /**
   * Setter for local_path property
   * @param string $filename
   */
  public function set_local_path($filename) {
    $file = filter_var($filename);
    $this->local_path = $this->config->local_path . "/$file.html";
  }

  /**
   * Setter for remote_path property
   * @param string $filename
   */
  public function set_remote_path($filename) {
    $file = filter_var($filename);
    $this->remote_path = $this->config->remote_site . "/$file.html";
  }

  /**
   * Retrieve the remote source
   * @return string Remote source text
   */
  public function get_remote_path_src() {
    if ( ! $this->remote_path) {
      throw new Exception("you didn't set the remote file yet!");
    }
    if ( ! $this->local_path) {
      throw new Exception("you didn't set the local file yet!");
    }
    if ( ! $remote_src = file_get_contents($this->remote_path)) {
      throw new Exception("we had a problem getting the remote file!");
    }

    return $remote_src;
  }

  /**
   * Parse a source string for the values we want
   * @param string $src
   * @return mixed Values array on success or bool(FALSE) on failure
   */
  public function parse_remote_src($src='') {
    $src = filter_validate($src);
    if (stristr($src, 'value_we_want_to_find')) {
      return array('val1', 'val2');
    } else {
      return FALSE;
    }
  }

  /**
   * Getter for remote file path property
   * @return string Remote path
   */
  public function get_remote_path() {
    return $this->remote_path;
  }

  /**
   * Getter for local file path property
   * @return string Local path
   */
  public function get_local_path() {
    return $this->local_path;
  }
}

보시다시피 이러한 각 클래스 메서드는 쉽게 테스트 할 수있는 클래스의 특정 기능을 처리합니다. 원격 파일 검색이 작동 했습니까? 파싱하려는 값을 찾았습니까? 등. 갑자기 이러한 추상적 인 주장이 훨씬 더 유용 해 보입니다.

IMHO, the more you delve into testing the more you realize it's more about good code design and sensible architecture than simply making sure things work as expected. And here is where the benefits of OOP really start to shine. You can test procedural code just fine, but for a large project with interdependent parts testing has a way of enforcing good design. I know that may be troll bait for some procedural people but oh well.

The more you test, the more you'll find yourself writing code and asking yourself, "Will I be able to test this?" And if not, you'll probably change the structure then and there.

However, code need not be elementary to be testable. Stubbing and mocking allows you to test external operations the success or failure of which is entirely out of control. You can create fixtures to test database operations and pretty much anything else.

The more I test, the more I realize that if I'm having a tough time testing something it's most likely because I have an underlying design problem. If I straighten that out it usually results in all green bars in my test results.

Finally, here are a couple of links that really helped me to start thinking in a test-friendly fashion. The first one is a tongue-in-cheek list of what NOT to do if you want to write testable code. In fact, if you browse that entire site you'll find lots of helpful stuff that will help set you on the path to 100% code coverage. Another helpful article is this discussion of dependency injection.

Good luck!


I have unsuccessfully tried to use PHPUnit with Codeigniter. For example if I wanted to test my CI Models, I ran into the problem of how I will get an instance of that model as it somehow needs the whole CI framework to load it. Consider how you load a model for instance:

$this->load->model("domain_model");

The problem is that if you look at the super class for a load method you won't find it. It's not as straightforward if you're testing Plain Old PHP Objects where you can mock your dependencies easily and test functionality.

Hence, I have settled for CI's Unit testing class.

my apps grab a bunch of data from the database then display it in some sort of table.

If you're testing your controllers you're essentially testing the business logic (if you have) in there as well as the sql query that "grabs a bunch of data" from the database. This is already integration testing.

Best way to is to test the CI model first to test the grabbing of data --- this will be useful if you a have a very complicated query -- and then the controller next to test the business logic that is applied to the data grabbed by the CI Model. It is a good practice to test only one thing at a time. So what will you test? The query or the business logic?

I am assuming that you want to test the grabbing of data first, the general steps are

  1. Get some test data and setup your database, tables etc.

  2. Have some mechanism to populate the database with test data as well as delete it after the test. PHPUnit's Database extension has a way to do this although I don't know if that is supported by the framework you posted. Let us know.

  3. Write your test, pass it.

Your test method might look like this:

// At this point database has already been populated
public function testGetSomethingFromDB() {
    $something_model = $this->load->model("domain_model");
    $results = $something_model->getSomethings();
    $this->assertEquals(array(
       "item1","item2"), $results);

}
// After test is run database is truncated. 

Just in case you want to use CI's unit testing class, here is a modified code snippet of one test I wrote using it:

class User extends CI_Controller {
    function __construct() {
        parent::__construct(false);
        $this->load->model("user_model");
        $this->load->library("unit_test");
    }

public function testGetZone() {
            // POPULATE DATA FIRST
    $user1 = array(
        'user_no' => 11,
        'first_name' => 'First',
        'last_name' => 'User'
    );

    $this->db->insert('user',$user1);

            // run method
    $all = $this->user_model->get_all_users();
            // and test
    echo $this->unit->run(count($all),1);

            // DELETE
    $this->db->delete('user',array('user_no' => 11));

}

ReferenceURL : https://stackoverflow.com/questions/8395147/how-do-i-use-phpunit-with-codeigniter

반응형